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