1 /*
2     This file is part of KMail, the KDE mail client.
3     SPDX-FileCopyrightText: 2002 Don Sanders <sanders@kde.org>
4     SPDX-FileCopyrightText: 2013-2021 Laurent Montel <montel@kde.org>
5 
6     SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 //
10 // This file implements various "command" classes. These command classes
11 // are based on the command design pattern.
12 //
13 // Historically various operations were implemented as slots of KMMainWin.
14 // This proved inadequate as KMail has multiple top level windows
15 // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
16 // benefit from using these operations. It is desirable that these
17 // classes can operate without depending on or altering the state of
18 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
19 //
20 // Now these operations have been rewritten as KMCommand based classes,
21 // making them independent of KMMainWin.
22 //
23 // The base command class KMCommand is async, which is a difference
24 // from the conventional command pattern. As normal derived classes implement
25 // the execute method, but client classes call start() instead of
26 // calling execute() directly. start() initiates async operations,
27 // and on completion of these operations calls execute() and then deletes
28 // the command. (So the client must not construct commands on the stack).
29 //
30 // The type of async operation supported by KMCommand is retrieval
31 // of messages from an IMAP server.
32 
33 #include "kmcommands.h"
34 
35 #include "kmail_debug.h"
36 #include "kmreadermainwin.h"
37 #include "secondarywindow.h"
38 #include "settings/kmailsettings.h"
39 #include "util.h"
40 #include "widgets/collectionpane.h"
41 
42 #include "job/createforwardmessagejob.h"
43 #include "job/createreplymessagejob.h"
44 
45 #include "editor/composer.h"
46 #include "kmmainwidget.h"
47 #include "undostack.h"
48 
49 #include <KIdentityManagement/IdentityManager>
50 
51 #include <KMime/MDN>
52 #include <KMime/Message>
53 
54 #include <Akonadi/ItemCopyJob>
55 #include <Akonadi/ItemCreateJob>
56 #include <Akonadi/ItemDeleteJob>
57 #include <Akonadi/ItemFetchJob>
58 #include <Akonadi/ItemModifyJob>
59 #include <Akonadi/ItemMoveJob>
60 #include <Akonadi/Tag>
61 #include <Akonadi/TagCreateJob>
62 
63 #include <MailCommon/CryptoUtils>
64 #include <MailCommon/FilterAction>
65 #include <MailCommon/FilterManager>
66 #include <MailCommon/FolderSettings>
67 #include <MailCommon/MDNStateAttribute>
68 #include <MailCommon/MailFilter>
69 #include <MailCommon/MailKernel>
70 #include <MailCommon/MailUtil>
71 #include <MailCommon/RedirectDialog>
72 
73 #include <MessageCore/MailingList>
74 #include <MessageCore/MessageCoreSettings>
75 #include <MessageCore/StringUtil>
76 
77 #include <MessageComposer/MessageComposerSettings>
78 #include <MessageComposer/MessageHelper>
79 #include <MessageComposer/MessageSender>
80 #include <MessageComposer/Util>
81 
82 #include <MessageList/Pane>
83 
84 #include <MessageViewer/CSSHelper>
85 #include <MessageViewer/HeaderStylePlugin>
86 #include <MessageViewer/MessageViewerSettings>
87 #include <MessageViewer/MessageViewerUtil>
88 #include <MessageViewer/ObjectTreeEmptySource>
89 
90 #include <MimeTreeParser/NodeHelper>
91 #include <MimeTreeParser/ObjectTreeParser>
92 
93 #include <MailTransport/TransportManager>
94 #include <MailTransportAkonadi/SentBehaviourAttribute>
95 #include <MailTransportAkonadi/TransportAttribute>
96 
97 #include <Libkdepim/ProgressManager>
98 #include <PimCommon/BroadcastStatus>
99 
100 #include <KCursorSaver>
101 
102 #include <gpgme++/error.h>
103 
104 #include <KBookmarkManager>
105 
106 #include <KEmailAddress>
107 #include <KFileWidget>
108 #include <KLocalizedString>
109 #include <KMessageBox>
110 #include <KRecentDirs>
111 
112 // KIO headers
113 #include <KIO/JobUiDelegate>
114 #include <KIO/StatJob>
115 
116 #include <QApplication>
117 #include <QByteArray>
118 #include <QFileDialog>
119 #include <QFontDatabase>
120 #include <QProgressDialog>
121 #include <QStandardPaths>
122 
123 using KMail::SecondaryWindow;
124 using MailTransport::TransportManager;
125 using MessageComposer::MessageFactoryNG;
126 
127 using KPIM::ProgressItem;
128 using KPIM::ProgressManager;
129 using namespace KMime;
130 
131 using namespace MailCommon;
132 
133 /// Helper to sanely show an error message for a job
showJobError(KJob * job)134 static void showJobError(KJob *job)
135 {
136     assert(job);
137     // we can be called from the KJob::kill, where we are no longer a KIO::Job
138     // so better safe than sorry
139     auto kiojob = qobject_cast<KIO::Job *>(job);
140     if (kiojob && kiojob->uiDelegate()) {
141         kiojob->uiDelegate()->showErrorMessage();
142     } else {
143         qCWarning(KMAIL_LOG) << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString();
144     }
145 }
146 
KMCommand(QWidget * parent)147 KMCommand::KMCommand(QWidget *parent)
148     : mDeletesItself(false)
149     , mEmitsCompletedItself(false)
150     , mParent(parent)
151 {
152 }
153 
KMCommand(QWidget * parent,const Akonadi::Item & msg)154 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item &msg)
155     : mDeletesItself(false)
156     , mEmitsCompletedItself(false)
157     , mParent(parent)
158 {
159     if (msg.isValid() || msg.hasPayload<KMime::Message::Ptr>()) {
160         mMsgList.append(msg);
161     }
162 }
163 
KMCommand(QWidget * parent,const Akonadi::Item::List & msgList)164 KMCommand::KMCommand(QWidget *parent, const Akonadi::Item::List &msgList)
165     : mDeletesItself(false)
166     , mEmitsCompletedItself(false)
167     , mParent(parent)
168 {
169     mMsgList = msgList;
170 }
171 
172 KMCommand::~KMCommand() = default;
173 
result() const174 KMCommand::Result KMCommand::result() const
175 {
176     if (mResult == Undefined) {
177         qCDebug(KMAIL_LOG) << "mResult is Undefined";
178     }
179     return mResult;
180 }
181 
retrievedMsgs() const182 const Akonadi::Item::List KMCommand::retrievedMsgs() const
183 {
184     return mRetrievedMsgs;
185 }
186 
retrievedMessage() const187 Akonadi::Item KMCommand::retrievedMessage() const
188 {
189     if (mRetrievedMsgs.isEmpty()) {
190         return Akonadi::Item();
191     }
192     return *(mRetrievedMsgs.begin());
193 }
194 
parentWidget() const195 QWidget *KMCommand::parentWidget() const
196 {
197     return mParent;
198 }
199 
deletesItself() const200 bool KMCommand::deletesItself() const
201 {
202     return mDeletesItself;
203 }
204 
setDeletesItself(bool deletesItself)205 void KMCommand::setDeletesItself(bool deletesItself)
206 {
207     mDeletesItself = deletesItself;
208 }
209 
emitsCompletedItself() const210 bool KMCommand::emitsCompletedItself() const
211 {
212     return mEmitsCompletedItself;
213 }
214 
setEmitsCompletedItself(bool emitsCompletedItself)215 void KMCommand::setEmitsCompletedItself(bool emitsCompletedItself)
216 {
217     mEmitsCompletedItself = emitsCompletedItself;
218 }
219 
setResult(KMCommand::Result result)220 void KMCommand::setResult(KMCommand::Result result)
221 {
222     mResult = result;
223 }
224 
225 int KMCommand::mCountJobs = 0;
226 
start()227 void KMCommand::start()
228 {
229     connect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer);
230 
231     if (mMsgList.isEmpty()) {
232         Q_EMIT messagesTransfered(OK);
233         return;
234     }
235 
236     // Special case of operating on message that isn't in a folder
237     const Akonadi::Item mb = mMsgList.constFirst();
238     if ((mMsgList.count() == 1) && MessageComposer::Util::isStandaloneMessage(mb)) {
239         mRetrievedMsgs.append(mMsgList.takeFirst());
240         Q_EMIT messagesTransfered(OK);
241         return;
242     }
243 
244     // we can only retrieve items with a valid id
245     for (const Akonadi::Item &item : std::as_const(mMsgList)) {
246         if (!item.isValid()) {
247             Q_EMIT messagesTransfered(Failed);
248             return;
249         }
250     }
251 
252     // transfer the selected messages first
253     transferSelectedMsgs();
254 }
255 
slotPostTransfer(KMCommand::Result result)256 void KMCommand::slotPostTransfer(KMCommand::Result result)
257 {
258     disconnect(this, &KMCommand::messagesTransfered, this, &KMCommand::slotPostTransfer);
259     if (result == OK) {
260         result = execute();
261     }
262     mResult = result;
263     if (!emitsCompletedItself()) {
264         Q_EMIT completed(this);
265     }
266     if (!deletesItself()) {
267         deleteLater();
268     }
269 }
270 
createFetchJob(const Akonadi::Item::List & items)271 Akonadi::ItemFetchJob *KMCommand::createFetchJob(const Akonadi::Item::List &items)
272 {
273     return new Akonadi::ItemFetchJob(items, this);
274 }
275 
transferSelectedMsgs()276 void KMCommand::transferSelectedMsgs()
277 {
278     // make sure no other transfer is active
279     if (KMCommand::mCountJobs > 0) {
280         Q_EMIT messagesTransfered(Failed);
281         return;
282     }
283 
284     bool complete = true;
285     KMCommand::mCountJobs = 0;
286     mCountMsgs = 0;
287     mRetrievedMsgs.clear();
288     mCountMsgs = mMsgList.count();
289     uint totalSize = 0;
290     // the QProgressDialog for the user-feedback. Only enable it if it's needed.
291     // For some commands like KMSetStatusCommand it's not needed. Note, that
292     // for some reason the QProgressDialog eats the MouseReleaseEvent (if a
293     // command is executed after the MousePressEvent), cf. bug #71761.
294     if (mCountMsgs > 0) {
295         mProgressDialog = new QProgressDialog(mParent);
296         mProgressDialog.data()->setWindowTitle(i18nc("@title:window", "Please wait"));
297 
298         mProgressDialog.data()->setLabelText(
299             i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count()));
300         mProgressDialog.data()->setModal(true);
301         mProgressDialog.data()->setMinimumDuration(1000);
302     }
303 
304     // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever
305     if (!mFetchScope.isEmpty()) {
306         complete = false;
307         ++KMCommand::mCountJobs;
308         Akonadi::ItemFetchJob *fetch = createFetchJob(mMsgList);
309         mFetchScope.fetchAttribute<MailCommon::MDNStateAttribute>();
310         fetch->setFetchScope(mFetchScope);
311         connect(fetch, &Akonadi::ItemFetchJob::itemsReceived, this, &KMCommand::slotMsgTransfered);
312         connect(fetch, &Akonadi::ItemFetchJob::result, this, &KMCommand::slotJobFinished);
313     } else {
314         // no need to fetch anything
315         if (!mMsgList.isEmpty()) {
316             mRetrievedMsgs = mMsgList;
317         }
318     }
319 
320     if (complete) {
321         delete mProgressDialog.data();
322         mProgressDialog.clear();
323         Q_EMIT messagesTransfered(OK);
324     } else {
325         // wait for the transfer and tell the progressBar the necessary steps
326         if (mProgressDialog.data()) {
327             connect(mProgressDialog.data(), &QProgressDialog::canceled, this, &KMCommand::slotTransferCancelled);
328             mProgressDialog.data()->setMaximum(totalSize);
329         }
330     }
331 }
332 
slotMsgTransfered(const Akonadi::Item::List & msgs)333 void KMCommand::slotMsgTransfered(const Akonadi::Item::List &msgs)
334 {
335     if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
336         Q_EMIT messagesTransfered(Canceled);
337         return;
338     }
339     // save the complete messages
340     mRetrievedMsgs.append(msgs);
341 }
342 
slotJobFinished()343 void KMCommand::slotJobFinished()
344 {
345     // the job is finished (with / without error)
346     KMCommand::mCountJobs--;
347 
348     if (mProgressDialog.data() && mProgressDialog.data()->wasCanceled()) {
349         return;
350     }
351 
352     if (mCountMsgs > mRetrievedMsgs.count()) {
353         // the message wasn't retrieved before => error
354         if (mProgressDialog.data()) {
355             mProgressDialog.data()->hide();
356         }
357         slotTransferCancelled();
358         return;
359     }
360     // update the progressbar
361     if (mProgressDialog.data()) {
362         mProgressDialog.data()->setLabelText(
363             i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", KMCommand::mCountJobs));
364     }
365     if (KMCommand::mCountJobs == 0) {
366         // all done
367         delete mProgressDialog.data();
368         mProgressDialog.clear();
369         Q_EMIT messagesTransfered(OK);
370     }
371 }
372 
slotTransferCancelled()373 void KMCommand::slotTransferCancelled()
374 {
375     KMCommand::mCountJobs = 0;
376     mCountMsgs = 0;
377     mRetrievedMsgs.clear();
378     Q_EMIT messagesTransfered(Canceled);
379 }
380 
KMMailtoComposeCommand(const QUrl & url,const Akonadi::Item & msg)381 KMMailtoComposeCommand::KMMailtoComposeCommand(const QUrl &url, const Akonadi::Item &msg)
382     : mUrl(url)
383     , mMessage(msg)
384 {
385 }
386 
execute()387 KMCommand::Result KMMailtoComposeCommand::execute()
388 {
389     KMime::Message::Ptr msg(new KMime::Message);
390     uint id = 0;
391 
392     if (mMessage.isValid() && mMessage.parentCollection().isValid()) {
393         QSharedPointer<FolderSettings> fd = FolderSettings::forCollection(mMessage.parentCollection(), false);
394         id = fd->identity();
395     }
396 
397     MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
398     msg->contentType()->setCharset("utf-8");
399     msg->to()->fromUnicodeString(KEmailAddress::decodeMailtoUrl(mUrl), "utf-8");
400 
401     KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
402     win->setFocusToSubject();
403     win->show();
404     return OK;
405 }
406 
KMMailtoReplyCommand(QWidget * parent,const QUrl & url,const Akonadi::Item & msg,const QString & selection)407 KMMailtoReplyCommand::KMMailtoReplyCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg, const QString &selection)
408     : KMCommand(parent, msg)
409     , mUrl(url)
410     , mSelection(selection)
411 {
412     fetchScope().fetchFullPayload(true);
413     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
414 }
415 
execute()416 KMCommand::Result KMMailtoReplyCommand::execute()
417 {
418     Akonadi::Item item = retrievedMessage();
419     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
420     if (!msg) {
421         return Failed;
422     }
423     CreateReplyMessageJobSettings settings;
424     settings.item = item;
425     settings.msg = msg;
426     settings.selection = mSelection;
427     settings.url = mUrl;
428     settings.replyStrategy = MessageComposer::ReplyNone;
429     settings.replyAsHtml = mReplyAsHtml;
430 
431     auto job = new CreateReplyMessageJob;
432     job->setSettings(settings);
433     job->start();
434 
435     return OK;
436 }
437 
replyAsHtml() const438 bool KMMailtoReplyCommand::replyAsHtml() const
439 {
440     return mReplyAsHtml;
441 }
442 
setReplyAsHtml(bool replyAsHtml)443 void KMMailtoReplyCommand::setReplyAsHtml(bool replyAsHtml)
444 {
445     mReplyAsHtml = replyAsHtml;
446 }
447 
KMMailtoForwardCommand(QWidget * parent,const QUrl & url,const Akonadi::Item & msg)448 KMMailtoForwardCommand::KMMailtoForwardCommand(QWidget *parent, const QUrl &url, const Akonadi::Item &msg)
449     : KMCommand(parent, msg)
450     , mUrl(url)
451 {
452     fetchScope().fetchFullPayload(true);
453     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
454 }
455 
execute()456 KMCommand::Result KMMailtoForwardCommand::execute()
457 {
458     // TODO : consider factoring createForward into this method.
459     Akonadi::Item item = retrievedMessage();
460     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
461     if (!msg) {
462         return Failed;
463     }
464     CreateForwardMessageJobSettings settings;
465     settings.item = item;
466     settings.msg = msg;
467     settings.url = mUrl;
468 
469     auto job = new CreateForwardMessageJob;
470     job->setSettings(settings);
471     job->start();
472     return OK;
473 }
474 
KMAddBookmarksCommand(const QUrl & url,QWidget * parent)475 KMAddBookmarksCommand::KMAddBookmarksCommand(const QUrl &url, QWidget *parent)
476     : KMCommand(parent)
477     , mUrl(url)
478 {
479 }
480 
execute()481 KMCommand::Result KMAddBookmarksCommand::execute()
482 {
483     const QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/konqueror/bookmarks.xml");
484     QFileInfo fileInfo(filename);
485     QDir().mkpath(fileInfo.absolutePath());
486     KBookmarkManager *bookManager = KBookmarkManager::managerForFile(filename, QStringLiteral("konqueror"));
487     KBookmarkGroup group = bookManager->root();
488     group.addBookmark(mUrl.path(), QUrl(mUrl), QString());
489     if (bookManager->save()) {
490         bookManager->emitChanged(group);
491     }
492 
493     return OK;
494 }
495 
KMUrlSaveCommand(const QUrl & url,QWidget * parent)496 KMUrlSaveCommand::KMUrlSaveCommand(const QUrl &url, QWidget *parent)
497     : KMCommand(parent)
498     , mUrl(url)
499 {
500 }
501 
execute()502 KMCommand::Result KMUrlSaveCommand::execute()
503 {
504     if (mUrl.isEmpty()) {
505         return OK;
506     }
507     QString recentDirClass;
508     QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///OpenMessage")), recentDirClass);
509     startUrl.setPath(startUrl.path() + QLatin1Char('/') + mUrl.fileName());
510     const QUrl saveUrl = QFileDialog::getSaveFileUrl(parentWidget(), i18n("Save To File"), startUrl);
511     if (saveUrl.isEmpty()) {
512         return Canceled;
513     }
514 
515     if (!recentDirClass.isEmpty()) {
516         KRecentDirs::add(recentDirClass, saveUrl.path());
517     }
518 
519     KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite);
520     connect(job, &KIO::Job::result, this, &KMUrlSaveCommand::slotUrlSaveResult);
521     setEmitsCompletedItself(true);
522     return OK;
523 }
524 
slotUrlSaveResult(KJob * job)525 void KMUrlSaveCommand::slotUrlSaveResult(KJob *job)
526 {
527     if (job->error()) {
528         showJobError(job);
529         setResult(Failed);
530     } else {
531         setResult(OK);
532     }
533     Q_EMIT completed(this);
534 }
535 
KMEditMessageCommand(QWidget * parent,const KMime::Message::Ptr & msg)536 KMEditMessageCommand::KMEditMessageCommand(QWidget *parent, const KMime::Message::Ptr &msg)
537     : KMCommand(parent)
538     , mMessage(msg)
539 {
540 }
541 
execute()542 KMCommand::Result KMEditMessageCommand::execute()
543 {
544     if (!mMessage) {
545         return Failed;
546     }
547 
548     KMail::Composer *win = KMail::makeComposer();
549     bool lastEncrypt = false;
550     bool lastSign = false;
551     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage);
552     win->setMessage(mMessage, lastSign, lastEncrypt, false, true);
553     win->show();
554     win->setModified(true);
555     return OK;
556 }
557 
KMEditItemCommand(QWidget * parent,const Akonadi::Item & msg,bool deleteFromSource)558 KMEditItemCommand::KMEditItemCommand(QWidget *parent, const Akonadi::Item &msg, bool deleteFromSource)
559     : KMCommand(parent, msg)
560     , mDeleteFromSource(deleteFromSource)
561 {
562     fetchScope().fetchFullPayload(true);
563     fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
564     fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
565     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
566 }
567 
568 KMEditItemCommand::~KMEditItemCommand() = default;
569 
execute()570 KMCommand::Result KMEditItemCommand::execute()
571 {
572     Akonadi::Item item = retrievedMessage();
573     if (!item.isValid() || !item.parentCollection().isValid()) {
574         return Failed;
575     }
576     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
577     if (!msg) {
578         return Failed;
579     }
580 
581     if (mDeleteFromSource) {
582         setDeletesItself(true);
583         auto job = new Akonadi::ItemDeleteJob(item);
584         connect(job, &KIO::Job::result, this, &KMEditItemCommand::slotDeleteItem);
585     }
586     KMail::Composer *win = KMail::makeComposer();
587     bool lastEncrypt = false;
588     bool lastSign = false;
589     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
590     win->setMessage(msg, lastSign, lastEncrypt, false, true);
591 
592     win->setFolder(item.parentCollection());
593 
594     const MailTransport::TransportAttribute *transportAttribute = item.attribute<MailTransport::TransportAttribute>();
595     if (transportAttribute) {
596         win->setCurrentTransport(transportAttribute->transportId());
597     } else {
598         int transportId = -1;
599         if (auto hrd = msg->headerByType("X-KMail-Transport")) {
600             transportId = hrd->asUnicodeString().toInt();
601         }
602         if (transportId != -1) {
603             win->setCurrentTransport(transportId);
604         }
605     }
606 
607     const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute<MailTransport::SentBehaviourAttribute>();
608     if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) {
609         win->setFcc(QString::number(sentAttribute->moveToCollection().id()));
610     }
611     win->show();
612     if (mDeleteFromSource) {
613         win->setModified(true);
614     }
615 
616     return OK;
617 }
618 
slotDeleteItem(KJob * job)619 void KMEditItemCommand::slotDeleteItem(KJob *job)
620 {
621     if (job->error()) {
622         showJobError(job);
623         setResult(Failed);
624     } else {
625         setResult(OK);
626     }
627     Q_EMIT completed(this);
628     deleteLater();
629 }
630 
KMUseTemplateCommand(QWidget * parent,const Akonadi::Item & msg)631 KMUseTemplateCommand::KMUseTemplateCommand(QWidget *parent, const Akonadi::Item &msg)
632     : KMCommand(parent, msg)
633 {
634     fetchScope().fetchFullPayload(true);
635     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
636 }
637 
execute()638 KMCommand::Result KMUseTemplateCommand::execute()
639 {
640     Akonadi::Item item = retrievedMessage();
641     if (!item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates(item.parentCollection())) {
642         return Failed;
643     }
644     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
645     if (!msg) {
646         return Failed;
647     }
648 
649     KMime::Message::Ptr newMsg(new KMime::Message);
650     newMsg->setContent(msg->encodedContent());
651     newMsg->parse();
652     // these fields need to be regenerated for the new message
653     newMsg->removeHeader<KMime::Headers::Date>();
654     newMsg->removeHeader<KMime::Headers::MessageID>();
655 
656     KMail::Composer *win = KMail::makeComposer();
657 
658     win->setMessage(newMsg, false, false, false, true);
659     win->show();
660     return OK;
661 }
662 
KMSaveMsgCommand(QWidget * parent,const Akonadi::Item::List & msgList)663 KMSaveMsgCommand::KMSaveMsgCommand(QWidget *parent, const Akonadi::Item::List &msgList)
664     : KMCommand(parent, msgList)
665 {
666     if (msgList.empty()) {
667         return;
668     }
669 
670     fetchScope().fetchFullPayload(true); // ### unless we call the corresponding KMCommand ctor, this has no effect
671 }
672 
execute()673 KMCommand::Result KMSaveMsgCommand::execute()
674 {
675     if (!MessageViewer::Util::saveMessageInMbox(retrievedMsgs(), parentWidget())) {
676         return Failed;
677     }
678     return OK;
679 }
680 
681 //-----------------------------------------------------------------------------
682 
KMOpenMsgCommand(QWidget * parent,const QUrl & url,const QString & encoding,KMMainWidget * main)683 KMOpenMsgCommand::KMOpenMsgCommand(QWidget *parent, const QUrl &url, const QString &encoding, KMMainWidget *main)
684     : KMCommand(parent)
685     , mUrl(url)
686     , mEncoding(encoding)
687     , mMainWidget(main)
688 {
689     qCDebug(KMAIL_LOG) << "url :" << url;
690 }
691 
execute()692 KMCommand::Result KMOpenMsgCommand::execute()
693 {
694     if (mUrl.isEmpty()) {
695         mUrl = QFileDialog::getOpenFileUrl(parentWidget(), i18n("Open Message"), QUrl(), QStringLiteral("%1 (*.mbox *.eml)").arg(i18n("Message")));
696     }
697     if (mUrl.isEmpty()) {
698         return Canceled;
699     }
700 
701     if (mMainWidget) {
702         mMainWidget->addRecentFile(mUrl);
703     }
704 
705     setDeletesItself(true);
706     mJob = KIO::get(mUrl, KIO::NoReload, KIO::HideProgressInfo);
707     connect(mJob, &KIO::TransferJob::data, this, &KMOpenMsgCommand::slotDataArrived);
708     connect(mJob, &KJob::result, this, &KMOpenMsgCommand::slotResult);
709     setEmitsCompletedItself(true);
710     return OK;
711 }
712 
slotDataArrived(KIO::Job *,const QByteArray & data)713 void KMOpenMsgCommand::slotDataArrived(KIO::Job *, const QByteArray &data)
714 {
715     if (data.isEmpty()) {
716         return;
717     }
718 
719     mMsgString.append(QString::fromLatin1(data.data()));
720 }
721 
doesNotContainMessage()722 void KMOpenMsgCommand::doesNotContainMessage()
723 {
724     KMessageBox::sorry(parentWidget(), i18n("The file does not contain a message."));
725     setResult(Failed);
726     Q_EMIT completed(this);
727     // Emulate closing of a secondary window so that KMail exits in case it
728     // was started with the --view command line option. Otherwise an
729     // invisible KMail would keep running.
730     auto win = new SecondaryWindow();
731     win->close();
732     win->deleteLater();
733     deleteLater();
734 }
735 
slotResult(KJob * job)736 void KMOpenMsgCommand::slotResult(KJob *job)
737 {
738     if (job->error()) {
739         // handle errors
740         showJobError(job);
741         setResult(Failed);
742     } else {
743         if (mMsgString.isEmpty()) {
744             qCDebug(KMAIL_LOG) << " Message not found. There is a problem";
745             doesNotContainMessage();
746             return;
747         }
748         int startOfMessage = 0;
749         if (mMsgString.startsWith(QLatin1String("From "))) {
750             startOfMessage = mMsgString.indexOf(QLatin1Char('\n'));
751             if (startOfMessage == -1) {
752                 doesNotContainMessage();
753                 return;
754             }
755             startOfMessage += 1; // the message starts after the '\n'
756         }
757         QVector<KMime::Message::Ptr> listMessages;
758 
759         // check for multiple messages in the file
760         bool multipleMessages = true;
761         int endOfMessage = mMsgString.indexOf(QLatin1String("\nFrom "), startOfMessage);
762         while (endOfMessage != -1) {
763             auto msg = new KMime::Message;
764             msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage).toUtf8()));
765             msg->parse();
766             if (!msg->hasContent()) {
767                 delete msg;
768                 msg = nullptr;
769                 doesNotContainMessage();
770                 return;
771             }
772             KMime::Message::Ptr mMsg(msg);
773             listMessages << mMsg;
774             startOfMessage = endOfMessage + 1;
775             endOfMessage = mMsgString.indexOf(QLatin1String("\nFrom "), startOfMessage);
776         }
777         if (endOfMessage == -1) {
778             endOfMessage = mMsgString.length();
779             multipleMessages = false;
780             auto msg = new KMime::Message;
781             msg->setContent(KMime::CRLFtoLF(mMsgString.mid(startOfMessage, endOfMessage - startOfMessage).toUtf8()));
782             msg->parse();
783             if (!msg->hasContent()) {
784                 delete msg;
785                 msg = nullptr;
786                 doesNotContainMessage();
787                 return;
788             }
789             KMime::Message::Ptr mMsg(msg);
790             listMessages << mMsg;
791         }
792         auto win = new KMReaderMainWin();
793         win->showMessage(mEncoding, listMessages);
794         win->show();
795         if (multipleMessages) {
796             KMessageBox::information(win,
797                                      i18n("The file contains multiple messages. "
798                                           "Only the first message is shown."));
799         }
800         setResult(OK);
801     }
802     Q_EMIT completed(this);
803     deleteLater();
804 }
805 
806 //-----------------------------------------------------------------------------
KMReplyCommand(QWidget * parent,const Akonadi::Item & msg,MessageComposer::ReplyStrategy replyStrategy,const QString & selection,bool noquote,const QString & templateName)807 KMReplyCommand::KMReplyCommand(QWidget *parent,
808                                const Akonadi::Item &msg,
809                                MessageComposer::ReplyStrategy replyStrategy,
810                                const QString &selection,
811                                bool noquote,
812                                const QString &templateName)
813     : KMCommand(parent, msg)
814     , mSelection(selection)
815     , mTemplate(templateName)
816     , m_replyStrategy(replyStrategy)
817     , mNoQuote(noquote)
818 {
819     fetchScope().fetchFullPayload(true);
820     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
821 }
822 
execute()823 KMCommand::Result KMReplyCommand::execute()
824 {
825     KCursorSaver saver(Qt::WaitCursor);
826     Akonadi::Item item = retrievedMessage();
827     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
828     if (!msg) {
829         return Failed;
830     }
831 
832     CreateReplyMessageJobSettings settings;
833     settings.item = item;
834     settings.msg = msg;
835     settings.selection = mSelection;
836     settings.replyStrategy = m_replyStrategy;
837     settings.templateStr = mTemplate;
838     settings.noQuote = mNoQuote;
839     settings.replyAsHtml = mReplyAsHtml;
840     // qDebug() << " settings " << mReplyAsHtml;
841 
842     auto job = new CreateReplyMessageJob;
843     job->setSettings(settings);
844     job->start();
845 
846     return OK;
847 }
848 
replyAsHtml() const849 bool KMReplyCommand::replyAsHtml() const
850 {
851     return mReplyAsHtml;
852 }
853 
setReplyAsHtml(bool replyAsHtml)854 void KMReplyCommand::setReplyAsHtml(bool replyAsHtml)
855 {
856     mReplyAsHtml = replyAsHtml;
857 }
858 
KMForwardCommand(QWidget * parent,const Akonadi::Item::List & msgList,uint identity,const QString & templateName,const QString & selection)859 KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, const QString &templateName, const QString &selection)
860     : KMCommand(parent, msgList)
861     , mIdentity(identity)
862     , mTemplate(templateName)
863     , mSelection(selection)
864 {
865     fetchScope().fetchFullPayload(true);
866     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
867 }
868 
KMForwardCommand(QWidget * parent,const Akonadi::Item & msg,uint identity,const QString & templateName,const QString & selection)869 KMForwardCommand::KMForwardCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, const QString &templateName, const QString &selection)
870     : KMCommand(parent, msg)
871     , mIdentity(identity)
872     , mTemplate(templateName)
873     , mSelection(selection)
874 {
875     fetchScope().fetchFullPayload(true);
876     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
877 }
878 
createComposer(const Akonadi::Item & item)879 KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item &item)
880 {
881     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
882     if (!msg) {
883         return Failed;
884     }
885     KCursorSaver saver(Qt::WaitCursor);
886 
887     CreateForwardMessageJobSettings settings;
888     settings.item = item;
889     settings.msg = msg;
890     settings.identity = mIdentity;
891     settings.templateStr = mTemplate;
892     settings.selection = mSelection;
893 
894     auto job = new CreateForwardMessageJob;
895     job->setSettings(settings);
896     job->start();
897     return OK;
898 }
899 
execute()900 KMCommand::Result KMForwardCommand::execute()
901 {
902     Akonadi::Item::List msgList = retrievedMsgs();
903 
904     if (msgList.count() >= 2) {
905         // ask if they want a mime digest forward
906 
907         int answer = KMessageBox::questionYesNoCancel(parentWidget(),
908                                                       i18n("Do you want to forward the selected messages as "
909                                                            "attachments in one message (as a MIME digest) or as "
910                                                            "individual messages?"),
911                                                       QString(),
912                                                       KGuiItem(i18n("Send As Digest")),
913                                                       KGuiItem(i18n("Send Individually")));
914 
915         if (answer == KMessageBox::Yes) {
916             Akonadi::Item firstItem(msgList.first());
917             MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message),
918                                      firstItem.id(),
919                                      CommonKernel->collectionFromId(firstItem.parentCollection().id()));
920             factory.setIdentityManager(KMKernel::self()->identityManager());
921             factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
922 
923             QPair<KMime::Message::Ptr, KMime::Content *> fwdMsg = factory.createForwardDigestMIME(msgList);
924             KMail::Composer *win = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
925             win->addAttach(fwdMsg.second);
926             win->show();
927             delete fwdMsg.second;
928             return OK;
929         } else if (answer == KMessageBox::No) { // NO MIME DIGEST, Multiple forward
930             Akonadi::Item::List::const_iterator it;
931             Akonadi::Item::List::const_iterator end(msgList.constEnd());
932 
933             for (it = msgList.constBegin(); it != end; ++it) {
934                 if (createComposer(*it) == Failed) {
935                     return Failed;
936                 }
937             }
938             return OK;
939         } else {
940             // user cancelled
941             return OK;
942         }
943     }
944 
945     // forward a single message at most.
946     Akonadi::Item item = msgList.first();
947     if (createComposer(item) == Failed) {
948         return Failed;
949     }
950     return OK;
951 }
952 
KMForwardAttachedCommand(QWidget * parent,const Akonadi::Item::List & msgList,uint identity,KMail::Composer * win)953 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item::List &msgList, uint identity, KMail::Composer *win)
954     : KMCommand(parent, msgList)
955     , mIdentity(identity)
956     , mWin(QPointer<KMail::Composer>(win))
957 {
958     fetchScope().fetchFullPayload(true);
959     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
960 }
961 
KMForwardAttachedCommand(QWidget * parent,const Akonadi::Item & msg,uint identity,KMail::Composer * win)962 KMForwardAttachedCommand::KMForwardAttachedCommand(QWidget *parent, const Akonadi::Item &msg, uint identity, KMail::Composer *win)
963     : KMCommand(parent, msg)
964     , mIdentity(identity)
965     , mWin(QPointer<KMail::Composer>(win))
966 {
967     fetchScope().fetchFullPayload(true);
968     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
969 }
970 
execute()971 KMCommand::Result KMForwardAttachedCommand::execute()
972 {
973     Akonadi::Item::List msgList = retrievedMsgs();
974     Akonadi::Item firstItem(msgList.first());
975     MessageFactoryNG factory(KMime::Message::Ptr(new KMime::Message), firstItem.id(), CommonKernel->collectionFromId(firstItem.parentCollection().id()));
976     factory.setIdentityManager(KMKernel::self()->identityManager());
977     factory.setFolderIdentity(MailCommon::Util::folderIdentity(firstItem));
978 
979     QPair<KMime::Message::Ptr, QVector<KMime::Content *>> fwdMsg = factory.createAttachedForward(msgList);
980     if (!mWin) {
981         mWin = KMail::makeComposer(fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity);
982     }
983     for (KMime::Content *attach : std::as_const(fwdMsg.second)) {
984         mWin->addAttach(attach);
985         delete attach;
986     }
987     mWin->show();
988     return OK;
989 }
990 
KMRedirectCommand(QWidget * parent,const Akonadi::Item::List & msgList)991 KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item::List &msgList)
992     : KMCommand(parent, msgList)
993 {
994     fetchScope().fetchFullPayload(true);
995     fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
996     fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
997 
998     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
999 }
1000 
KMRedirectCommand(QWidget * parent,const Akonadi::Item & msg)1001 KMRedirectCommand::KMRedirectCommand(QWidget *parent, const Akonadi::Item &msg)
1002     : KMCommand(parent, msg)
1003 {
1004     fetchScope().fetchFullPayload(true);
1005     fetchScope().fetchAttribute<MailTransport::SentBehaviourAttribute>();
1006     fetchScope().fetchAttribute<MailTransport::TransportAttribute>();
1007 
1008     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1009 }
1010 
execute()1011 KMCommand::Result KMRedirectCommand::execute()
1012 {
1013     const MailCommon::RedirectDialog::SendMode sendMode =
1014         MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater;
1015 
1016     QScopedPointer<MailCommon::RedirectDialog> dlg(new MailCommon::RedirectDialog(sendMode, parentWidget()));
1017     dlg->setObjectName(QStringLiteral("redirect"));
1018     if (dlg->exec() == QDialog::Rejected || !dlg) {
1019         return Failed;
1020     }
1021     if (!TransportManager::self()->showTransportCreationDialog(parentWidget(), TransportManager::IfNoTransportExists)) {
1022         return Failed;
1023     }
1024 
1025     // TODO use sendlateragent here too.
1026     const MessageComposer::MessageSender::SendMethod method =
1027         (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater;
1028 
1029     const int identity = dlg->identity();
1030     int transportId = dlg->transportId();
1031     const QString to = dlg->to();
1032     const QString cc = dlg->cc();
1033     const QString bcc = dlg->bcc();
1034     const Akonadi::Item::List lstItems = retrievedMsgs();
1035     for (const Akonadi::Item &item : lstItems) {
1036         const KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1037         if (!msg) {
1038             return Failed;
1039         }
1040         MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id()));
1041         factory.setIdentityManager(KMKernel::self()->identityManager());
1042         factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1043 
1044         if (transportId == -1) {
1045             const auto *transportAttribute = item.attribute<MailTransport::TransportAttribute>();
1046             if (transportAttribute) {
1047                 transportId = transportAttribute->transportId();
1048                 const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
1049                 if (!transport) {
1050                     transportId = -1;
1051                 }
1052             }
1053         }
1054 
1055         const auto *sentAttribute = item.attribute<MailTransport::SentBehaviourAttribute>();
1056         QString fcc;
1057         if (sentAttribute && (sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection)) {
1058             fcc = QString::number(sentAttribute->moveToCollection().id());
1059         }
1060 
1061         const KMime::Message::Ptr newMsg = factory.createRedirect(to, cc, bcc, transportId, fcc, identity);
1062         if (!newMsg) {
1063             return Failed;
1064         }
1065 
1066         MessageStatus status;
1067         status.setStatusFromFlags(item.flags());
1068         if (!status.isRead()) {
1069             FilterAction::sendMDN(item, KMime::MDN::Dispatched);
1070         }
1071 
1072         if (!kmkernel->msgSender()->send(newMsg, method)) {
1073             qCDebug(KMAIL_LOG) << "KMRedirectCommand: could not redirect message (sending failed)";
1074             return Failed; // error: couldn't send
1075         }
1076     }
1077 
1078     return OK;
1079 }
1080 
KMPrintCommand(QWidget * parent,const KMPrintCommandInfo & commandInfo)1081 KMPrintCommand::KMPrintCommand(QWidget *parent, const KMPrintCommandInfo &commandInfo)
1082     : KMCommand(parent, commandInfo.mMsg)
1083     , mPrintCommandInfo(commandInfo)
1084 {
1085     fetchScope().fetchFullPayload(true);
1086     if (MessageCore::MessageCoreSettings::useDefaultFonts()) {
1087         mPrintCommandInfo.mOverrideFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1088     } else {
1089         mPrintCommandInfo.mOverrideFont = MessageViewer::MessageViewerSettings::self()->printFont();
1090     }
1091 }
1092 
execute()1093 KMCommand::Result KMPrintCommand::execute()
1094 {
1095     auto printerWin = new KMReaderWin(nullptr, parentWidget(), nullptr);
1096     printerWin->setPrinting(true);
1097     printerWin->readConfig();
1098     printerWin->setPrintElementBackground(MessageViewer::MessageViewerSettings::self()->printBackgroundColorImages());
1099     if (mPrintCommandInfo.mHeaderStylePlugin) {
1100         printerWin->viewer()->setPluginName(mPrintCommandInfo.mHeaderStylePlugin->name());
1101     }
1102     printerWin->setDisplayFormatMessageOverwrite(mPrintCommandInfo.mFormat);
1103     printerWin->setHtmlLoadExtOverride(mPrintCommandInfo.mHtmlLoadExtOverride);
1104     printerWin->setUseFixedFont(mPrintCommandInfo.mUseFixedFont);
1105     printerWin->setOverrideEncoding(mPrintCommandInfo.mEncoding);
1106     printerWin->cssHelper()->setPrintFont(mPrintCommandInfo.mOverrideFont);
1107     printerWin->setDecryptMessageOverwrite(true);
1108     if (mPrintCommandInfo.mAttachmentStrategy) {
1109         printerWin->setAttachmentStrategy(mPrintCommandInfo.mAttachmentStrategy);
1110     }
1111     printerWin->viewer()->setShowSignatureDetails(mPrintCommandInfo.mShowSignatureDetails);
1112     printerWin->viewer()->setShowEncryptionDetails(mPrintCommandInfo.mShowEncryptionDetails);
1113     if (mPrintCommandInfo.mPrintPreview) {
1114         printerWin->viewer()->printPreviewMessage(retrievedMessage());
1115     } else {
1116         printerWin->viewer()->printMessage(retrievedMessage());
1117     }
1118     return OK;
1119 }
1120 
KMSetStatusCommand(const MessageStatus & status,const Akonadi::Item::List & items,bool invert)1121 KMSetStatusCommand::KMSetStatusCommand(const MessageStatus &status, const Akonadi::Item::List &items, bool invert)
1122     : KMCommand(nullptr, items)
1123     , mStatus(status)
1124     , mInvertMark(invert)
1125 {
1126     setDeletesItself(true);
1127 }
1128 
execute()1129 KMCommand::Result KMSetStatusCommand::execute()
1130 {
1131     bool parentStatus = false;
1132     // Toggle actions on threads toggle the whole thread
1133     // depending on the state of the parent.
1134     if (mInvertMark) {
1135         const Akonadi::Item first = retrievedMsgs().first();
1136         MessageStatus pStatus;
1137         pStatus.setStatusFromFlags(first.flags());
1138         if (pStatus & mStatus) {
1139             parentStatus = true;
1140         } else {
1141             parentStatus = false;
1142         }
1143     }
1144 
1145     Akonadi::Item::List itemsToModify;
1146     const Akonadi::Item::List lstItems = retrievedMsgs();
1147     for (const Akonadi::Item &it : lstItems) {
1148         if (mInvertMark) {
1149             // qCDebug(KMAIL_LOG)<<" item ::"<<tmpItem;
1150             if (it.isValid()) {
1151                 bool myStatus;
1152                 MessageStatus itemStatus;
1153                 itemStatus.setStatusFromFlags(it.flags());
1154                 if (itemStatus & mStatus) {
1155                     myStatus = true;
1156                 } else {
1157                     myStatus = false;
1158                 }
1159                 if (myStatus != parentStatus) {
1160                     continue;
1161                 }
1162             }
1163         }
1164         Akonadi::Item item(it);
1165         const Akonadi::Item::Flag flag = *(mStatus.statusFlags().constBegin());
1166         if (mInvertMark) {
1167             if (item.hasFlag(flag)) {
1168                 item.clearFlag(flag);
1169                 itemsToModify.push_back(item);
1170             } else {
1171                 item.setFlag(flag);
1172                 itemsToModify.push_back(item);
1173             }
1174         } else {
1175             if (!item.hasFlag(flag)) {
1176                 item.setFlag(flag);
1177                 itemsToModify.push_back(item);
1178             }
1179         }
1180     }
1181 
1182     if (itemsToModify.isEmpty()) {
1183         slotModifyItemDone(nullptr); // pretend we did something
1184     } else {
1185         auto modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1186         modifyJob->disableRevisionCheck();
1187         modifyJob->setIgnorePayload(true);
1188         connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetStatusCommand::slotModifyItemDone);
1189     }
1190     return OK;
1191 }
1192 
slotModifyItemDone(KJob * job)1193 void KMSetStatusCommand::slotModifyItemDone(KJob *job)
1194 {
1195     if (job && job->error()) {
1196         qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1197     }
1198     deleteLater();
1199 }
1200 
KMSetTagCommand(const Akonadi::Tag::List & tags,const Akonadi::Item::List & item,SetTagMode mode)1201 KMSetTagCommand::KMSetTagCommand(const Akonadi::Tag::List &tags, const Akonadi::Item::List &item, SetTagMode mode)
1202     : mTags(tags)
1203     , mItem(item)
1204     , mMode(mode)
1205 {
1206     setDeletesItself(true);
1207 }
1208 
execute()1209 KMCommand::Result KMSetTagCommand::execute()
1210 {
1211     for (const Akonadi::Tag &tag : std::as_const(mTags)) {
1212         if (!tag.isValid()) {
1213             auto createJob = new Akonadi::TagCreateJob(tag, this);
1214             connect(createJob, &Akonadi::TagCreateJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1215         } else {
1216             mCreatedTags << tag;
1217         }
1218     }
1219 
1220     if (mCreatedTags.size() == mTags.size()) {
1221         setTags();
1222     } else {
1223         deleteLater();
1224     }
1225 
1226     return OK;
1227 }
1228 
setTags()1229 void KMSetTagCommand::setTags()
1230 {
1231     Akonadi::Item::List itemsToModify;
1232     itemsToModify.reserve(mItem.count());
1233     for (const Akonadi::Item &i : std::as_const(mItem)) {
1234         Akonadi::Item item(i);
1235         if (mMode == CleanExistingAndAddNew) {
1236             // WorkAround. ClearTags doesn't work.
1237             const Akonadi::Tag::List lstTags = item.tags();
1238             for (const Akonadi::Tag &tag : lstTags) {
1239                 item.clearTag(tag);
1240             }
1241             // item.clearTags();
1242         }
1243 
1244         if (mMode == KMSetTagCommand::Toggle) {
1245             for (const Akonadi::Tag &tag : std::as_const(mCreatedTags)) {
1246                 if (item.hasTag(tag)) {
1247                     item.clearTag(tag);
1248                 } else {
1249                     item.setTag(tag);
1250                 }
1251             }
1252         } else {
1253             if (!mCreatedTags.isEmpty()) {
1254                 item.setTags(mCreatedTags);
1255             }
1256         }
1257         itemsToModify << item;
1258     }
1259     auto modifyJob = new Akonadi::ItemModifyJob(itemsToModify, this);
1260     modifyJob->disableRevisionCheck();
1261     modifyJob->setIgnorePayload(true);
1262     connect(modifyJob, &Akonadi::ItemModifyJob::result, this, &KMSetTagCommand::slotModifyItemDone);
1263 
1264     if (!mCreatedTags.isEmpty()) {
1265         KConfigGroup tag(KMKernel::self()->config(), "MessageListView");
1266         const QString oldTagList = tag.readEntry("TagSelected");
1267         QStringList lst = oldTagList.split(QLatin1Char(','));
1268         for (const Akonadi::Tag &createdTag : std::as_const(mCreatedTags)) {
1269             const QString url = createdTag.url().url();
1270             if (!lst.contains(url)) {
1271                 lst.append(url);
1272             }
1273         }
1274         tag.writeEntry("TagSelected", lst);
1275         KMKernel::self()->updatePaneTagComboBox();
1276     }
1277 }
1278 
slotModifyItemDone(KJob * job)1279 void KMSetTagCommand::slotModifyItemDone(KJob *job)
1280 {
1281     if (job && job->error()) {
1282         qCWarning(KMAIL_LOG) << " Error trying to set item status:" << job->errorText();
1283     }
1284     deleteLater();
1285 }
1286 
KMFilterActionCommand(QWidget * parent,const QVector<qlonglong> & msgListId,const QString & filterId)1287 KMFilterActionCommand::KMFilterActionCommand(QWidget *parent, const QVector<qlonglong> &msgListId, const QString &filterId)
1288     : KMCommand(parent)
1289     , mMsgListId(msgListId)
1290     , mFilterId(filterId)
1291 {
1292 }
1293 
execute()1294 KMCommand::Result KMFilterActionCommand::execute()
1295 {
1296     KCursorSaver saver(Qt::WaitCursor);
1297     int msgCount = 0;
1298     const int msgCountToFilter = mMsgListId.count();
1299     ProgressItem *progressItem = ProgressManager::createProgressItem(QLatin1String("filter") + ProgressManager::getUniqueID(),
1300                                                                      i18n("Filtering messages"),
1301                                                                      QString(),
1302                                                                      true,
1303                                                                      KPIM::ProgressItem::Unknown);
1304     progressItem->setTotalItems(msgCountToFilter);
1305 
1306     for (const qlonglong &id : std::as_const(mMsgListId)) {
1307         int diff = msgCountToFilter - ++msgCount;
1308         if (diff < 10 || !(msgCount % 10) || msgCount <= 10) {
1309             progressItem->updateProgress();
1310             const QString statusMsg = i18n("Filtering message %1 of %2", msgCount, msgCountToFilter);
1311             PimCommon::BroadcastStatus::instance()->setStatusMsg(statusMsg);
1312             qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 50);
1313         }
1314 
1315         MailCommon::FilterManager::instance()->filter(Akonadi::Item(id), mFilterId, QString());
1316         progressItem->incCompletedItems();
1317     }
1318 
1319     progressItem->setComplete();
1320     progressItem = nullptr;
1321     return OK;
1322 }
1323 
KMMetaFilterActionCommand(const QString & filterId,KMMainWidget * main)1324 KMMetaFilterActionCommand::KMMetaFilterActionCommand(const QString &filterId, KMMainWidget *main)
1325     : QObject(main)
1326     , mFilterId(filterId)
1327     , mMainWidget(main)
1328 {
1329 }
1330 
start()1331 void KMMetaFilterActionCommand::start()
1332 {
1333     KMCommand *filterCommand = new KMFilterActionCommand(mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId(), mFilterId);
1334     filterCommand->start();
1335 }
1336 
KMMailingListFilterCommand(QWidget * parent,const Akonadi::Item & msg)1337 KMMailingListFilterCommand::KMMailingListFilterCommand(QWidget *parent, const Akonadi::Item &msg)
1338     : KMCommand(parent, msg)
1339 {
1340 }
1341 
execute()1342 KMCommand::Result KMMailingListFilterCommand::execute()
1343 {
1344     QByteArray name;
1345     QString value;
1346     Akonadi::Item item = retrievedMessage();
1347     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1348     if (!msg) {
1349         return Failed;
1350     }
1351     if (!MailingList::name(msg, name, value).isEmpty()) {
1352         FilterIf->openFilterDialog(false);
1353         FilterIf->createFilter(name, value);
1354         return OK;
1355     } else {
1356         return Failed;
1357     }
1358 }
1359 
KMCopyCommand(const Akonadi::Collection & destFolder,const Akonadi::Item::List & msgList)1360 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList)
1361     : KMCommand(nullptr, msgList)
1362     , mDestFolder(destFolder)
1363 {
1364 }
1365 
KMCopyCommand(const Akonadi::Collection & destFolder,const Akonadi::Item & msg)1366 KMCopyCommand::KMCopyCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg)
1367     : KMCommand(nullptr, msg)
1368     , mDestFolder(destFolder)
1369 {
1370 }
1371 
execute()1372 KMCommand::Result KMCopyCommand::execute()
1373 {
1374     setDeletesItself(true);
1375 
1376     Akonadi::Item::List listItem = retrievedMsgs();
1377     auto job = new Akonadi::ItemCopyJob(listItem, Akonadi::Collection(mDestFolder.id()), this);
1378     connect(job, &KIO::Job::result, this, &KMCopyCommand::slotCopyResult);
1379 
1380     return OK;
1381 }
1382 
slotCopyResult(KJob * job)1383 void KMCopyCommand::slotCopyResult(KJob *job)
1384 {
1385     if (job->error()) {
1386         // handle errors
1387         showJobError(job);
1388         setResult(Failed);
1389     }
1390 
1391     qobject_cast<Akonadi::ItemCopyJob *>(job);
1392 
1393     Q_EMIT completed(this);
1394     deleteLater();
1395 }
1396 
KMCopyDecryptedCommand(const Akonadi::Collection & destFolder,const Akonadi::Item::List & msgList)1397 KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList)
1398     : KMCommand(nullptr, msgList)
1399     , mDestFolder(destFolder)
1400 {
1401     fetchScope().fetchAllAttributes();
1402     fetchScope().fetchFullPayload();
1403 }
1404 
KMCopyDecryptedCommand(const Akonadi::Collection & destFolder,const Akonadi::Item & msg)1405 KMCopyDecryptedCommand::KMCopyDecryptedCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg)
1406     : KMCopyDecryptedCommand(destFolder, Akonadi::Item::List{msg})
1407 {
1408 }
1409 
execute()1410 KMCommand::Result KMCopyDecryptedCommand::execute()
1411 {
1412     setDeletesItself(true);
1413 
1414     const auto items = retrievedMsgs();
1415     for (const auto &item : items) {
1416         // Decrypt
1417         if (!item.hasPayload<KMime::Message::Ptr>()) {
1418             continue;
1419         }
1420         const auto msg = item.payload<KMime::Message::Ptr>();
1421         bool wasEncrypted;
1422         auto decMsg = MailCommon::CryptoUtils::decryptMessage(msg, wasEncrypted);
1423         if (!wasEncrypted) {
1424             decMsg = msg;
1425         }
1426 
1427         Akonadi::Item decItem;
1428         decItem.setMimeType(KMime::Message::mimeType());
1429         decItem.setPayload(decMsg);
1430 
1431         auto job = new Akonadi::ItemCreateJob(decItem, mDestFolder, this);
1432         connect(job, &Akonadi::Job::result, this, &KMCopyDecryptedCommand::slotAppendResult);
1433         mPendingJobs << job;
1434     }
1435 
1436     if (mPendingJobs.isEmpty()) {
1437         Q_EMIT completed(this);
1438         deleteLater();
1439     }
1440 
1441     return KMCommand::OK;
1442 }
1443 
slotAppendResult(KJob * job)1444 void KMCopyDecryptedCommand::slotAppendResult(KJob *job)
1445 {
1446     mPendingJobs.removeOne(job);
1447     if (mPendingJobs.isEmpty()) {
1448         Q_EMIT completed(this);
1449         deleteLater();
1450     }
1451 }
1452 
KMMoveCommand(const Akonadi::Collection & destFolder,const Akonadi::Item::List & msgList,MessageList::Core::MessageItemSetReference ref)1453 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1454     : KMCommand(nullptr, msgList)
1455     , mDestFolder(destFolder)
1456     , mRef(ref)
1457 {
1458     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1459 }
1460 
KMMoveCommand(const Akonadi::Collection & destFolder,const Akonadi::Item & msg,MessageList::Core::MessageItemSetReference ref)1461 KMMoveCommand::KMMoveCommand(const Akonadi::Collection &destFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1462     : KMCommand(nullptr, msg)
1463     , mDestFolder(destFolder)
1464     , mRef(ref)
1465 {
1466     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1467 }
1468 
slotMoveResult(KJob * job)1469 void KMMoveCommand::slotMoveResult(KJob *job)
1470 {
1471     if (job->error()) {
1472         // handle errors
1473         showJobError(job);
1474         completeMove(Failed);
1475     } else {
1476         completeMove(OK);
1477     }
1478 }
1479 
execute()1480 KMCommand::Result KMMoveCommand::execute()
1481 {
1482     KCursorSaver saver(Qt::WaitCursor);
1483     setEmitsCompletedItself(true);
1484     setDeletesItself(true);
1485     Akonadi::Item::List retrievedList = retrievedMsgs();
1486     if (!retrievedList.isEmpty()) {
1487         if (mDestFolder.isValid()) {
1488             auto job = new Akonadi::ItemMoveJob(retrievedList, mDestFolder, this);
1489             connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1490 
1491             // group by source folder for undo
1492             std::sort(retrievedList.begin(), retrievedList.end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) {
1493                 return lhs.storageCollectionId() < rhs.storageCollectionId();
1494             });
1495             Akonadi::Collection parent;
1496             int undoId = -1;
1497             for (const Akonadi::Item &item : std::as_const(retrievedList)) {
1498                 if (item.storageCollectionId() <= 0) {
1499                     continue;
1500                 }
1501                 if (parent.id() != item.storageCollectionId()) {
1502                     parent = Akonadi::Collection(item.storageCollectionId());
1503                     undoId = kmkernel->undoStack()->newUndoAction(parent, mDestFolder);
1504                 }
1505                 kmkernel->undoStack()->addMsgToAction(undoId, item);
1506             }
1507         } else {
1508             auto job = new Akonadi::ItemDeleteJob(retrievedList, this);
1509             connect(job, &KIO::Job::result, this, &KMMoveCommand::slotMoveResult);
1510         }
1511     } else {
1512         deleteLater();
1513         return Failed;
1514     }
1515     // TODO set SSL state according to source and destfolder connection?
1516     Q_ASSERT(!mProgressItem);
1517     mProgressItem = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(),
1518                                                         mDestFolder.isValid() ? i18n("Moving messages") : i18n("Deleting messages"),
1519                                                         QString(),
1520                                                         true,
1521                                                         KPIM::ProgressItem::Unknown);
1522     mProgressItem->setUsesBusyIndicator(true);
1523     connect(mProgressItem, &ProgressItem::progressItemCanceled, this, &KMMoveCommand::slotMoveCanceled);
1524     return OK;
1525 }
1526 
completeMove(Result result)1527 void KMMoveCommand::completeMove(Result result)
1528 {
1529     if (mProgressItem) {
1530         mProgressItem->setComplete();
1531         mProgressItem = nullptr;
1532     }
1533     setResult(result);
1534     Q_EMIT moveDone(this);
1535     Q_EMIT completed(this);
1536     deleteLater();
1537 }
1538 
slotMoveCanceled()1539 void KMMoveCommand::slotMoveCanceled()
1540 {
1541     completeMove(Canceled);
1542 }
1543 
KMTrashMsgCommand(const Akonadi::Collection & srcFolder,const Akonadi::Item::List & msgList,MessageList::Core::MessageItemSetReference ref)1544 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item::List &msgList, MessageList::Core::MessageItemSetReference ref)
1545     : KMCommand()
1546     , mRef(ref)
1547 {
1548     // When trashing items from a virtual collection, they may each have a different
1549     // trash folder, so we need to handle it here carefully
1550     if (srcFolder.isVirtual()) {
1551         QHash<qint64, Akonadi::Collection> cache;
1552         for (const auto &msg : msgList) {
1553             auto cacheIt = cache.find(msg.storageCollectionId());
1554             if (cacheIt == cache.end()) {
1555                 cacheIt = cache.insert(msg.storageCollectionId(), findTrashFolder(CommonKernel->collectionFromId(msg.storageCollectionId())));
1556             }
1557             auto trashIt = mTrashFolders.find(*cacheIt);
1558             if (trashIt == mTrashFolders.end()) {
1559                 trashIt = mTrashFolders.insert(*cacheIt, {});
1560             }
1561             trashIt->push_back(msg);
1562         }
1563     } else {
1564         mTrashFolders.insert(findTrashFolder(srcFolder), msgList);
1565     }
1566 }
1567 
operation() const1568 KMTrashMsgCommand::TrashOperation KMTrashMsgCommand::operation() const
1569 {
1570     if (!mPendingMoves.isEmpty() && !mPendingDeletes.isEmpty()) {
1571         return Both;
1572     } else if (!mPendingMoves.isEmpty()) {
1573         return MoveToTrash;
1574     } else if (!mPendingDeletes.isEmpty()) {
1575         return Delete;
1576     } else {
1577         if (mTrashFolders.size() == 1) {
1578             if (mTrashFolders.begin().key().isValid()) {
1579                 return MoveToTrash;
1580             } else {
1581                 return Delete;
1582             }
1583         } else {
1584             return Unknown;
1585         }
1586     }
1587 }
1588 
KMTrashMsgCommand(const Akonadi::Collection & srcFolder,const Akonadi::Item & msg,MessageList::Core::MessageItemSetReference ref)1589 KMTrashMsgCommand::KMTrashMsgCommand(const Akonadi::Collection &srcFolder, const Akonadi::Item &msg, MessageList::Core::MessageItemSetReference ref)
1590     : KMTrashMsgCommand(srcFolder, Akonadi::Item::List{msg}, ref)
1591 {
1592 }
1593 
findTrashFolder(const Akonadi::Collection & folder)1594 Akonadi::Collection KMTrashMsgCommand::findTrashFolder(const Akonadi::Collection &folder)
1595 {
1596     Akonadi::Collection col = CommonKernel->trashCollectionFromResource(folder);
1597     if (!col.isValid()) {
1598         col = CommonKernel->trashCollectionFolder();
1599     }
1600     if (folder != col) {
1601         return col;
1602     }
1603     return Akonadi::Collection();
1604 }
1605 
execute()1606 KMCommand::Result KMTrashMsgCommand::execute()
1607 {
1608     KCursorSaver saver(Qt::WaitCursor);
1609     setEmitsCompletedItself(true);
1610     setDeletesItself(true);
1611     for (auto trashIt = mTrashFolders.begin(), end = mTrashFolders.end(); trashIt != end; ++trashIt) {
1612         const auto trash = trashIt.key();
1613         if (trash.isValid()) {
1614             auto job = new Akonadi::ItemMoveJob(*trashIt, trash, this);
1615             connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotMoveResult);
1616             mPendingMoves.push_back(job);
1617 
1618             // group by source folder for undo
1619             std::sort(trashIt->begin(), trashIt->end(), [](const Akonadi::Item &lhs, const Akonadi::Item &rhs) {
1620                 return lhs.storageCollectionId() < rhs.storageCollectionId();
1621             });
1622             Akonadi::Collection parent;
1623             int undoId = -1;
1624             for (const Akonadi::Item &item : std::as_const(*trashIt)) {
1625                 if (item.storageCollectionId() <= 0) {
1626                     continue;
1627                 }
1628                 if (parent.id() != item.storageCollectionId()) {
1629                     parent = Akonadi::Collection(item.storageCollectionId());
1630                     undoId = kmkernel->undoStack()->newUndoAction(parent, trash);
1631                 }
1632                 kmkernel->undoStack()->addMsgToAction(undoId, item);
1633             }
1634         } else {
1635             auto job = new Akonadi::ItemDeleteJob(*trashIt, this);
1636             connect(job, &KIO::Job::result, this, &KMTrashMsgCommand::slotDeleteResult);
1637             mPendingDeletes.push_back(job);
1638         }
1639     }
1640 
1641     if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) {
1642         deleteLater();
1643         return Failed;
1644     }
1645 
1646     // TODO set SSL state according to source and destfolder connection?
1647     if (!mPendingMoves.isEmpty()) {
1648         Q_ASSERT(!mMoveProgress);
1649         mMoveProgress = ProgressManager::createProgressItem(QLatin1String("move") + ProgressManager::getUniqueID(),
1650                                                             i18n("Moving messages"),
1651                                                             QString(),
1652                                                             true,
1653                                                             KPIM::ProgressItem::Unknown);
1654         mMoveProgress->setUsesBusyIndicator(true);
1655         connect(mMoveProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled);
1656     }
1657     if (!mPendingDeletes.isEmpty()) {
1658         Q_ASSERT(!mDeleteProgress);
1659         mDeleteProgress = ProgressManager::createProgressItem(QLatin1String("delete") + ProgressManager::getUniqueID(),
1660                                                               i18n("Deleting messages"),
1661                                                               QString(),
1662                                                               true,
1663                                                               KPIM::ProgressItem::Unknown);
1664         mDeleteProgress->setUsesBusyIndicator(true);
1665         connect(mDeleteProgress, &ProgressItem::progressItemCanceled, this, &KMTrashMsgCommand::slotMoveCanceled);
1666     }
1667     return OK;
1668 }
1669 
slotMoveResult(KJob * job)1670 void KMTrashMsgCommand::slotMoveResult(KJob *job)
1671 {
1672     mPendingMoves.removeOne(job);
1673     if (job->error()) {
1674         // handle errors
1675         showJobError(job);
1676         completeMove(Failed);
1677     } else if (mPendingMoves.isEmpty() && mPendingDeletes.isEmpty()) {
1678         completeMove(OK);
1679     }
1680 }
1681 
slotDeleteResult(KJob * job)1682 void KMTrashMsgCommand::slotDeleteResult(KJob *job)
1683 {
1684     mPendingDeletes.removeOne(job);
1685     if (job->error()) {
1686         showJobError(job);
1687         completeMove(Failed);
1688     } else if (mPendingDeletes.isEmpty() && mPendingMoves.isEmpty()) {
1689         completeMove(OK);
1690     }
1691 }
1692 
slotMoveCanceled()1693 void KMTrashMsgCommand::slotMoveCanceled()
1694 {
1695     completeMove(Canceled);
1696 }
1697 
completeMove(KMCommand::Result result)1698 void KMTrashMsgCommand::completeMove(KMCommand::Result result)
1699 {
1700     if (result == Failed) {
1701         for (auto job : std::as_const(mPendingMoves)) {
1702             job->kill();
1703         }
1704         for (auto job : std::as_const(mPendingDeletes)) {
1705             job->kill();
1706         }
1707     }
1708 
1709     if (mDeleteProgress) {
1710         mDeleteProgress->setComplete();
1711         mDeleteProgress = nullptr;
1712     }
1713     if (mMoveProgress) {
1714         mMoveProgress->setComplete();
1715         mMoveProgress = nullptr;
1716     }
1717 
1718     setResult(result);
1719     Q_EMIT moveDone(this);
1720     Q_EMIT completed(this);
1721     deleteLater();
1722 }
1723 
KMSaveAttachmentsCommand(QWidget * parent,const Akonadi::Item & msg,MessageViewer::Viewer * viewer)1724 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item &msg, MessageViewer::Viewer *viewer)
1725     : KMCommand(parent, msg)
1726     , mViewer(viewer)
1727 {
1728     fetchScope().fetchFullPayload(true);
1729 }
1730 
KMSaveAttachmentsCommand(QWidget * parent,const Akonadi::Item::List & msgs,MessageViewer::Viewer * viewer)1731 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand(QWidget *parent, const Akonadi::Item::List &msgs, MessageViewer::Viewer *viewer)
1732     : KMCommand(parent, msgs)
1733     , mViewer(viewer)
1734 {
1735     fetchScope().fetchFullPayload(true);
1736 }
1737 
execute()1738 KMCommand::Result KMSaveAttachmentsCommand::execute()
1739 {
1740     KMime::Content::List contentsToSave;
1741     const Akonadi::Item::List lstItems = retrievedMsgs();
1742     for (const Akonadi::Item &item : lstItems) {
1743         if (item.hasPayload<KMime::Message::Ptr>()) {
1744             contentsToSave += item.payload<KMime::Message::Ptr>()->attachments();
1745         } else {
1746             qCWarning(KMAIL_LOG) << "Retrieved item has no payload? Ignoring for saving the attachments";
1747         }
1748     }
1749     QList<QUrl> urlList;
1750     if (MessageViewer::Util::saveAttachments(contentsToSave, parentWidget(), urlList)) {
1751         if (mViewer) {
1752             mViewer->showOpenAttachmentFolderWidget(urlList);
1753         }
1754         return OK;
1755     }
1756     return Failed;
1757 }
1758 
KMResendMessageCommand(QWidget * parent,const Akonadi::Item & msg)1759 KMResendMessageCommand::KMResendMessageCommand(QWidget *parent, const Akonadi::Item &msg)
1760     : KMCommand(parent, msg)
1761 {
1762     fetchScope().fetchFullPayload(true);
1763     fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
1764 }
1765 
execute()1766 KMCommand::Result KMResendMessageCommand::execute()
1767 {
1768     Akonadi::Item item = retrievedMessage();
1769     KMime::Message::Ptr msg = MessageComposer::Util::message(item);
1770     if (!msg) {
1771         return Failed;
1772     }
1773     MessageFactoryNG factory(msg, item.id(), CommonKernel->collectionFromId(item.parentCollection().id()));
1774     factory.setIdentityManager(KMKernel::self()->identityManager());
1775     factory.setFolderIdentity(MailCommon::Util::folderIdentity(item));
1776     KMime::Message::Ptr newMsg = factory.createResend();
1777     newMsg->contentType()->setCharset(MimeTreeParser::NodeHelper::charset(msg.data()));
1778 
1779     KMail::Composer *win = KMail::makeComposer();
1780     bool lastEncrypt = false;
1781     bool lastSign = false;
1782     KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg);
1783     win->setMessage(newMsg, lastSign, lastEncrypt, false, true);
1784 
1785     // Make sure to use current folder as requested by David
1786     // We avoid to use an invalid folder when we open an email on two different computer.
1787     win->setFcc(QString::number(item.parentCollection().id()));
1788     win->show();
1789 
1790     return OK;
1791 }
1792 
KMShareImageCommand(const QUrl & url,QWidget * parent)1793 KMShareImageCommand::KMShareImageCommand(const QUrl &url, QWidget *parent)
1794     : KMCommand(parent)
1795     , mUrl(url)
1796 {
1797 }
1798 
execute()1799 KMCommand::Result KMShareImageCommand::execute()
1800 {
1801     KMime::Message::Ptr msg(new KMime::Message);
1802     uint id = 0;
1803 
1804     MessageHelper::initHeader(msg, KMKernel::self()->identityManager(), id);
1805     msg->contentType()->setCharset("utf-8");
1806 
1807     KMail::Composer *win = KMail::makeComposer(msg, false, false, KMail::Composer::New, id);
1808     win->setFocusToSubject();
1809     QVector<KMail::Composer::AttachmentInfo> infoList;
1810     KMail::Composer::AttachmentInfo info;
1811     info.url = mUrl;
1812     info.comment = i18n("Image");
1813     infoList.append(info);
1814     win->addAttachment(infoList, false);
1815     win->show();
1816     return OK;
1817 }
1818 
KMFetchMessageCommand(QWidget * parent,const Akonadi::Item & item,MessageViewer::Viewer * viewer,KMReaderMainWin * win)1819 KMFetchMessageCommand::KMFetchMessageCommand(QWidget *parent, const Akonadi::Item &item, MessageViewer::Viewer *viewer, KMReaderMainWin *win)
1820     : KMCommand(parent, item)
1821     , mViewer(viewer)
1822     , mReaderMainWin(win)
1823 {
1824     // Workaround KMCommand::transferSelectedMsgs() expecting non-empty fetchscope
1825     fetchScope().fetchFullPayload(true);
1826 }
1827 
createFetchJob(const Akonadi::Item::List & items)1828 Akonadi::ItemFetchJob *KMFetchMessageCommand::createFetchJob(const Akonadi::Item::List &items)
1829 {
1830     Q_ASSERT(items.size() == 1);
1831     Akonadi::ItemFetchJob *fetch = mViewer->createFetchJob(items.first());
1832     fetchScope() = fetch->fetchScope();
1833     return fetch;
1834 }
1835 
execute()1836 KMCommand::Result KMFetchMessageCommand::execute()
1837 {
1838     Akonadi::Item item = retrievedMessage();
1839     if (!item.isValid() || !item.hasPayload<KMime::Message::Ptr>()) {
1840         return Failed;
1841     }
1842 
1843     mItem = item;
1844     return OK;
1845 }
1846 
readerMainWin() const1847 KMReaderMainWin *KMFetchMessageCommand::readerMainWin() const
1848 {
1849     return mReaderMainWin;
1850 }
1851 
item() const1852 Akonadi::Item KMFetchMessageCommand::item() const
1853 {
1854     return mItem;
1855 }
1856 
operator <<(QDebug d,const KMPrintCommandInfo & t)1857 QDebug operator<<(QDebug d, const KMPrintCommandInfo &t)
1858 {
1859     d << "item id " << t.mMsg.id();
1860     d << "mOverrideFont " << t.mOverrideFont;
1861     d << "mEncoding " << t.mEncoding;
1862     d << "mFormat " << t.mFormat;
1863     d << "mHtmlLoadExtOverride " << t.mHtmlLoadExtOverride;
1864     d << "mUseFixedFont " << t.mUseFixedFont;
1865     d << "mPrintPreview " << t.mPrintPreview;
1866     d << "mShowSignatureDetails " << t.mShowSignatureDetails;
1867     d << "mShowEncryptionDetails " << t.mShowEncryptionDetails;
1868     d << "mAttachmentStrategy " << t.mAttachmentStrategy;
1869     d << "mHeaderStylePlugin " << t.mHeaderStylePlugin;
1870     return d;
1871 }
1872