From 09c57a404bece694e0e8175cd5c0c9ba00122e81 Mon Sep 17 00:00:00 2001 From: Darrick Date: Tue, 3 Dec 2024 09:52:15 +0100 Subject: [PATCH] [releases/25.x] [Email] Add email retrieval filters (#2425) This pull request backports #2415 to releases/25.x Fixes [AB#550315](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/550315) --------- Co-authored-by: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> --- .github/AL-Go-Settings.json | 3 +- .../App/DotNet Aliases/src/dotnet.al | 10 + .../src/Account/EmailAccountImpl.Codeunit.al | 6 +- .../Email/src/Account/EmailAccounts.Page.al | 34 +- .../DefaultEmailConnectorv2.Codeunit.al | 17 +- .../src/Connector/EmailConnector.Enum.al | 13 +- .../Connector/EmailConnectorv2.Interface.al | 8 +- .../Connector/EmailConnectorv3.Interface.al | 34 ++ .../App/Email/src/Email/Email.Codeunit.al | 78 ++++- .../App/Email/src/Email/EmailImpl.Codeunit.al | 116 +++++-- .../Email/src/Email/Inbox/EmailInbox.Table.al | 10 + .../Inbox/EmailRetrievalFilters.Table.al | 65 ++++ .../App/Email/src/Email/SendEmail.Codeunit.al | 27 +- .../src/Message/EmailMessage.Codeunit.al | 27 +- .../src/Message/EmailMessageImpl.Codeunit.al | 62 ++-- .../Test Library/Email/app.json | 4 + .../Email/src/TestEmailConnector.EnumExt.al | 6 + .../src/TestEmailConnectorv2.Codeunit.al | 8 +- .../src/TestEmailConnectorv3.Codeunit.al | 90 ++++++ .../Test/Email/src/EmailTest.Codeunit.al | 304 ++++++++++++++++-- 20 files changed, 811 insertions(+), 111 deletions(-) create mode 100644 src/System Application/App/Email/src/Connector/EmailConnectorv3.Interface.al create mode 100644 src/System Application/App/Email/src/Email/Inbox/EmailRetrievalFilters.Table.al create mode 100644 src/System Application/Test Library/Email/src/TestEmailConnectorv3.Codeunit.al diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json index 9d995ea2d7..9db124024d 100644 --- a/.github/AL-Go-Settings.json +++ b/.github/AL-Go-Settings.json @@ -18,7 +18,8 @@ "CLEAN22", "CLEAN23", "CLEAN24", - "CLEAN25" + "CLEAN25", + "CLEAN26" ], "unusedALGoSystemFiles": [ "AddExistingAppOrTestApp.yaml", diff --git a/src/System Application/App/DotNet Aliases/src/dotnet.al b/src/System Application/App/DotNet Aliases/src/dotnet.al index 3d6c90fe62..ab644a31b1 100644 --- a/src/System Application/App/DotNet Aliases/src/dotnet.al +++ b/src/System Application/App/DotNet Aliases/src/dotnet.al @@ -261,6 +261,16 @@ dotnet } } + assembly("Microsoft.Dynamics.Nav.AppHTMLSanitizer") + { + Culture = 'neutral'; + PublicKeyToken = '31bf3856ad364e35'; + + type("Microsoft.Dynamics.Nav.AppHTMLSanitizer.AppHtmlSanitizer"; "AppHtmlSanitizer") + { + } + } + assembly("Microsoft.Dynamics.Nav.AzureADGraphClient") { type("Microsoft.Dynamics.Nav.AzureADGraphClient.GroupInfoPage"; "GroupInfoPage") diff --git a/src/System Application/App/Email/src/Account/EmailAccountImpl.Codeunit.al b/src/System Application/App/Email/src/Account/EmailAccountImpl.Codeunit.al index 63bc7aa1f9..b66b5a4022 100644 --- a/src/System Application/App/Email/src/Account/EmailAccountImpl.Codeunit.al +++ b/src/System Application/App/Email/src/Account/EmailAccountImpl.Codeunit.al @@ -29,16 +29,16 @@ codeunit 8889 "Email Account Impl." var EmailAccounts: Record "Email Account"; Connector: Enum "Email Connector"; - IEmailConnector: Interface "Email Connector"; + EmailConnector: Interface "Email Connector"; begin TempEmailAccount.Reset(); TempEmailAccount.DeleteAll(); foreach Connector in Connector.Ordinals do begin - IEmailConnector := Connector; + EmailConnector := Connector; EmailAccounts.DeleteAll(); - IEmailConnector.GetAccounts(EmailAccounts); + EmailConnector.GetAccounts(EmailAccounts); if EmailAccounts.FindSet() then repeat diff --git a/src/System Application/App/Email/src/Account/EmailAccounts.Page.al b/src/System Application/App/Email/src/Account/EmailAccounts.Page.al index bfe33cf06a..71fd3a9995 100644 --- a/src/System Application/App/Email/src/Account/EmailAccounts.Page.al +++ b/src/System Application/App/Email/src/Account/EmailAccounts.Page.al @@ -387,8 +387,8 @@ page 8887 "Email Accounts" IsSelected := not IsNullGuid(SelectedAccountId); EmailAccount.GetAllAccounts(true, Rec); // Refresh the email accounts - if V2Filter then - FilterToConnectorv2Accounts(Rec); + if V2V3Filter then + FilterToConnectorv2v3Accounts(Rec); EmailScenario.GetDefaultEmailAccount(DefaultEmailAccount); // Refresh the default email account if IsSelected then begin @@ -402,7 +402,7 @@ page 8887 "Email Accounts" CurrPage.Update(false); end; - local procedure FilterToConnectorv2Accounts(var EmailAccounts: Record "Email Account") + local procedure FilterToConnectorv2v3Accounts(var EmailAccounts: Record "Email Account") var IConnector: Interface "Email Connector"; begin @@ -411,7 +411,13 @@ page 8887 "Email Accounts" repeat IConnector := EmailAccounts.Connector; - if not (IConnector is "Email Connector v2") then +#if not CLEAN26 +#pragma warning disable AL0432 + if not (IConnector is "Email Connector v2") and not (IConnector is "Email Connector v3") then +#pragma warning restore AL0432 +#else + if not (IConnector is "Email Connector v3") then +#endif EmailAccounts.Delete(); until EmailAccounts.Next() = 0; end; @@ -457,12 +463,24 @@ page 8887 "Email Accounts" end; /// - /// Filters the email accounts to only show accounts that are using the Email Connector v2. + /// Filters the email accounts to only show accounts that are using the Email Connector v2 or v3. /// /// True to filter the email accounts, false to show all email accounts - procedure FilterConnectorV2Accounts(Filter: Boolean) +#if not CLEAN26 + [Obsolete('Replaced by FilterConnectorV3Accounts. In addition, this function now returns both v2 and v3 accounts.', '26.0')] + procedure FilterConnectorV2Accounts(UseFilter: Boolean) begin - V2Filter := Filter; + V2V3Filter := UseFilter; + end; +#endif + + /// + /// Filters the email accounts to only show accounts that are using the Email Connector v2 or v3. + /// + /// True to filter the email accounts, false to show all email accounts + procedure FilterConnectorV3Accounts(UseFilter: Boolean) + begin + V2V3Filter := UseFilter; end; var @@ -477,6 +495,6 @@ page 8887 "Email Accounts" ShowLogo: Boolean; IsLookupMode: Boolean; HasEmailAccount: Boolean; - V2Filter: Boolean; + V2V3Filter: Boolean; EmailConnectorHasBeenUninstalledMsg: Label 'The selected email extension has been uninstalled. To view information about the email account, you must reinstall the extension.'; } \ No newline at end of file diff --git a/src/System Application/App/Email/src/Connector/DefaultEmailConnectorv2.Codeunit.al b/src/System Application/App/Email/src/Connector/DefaultEmailConnectorv2.Codeunit.al index 9be71709cb..1a12853797 100644 --- a/src/System Application/App/Email/src/Connector/DefaultEmailConnectorv2.Codeunit.al +++ b/src/System Application/App/Email/src/Connector/DefaultEmailConnectorv2.Codeunit.al @@ -8,8 +8,15 @@ namespace System.Email; /// /// This is the default implementation of the Email Connector v2 interface which adds the reply, retrievial of emails and marking them as read functionalities. /// -codeunit 8998 "Default Email Connector v2" implements "Email Connector v2" +#if not CLEAN26 +#pragma warning disable AL0432 +codeunit 8998 "Default Email Connector v2" implements "Email Connector v2", "Email Connector v3" +#pragma warning restore AL0432 +#else +codeunit 8998 "Default Email Connector v2" implements "Email Connector v3" +#endif { + procedure Send(EmailMessage: Codeunit "Email Message"; AccountId: Guid) begin @@ -50,10 +57,18 @@ codeunit 8998 "Default Email Connector v2" implements "Email Connector v2" end; +#if not CLEAN26 + [Obsolete('Replaced by RetrieveEmails with an additional Filters parameter of type Record "Email Retrieval Filters".', '26.0')] procedure RetrieveEmails(AccountId: Guid; var EmailInbox: Record "Email Inbox") begin end; +#endif + + procedure RetrieveEmails(AccountId: Guid; var EmailInbox: Record "Email Inbox"; var Filters: Record "Email Retrieval Filters" temporary) + begin + + end; procedure MarkAsRead(AccountId: Guid; ExternalId: Text) begin diff --git a/src/System Application/App/Email/src/Connector/EmailConnector.Enum.al b/src/System Application/App/Email/src/Connector/EmailConnector.Enum.al index e7b91df374..e44699a614 100644 --- a/src/System Application/App/Email/src/Connector/EmailConnector.Enum.al +++ b/src/System Application/App/Email/src/Connector/EmailConnector.Enum.al @@ -8,9 +8,18 @@ namespace System.Email; /// /// Enum that holds all of the available email connectors. /// -enum 8889 "Email Connector" implements "Email Connector", "Email Connector v2", "Default Email Rate Limit" +#if not CLEAN26 +#pragma warning disable AL0432 +enum 8889 "Email Connector" implements "Email Connector", "Email Connector v2", "Email Connector v3", "Default Email Rate Limit" +#pragma warning restore AL0432 +#else +enum 8889 "Email Connector" implements "Email Connector", "Email Connector v3", "Default Email Rate Limit" +#endif { Extensible = true; DefaultImplementation = "Default Email Rate Limit" = "Default Email Rate Limit", - "Email Connector v2" = "Default Email Connector v2"; +#if not CLEAN26 + "Email Connector v2" = "Default Email Connector v2", +#endif + "Email Connector v3" = "Default Email Connector v2"; } \ No newline at end of file diff --git a/src/System Application/App/Email/src/Connector/EmailConnectorv2.Interface.al b/src/System Application/App/Email/src/Connector/EmailConnectorv2.Interface.al index 20f36643f5..73fa9a93bd 100644 --- a/src/System Application/App/Email/src/Connector/EmailConnectorv2.Interface.al +++ b/src/System Application/App/Email/src/Connector/EmailConnectorv2.Interface.al @@ -1,3 +1,4 @@ +#if not CLEAN26 // ------------------------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. @@ -10,6 +11,10 @@ namespace System.Email; /// interface "Email Connector v2" extends "Email Connector" { + ObsoleteReason = 'Replaced by "Email Connector v3" which adds filtering capability for retrieval of emails'; + ObsoleteState = Pending; + ObsoleteTag = '26.0'; + /// /// Reply to an e-mail using the provided account. /// @@ -30,4 +35,5 @@ interface "Email Connector v2" extends "Email Connector" /// The email account ID. /// The external ID of the email. procedure MarkAsRead(AccountId: Guid; ExternalId: Text); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/System Application/App/Email/src/Connector/EmailConnectorv3.Interface.al b/src/System Application/App/Email/src/Connector/EmailConnectorv3.Interface.al new file mode 100644 index 0000000000..3457e2edb0 --- /dev/null +++ b/src/System Application/App/Email/src/Connector/EmailConnectorv3.Interface.al @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Email; + +/// +/// An e-mail connector interface enhances the "Email Connector" with reading, replying to e-mails and marking emails as read. +/// +interface "Email Connector v3" extends "Email Connector" +{ + /// + /// Reply to an e-mail using the provided account. + /// + /// The email message that is to be sent out. + /// The email account ID which is used to send out the email. + procedure Reply(var EmailMessage: Codeunit "Email Message"; AccountId: Guid); + + /// + /// Read e-mails from the provided account. + /// + /// The email account ID which is used to send out the email. + /// The email inbox record that will store the emails. + /// Filters to be used when retrieving emails. + procedure RetrieveEmails(AccountId: Guid; var EmailInbox: Record "Email Inbox"; var Filters: Record "Email Retrieval Filters" temporary); + + /// + /// Mark an e-mail as read in the provided account. + /// + /// The email account ID. + /// The external ID of the email. + procedure MarkAsRead(AccountId: Guid; ExternalId: Text); +} \ No newline at end of file diff --git a/src/System Application/App/Email/src/Email/Email.Codeunit.al b/src/System Application/App/Email/src/Email/Email.Codeunit.al index 6455872e7a..8968910823 100644 --- a/src/System Application/App/Email/src/Email/Email.Codeunit.al +++ b/src/System Application/App/Email/src/Email/Email.Codeunit.al @@ -197,7 +197,7 @@ codeunit 8901 Email #endregion #region Reply - +#if not CLEAN26 /// /// Sends a reply to an external message id in the foreground. /// @@ -206,9 +206,10 @@ codeunit 8901 Email /// The ID of the email account to use for sending the email. /// The email connector to use for sending the email. /// True if sent + [Obsolete('Replaced by Reply without the ExternalId parameter. ExternalId is not used and is contained in the EmailMessage parameter.', '26.0')] procedure Reply(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean begin - exit(EmailImpl.Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector)); + exit(EmailImpl.Reply(EmailMessage, EmailAccountId, EmailConnector)); end; /// @@ -219,9 +220,10 @@ codeunit 8901 Email /// The ID of the email account to use for sending the email. /// The email connector to use for sending the email. /// True if sent + [Obsolete('Replaced by ReplyAll without the ExternalId parameter. ExternalId is not used and is contained in the EmailMessage parameter.', '26.0')] procedure ReplyAll(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean begin - exit(EmailImpl.ReplyAll(EmailMessage, ExternalId, EmailAccountId, EmailConnector)); + exit(EmailImpl.ReplyAll(EmailMessage, EmailAccountId, EmailConnector)); end; /// @@ -232,9 +234,10 @@ codeunit 8901 Email /// The ID of the email account to use for sending the email. /// The email connector to use for sending the email. /// The email outbox which is set up for sending in the background. + [Obsolete('Replaced by EnqueueReply without the ExternalId parameter. ExternalId is not used and is contained in the EmailMessage parameter.', '26.0')] procedure EnqueueReply(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") begin - EmailImpl.Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox); + EmailImpl.Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox); end; /// @@ -245,25 +248,88 @@ codeunit 8901 Email /// The ID of the email account to use for sending the email. /// The email connector to use for sending the email. /// The email outbox which is set up for sending in the background. + [Obsolete('Replaced by EnqueueReplyAll without the ExternalId parameter. ExternalId is not used and is contained in the EmailMessage parameter.', '26.0')] procedure EnqueueReplyAll(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") begin - EmailImpl.ReplyAll(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox); + EmailImpl.ReplyAll(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox); + end; +#endif + + /// + /// Sends a reply to an external message id in the foreground. + /// + /// The email message with the details of the recipients and reply to be added. + /// The ID of the email account to use for sending the email. + /// The email connector to use for sending the email. + /// True if sent + procedure Reply(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean + begin + exit(EmailImpl.Reply(EmailMessage, EmailAccountId, EmailConnector)); + end; + + /// + /// Sends a reply to an external message id to all recipients on that email in the foreground. + /// + /// The email message with the details of the recipients and reply to be added. + /// The ID of the email account to use for sending the email. + /// The email connector to use for sending the email. + /// True if sent + procedure ReplyAll(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean + begin + exit(EmailImpl.ReplyAll(EmailMessage, EmailAccountId, EmailConnector)); + end; + + /// + /// Sends a reply to an external message id in the background. + /// + /// The email message with the details of the recipients and reply to be added. + /// The ID of the email account to use for sending the email. + /// The email connector to use for sending the email. + /// The email outbox which is set up for sending in the background. + procedure EnqueueReply(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") + begin + EmailImpl.Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox); + end; + + /// + /// Sends a reply to an external message id to all recipients on that email in the foreground. + /// + /// The email message with the details of the recipients and reply to be added. + /// The ID of the email account to use for sending the email. + /// The email connector to use for sending the email. + /// The email outbox which is set up for sending in the background. + procedure EnqueueReplyAll(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") + begin + EmailImpl.ReplyAll(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox); end; #endregion #region RetrieveEmails - +#if not CLEAN26 /// /// Retrieves emails from the email account. /// /// The ID of the email account to use for sending the email. /// The email connector to use for sending the email. /// The return record of all new emails that were retrieved. + [Obsolete('Replaced by RetrieveEmails with an additional Filters parameter of type Record "Email Retrieval Filters".', '26.0')] procedure RetrieveEmails(EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailInbox: Record "Email Inbox") begin EmailImpl.RetrieveEmails(EmailAccountId, EmailConnector, EmailInbox); end; +#endif + /// + /// Retrieves emails from the email account. + /// + /// The ID of the email account to use for sending the email. + /// The email connector to use for sending the email. + /// The return record of all new emails that were retrieved. + /// Filters to be used when retrieving emails. + procedure RetrieveEmails(EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailInbox: Record "Email Inbox"; var Filters: Record "Email Retrieval Filters" temporary) + begin + EmailImpl.RetrieveEmails(EmailAccountId, EmailConnector, EmailInbox, Filters); + end; #endregion diff --git a/src/System Application/App/Email/src/Email/EmailImpl.Codeunit.al b/src/System Application/App/Email/src/Email/EmailImpl.Codeunit.al index e08ce2c8a6..c181dfe49f 100644 --- a/src/System Application/App/Email/src/Email/EmailImpl.Codeunit.al +++ b/src/System Application/App/Email/src/Email/EmailImpl.Codeunit.al @@ -125,31 +125,31 @@ codeunit 8900 "Email Impl" exit(Send(EmailMessage, EmailAccountId, EmailConnector, false, CurrentDateTime(), EmailOutbox)); end; - procedure Reply(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean + procedure Reply(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean var EmailOutbox: Record "Email Outbox"; begin - exit(Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), false, false)); + exit(Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), false, false)); end; - procedure ReplyAll(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean + procedure ReplyAll(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean var EmailOutbox: Record "Email Outbox"; begin - exit(Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), false, true)); + exit(Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), false, true)); end; - procedure Reply(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") + procedure Reply(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") begin - Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), true, false); + Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), true, false); end; - procedure ReplyAll(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") + procedure ReplyAll(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox") begin - Reply(EmailMessage, ExternalId, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), true, true); + Reply(EmailMessage, EmailAccountId, EmailConnector, EmailOutbox, CurrentDateTime(), true, true); end; - procedure Reply(EmailMessage: Codeunit "Email Message"; ExternalId: Text; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox"; NotBefore: DateTime; InBackground: Boolean; ReplyToAll: Boolean): Boolean + procedure Reply(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"; var EmailOutbox: Record "Email Outbox"; NotBefore: DateTime; InBackground: Boolean; ReplyToAll: Boolean): Boolean var EmailAccountRec: Record "Email Account"; CurrentUser: Record User; @@ -172,6 +172,9 @@ codeunit 8900 "Email Impl" if GetEmailOutbox(EmailMessage.GetId(), EmailOutbox) and IsOutboxEnqueued(EmailOutbox) then Error(EmailMessageQueuedErr); + if EmailMessage.GetExternalId() = '' then + Error(ExternalIdCannotBeEmptyErr); + // Get email account GetEmailAccount(EmailAccountId, EmailConnector, EmailAccountRec); @@ -199,17 +202,41 @@ codeunit 8900 "Email Impl" procedure RetrieveEmails(EmailAccountId: Guid; Connector: Enum "Email Connector"; var EmailInbox: Record "Email Inbox") var - IEmailConnectorv2: Interface "Email Connector v2"; + Filters: Record "Email Retrieval Filters"; + begin + Filters.Insert(); + RetrieveEmails(EmailAccountId, Connector, EmailInbox, Filters); + end; + + procedure RetrieveEmails(EmailAccountId: Guid; Connector: Enum "Email Connector"; var EmailInbox: Record "Email Inbox"; var Filters: Record "Email Retrieval Filters" temporary) + var +#if not CLEAN26 +#pragma warning disable AL0432 + EmailConnectorv2: Interface "Email Connector v2"; +#pragma warning restore AL0432 +#endif + EmailConnectorv3: Interface "Email Connector v3"; begin CheckRequiredPermissions(); - if CheckAndGetEmailConnectorv2(Connector, IEmailConnectorv2) then begin + if CheckAndGetEmailConnectorv3(Connector, EmailConnectorv3) then begin TelemetryAppsAndPublishers(TelemetryRetrieveEmailsUsedTxt); - IEmailConnectorv2.RetrieveEmails(EmailAccountId, EmailInbox); - end else - Error(EmailConnectorDoesNotSupportRetrievingEmailsErr); + EmailConnectorv3.RetrieveEmails(EmailAccountId, EmailInbox, Filters); + EmailInbox.MarkedOnly(true); + exit; + end; +#if not CLEAN26 +#pragma warning disable AL0432 + if CheckAndGetEmailConnectorv2(Connector, EmailConnectorv2) then begin +#pragma warning restore AL0432 + TelemetryAppsAndPublishers(TelemetryRetrieveEmailsUsedTxt); + EmailConnectorv2.RetrieveEmails(EmailAccountId, EmailInbox); + EmailInbox.MarkedOnly(true); + exit; + end; +#endif - EmailInbox.MarkedOnly(true); + Error(EmailConnectorDoesNotSupportRetrievingEmailsErr); end; local procedure TelemetryAppsAndPublishers(Message: Text) @@ -236,30 +263,59 @@ codeunit 8900 "Email Impl" procedure MarkAsRead(EmailAccountId: Guid; Connector: Enum "Email Connector"; ExternalId: Text) var - IEmailConnectorv2: Interface "Email Connector v2"; +#if not CLEAN26 +#pragma warning disable AL0432 + EmailConnectorv2: Interface "Email Connector v2"; +#pragma warning restore AL0432 +#endif + EmailConnectorv3: Interface "Email Connector v3"; begin CheckRequiredPermissions(); if ExternalId = '' then Error(ExternalIdCannotBeEmptyErr); - if CheckAndGetEmailConnectorv2(Connector, IEmailConnectorv2) then - IEmailConnectorv2.MarkAsRead(EmailAccountId, ExternalId) - else - Error(EmailConnectorDoesNotSupportMarkAsReadErr); + if CheckAndGetEmailConnectorv3(Connector, EmailConnectorv3) then begin + EmailConnectorv3.MarkAsRead(EmailAccountId, ExternalId); + exit; + end; +#if not CLEAN26 +#pragma warning disable AL0432 + if CheckAndGetEmailConnectorv2(Connector, EmailConnectorv2) then begin +#pragma warning restore AL0432 + EmailConnectorv2.MarkAsRead(EmailAccountId, ExternalId); + exit; + end; +#endif + + Error(EmailConnectorDoesNotSupportMarkAsReadErr); end; procedure CheckReplySupported(Connector: Enum "Email Connector"): Boolean var - IEmailConnectorv2: Interface "Email Connector v2"; +#if not CLEAN26 +#pragma warning disable AL0432 + EmailConnectorv2: Interface "Email Connector v2"; +#pragma warning restore AL0432 +#endif + EmailConnectorv3: Interface "Email Connector v3"; begin - if not CheckAndGetEmailConnectorv2(Connector, IEmailConnectorv2) then - Error(EmailconnectorDoesNotSupportReplyingErr); + if CheckAndGetEmailConnectorv3(Connector, EmailConnectorv3) then + exit(true); +#if not CLEAN26 +#pragma warning disable AL0432 + if CheckAndGetEmailConnectorv2(Connector, EmailConnectorv2) then + exit(true); +#pragma warning restore AL0432 +#endif - exit(true); + Error(EmailconnectorDoesNotSupportReplyingErr); end; - +#if not CLEAN26 +#pragma warning disable AL0432 + [Obsolete('Replaced by CheckAndGetEmailConnectorv3.', '26.0')] procedure CheckAndGetEmailConnectorv2(Connector: Interface "Email Connector"; var Connectorv2: Interface "Email Connector v2"): Boolean +#pragma warning restore AL0432 begin if Connector is "Email Connector v2" then begin Connectorv2 := Connector as "Email Connector v2"; @@ -267,6 +323,16 @@ codeunit 8900 "Email Impl" end else exit(false); end; +#endif + + procedure CheckAndGetEmailConnectorv3(Connector: Interface "Email Connector"; var Connectorv3: Interface "Email Connector v3"): Boolean + begin + if Connector is "Email Connector v3" then begin + Connectorv3 := Connector as "Email Connector v3"; + exit(true); + end else + exit(false); + end; procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario"; IsModal: Boolean): Enum "Email Action" var diff --git a/src/System Application/App/Email/src/Email/Inbox/EmailInbox.Table.al b/src/System Application/App/Email/src/Email/Inbox/EmailInbox.Table.al index 4235488296..d9ba46f55f 100644 --- a/src/System Application/App/Email/src/Email/Inbox/EmailInbox.Table.al +++ b/src/System Application/App/Email/src/Email/Inbox/EmailInbox.Table.al @@ -76,6 +76,16 @@ table 8886 "Email Inbox" { DataClassification = CustomerContent; } + + field(13; "Is Read"; Boolean) + { + DataClassification = CustomerContent; + } + + field(14; "Is Draft"; Boolean) + { + DataClassification = CustomerContent; + } } keys diff --git a/src/System Application/App/Email/src/Email/Inbox/EmailRetrievalFilters.Table.al b/src/System Application/App/Email/src/Email/Inbox/EmailRetrievalFilters.Table.al new file mode 100644 index 0000000000..7f17da78b6 --- /dev/null +++ b/src/System Application/App/Email/src/Email/Inbox/EmailRetrievalFilters.Table.al @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Email; + +/// Holds information about the filters for retrieving emails. +table 8885 "Email Retrieval Filters" +{ + Access = Public; + TableType = Temporary; + DataClassification = SystemMetadata; + InherentEntitlements = RIMDX; + InherentPermissions = RIMDX; + + fields + { + field(1; Id; Integer) + { + AutoIncrement = true; + } + + field(2; "Load Attachments"; Boolean) + { + Caption = 'Load Attachments'; + } + + field(3; "Unread Emails"; Boolean) + { + Caption = 'Unread Emails'; + } + + field(4; "Draft Emails"; Boolean) + { + Caption = 'Draft Emails'; + } + + field(5; "Max No. of Emails"; Integer) + { + InitValue = 20; + Caption = 'Max No. of Emails'; + } + + field(6; "Body Type"; Option) + { + OptionMembers = "HTML","Text"; + InitValue = "HTML"; + Caption = 'Body Type'; + } + + field(7; "Earliest Email"; DateTime) + { + Caption = 'Earliest Email'; + } + } + + keys + { + key(PK; Id) + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/src/System Application/App/Email/src/Email/SendEmail.Codeunit.al b/src/System Application/App/Email/src/Email/SendEmail.Codeunit.al index 08fd191271..c236de3940 100644 --- a/src/System Application/App/Email/src/Email/SendEmail.Codeunit.al +++ b/src/System Application/App/Email/src/Email/SendEmail.Codeunit.al @@ -16,22 +16,33 @@ codeunit 8890 "Send Email" var EmailMessage: Codeunit "Email Message"; EmailImpl: Codeunit "Email Impl"; - IEmailConnector: Interface "Email Connector"; - IEmailConnectorv2: Interface "Email Connector v2"; + EmailConnector: Interface "Email Connector"; +#if not CLEAN26 +#pragma warning disable AL0432 + EmailConnectorv2: Interface "Email Connector v2"; +#pragma warning restore AL0432 +#endif + EmailConnectorv3: Interface "Email Connector v3"; begin EmailMessage.Get(Rec.Id); if EmailMessage.GetExternalId() <> '' then begin - IEmailConnector := EmailConnector; - if EmailImpl.CheckAndGetEmailConnectorv2(IEmailConnector, IEmailConnectorv2) then - IEmailConnectorv2.Reply(EmailMessage, AccountId); + EmailConnector := GlobalEmailConnector; +#if not CLEAN26 +#pragma warning disable AL0432 + if EmailImpl.CheckAndGetEmailConnectorv2(EmailConnector, EmailConnectorv2) then +#pragma warning restore AL0432 + EmailConnectorv2.Reply(EmailMessage, AccountId); +#endif + if EmailImpl.CheckAndGetEmailConnectorv3(EmailConnector, EmailConnectorv3) then + EmailConnectorv3.Reply(EmailMessage, AccountId); end else - EmailConnector.Send(EmailMessage, AccountId); + GlobalEmailConnector.Send(EmailMessage, AccountId); end; procedure SetConnector(NewEmailConnector: Interface "Email Connector") begin - EmailConnector := NewEmailConnector; + GlobalEmailConnector := NewEmailConnector; end; procedure SetAccount(NewAccountId: Guid) @@ -40,6 +51,6 @@ codeunit 8890 "Send Email" end; var - EmailConnector: Interface "Email Connector"; + GlobalEmailConnector: Interface "Email Connector"; AccountId: Guid; } \ No newline at end of file diff --git a/src/System Application/App/Email/src/Message/EmailMessage.Codeunit.al b/src/System Application/App/Email/src/Message/EmailMessage.Codeunit.al index 01c836d4c8..3ae7967701 100644 --- a/src/System Application/App/Email/src/Message/EmailMessage.Codeunit.al +++ b/src/System Application/App/Email/src/Message/EmailMessage.Codeunit.al @@ -35,6 +35,21 @@ codeunit 8904 "Email Message" EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted); end; + /// + /// Creates the email with recipients, subject, and body. + /// + /// The recipient(s) of the email. A string containing the email addresses of the recipients separated by semicolon. + /// The subject of the email. + /// The body of the email. + /// Whether the body is HTML formatted. + /// If the body is of HTML formatting, most of it will be sanitized. Keeping only tags and no styling. + /// Sanitize is only applicable if the body is HTML formatted. It also helps prevent potential email messages from hiding images and text from the user. + procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean; Sanitize: Boolean) + begin + EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted, Sanitize); + end; + + /// /// Creates the email with recipients, subject, and body. /// @@ -43,8 +58,11 @@ codeunit 8904 "Email Message" /// The body of the email /// Whether the body is HTML formatted procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean) + var + CCRecipients: List of [Text]; + BCCRecipients: List of [Text]; begin - EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted); + EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted, CCRecipients, BCCRecipients, false); end; /// @@ -58,7 +76,7 @@ codeunit 8904 "Email Message" /// TThe BCC recipient(s) of the email. A list of email addresses that will be listed as BCC. procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) begin - EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted, CCRecipients, BCCRecipients); + EmailMessageImpl.Create(ToRecipients, Subject, Body, HtmlFormatted, CCRecipients, BCCRecipients, false); end; /// @@ -83,8 +101,11 @@ codeunit 8904 "Email Message" /// Whether the body is HTML formatted. /// The external message id to reply to. procedure CreateReply(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text) + var + CCRecipients: List of [Text]; + BCCRecipients: List of [Text]; begin - EmailMessageImpl.CreateReply(ToRecipients, Subject, Body, HtmlFormatted, ExternalId); + EmailMessageImpl.CreateReply(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients); end; /// diff --git a/src/System Application/App/Email/src/Message/EmailMessageImpl.Codeunit.al b/src/System Application/App/Email/src/Message/EmailMessageImpl.Codeunit.al index 1ceac8f859..9dc5be3522 100644 --- a/src/System Application/App/Email/src/Message/EmailMessageImpl.Codeunit.al +++ b/src/System Application/App/Email/src/Message/EmailMessageImpl.Codeunit.al @@ -53,57 +53,50 @@ codeunit 8905 "Email Message Impl." end; procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean) - var - EmptyList: List of [Text]; begin -#pragma warning disable AA0205 - Create(EmptyList, Subject, Body, HtmlFormatted); -#pragma warning restore AA0205 - - SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients); + Create(ToRecipients, Subject, Body, HtmlFormatted, false); end; - procedure Create(Recipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean) + procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean; Sanitize: Boolean) var - EmptyList: List of [Text]; + Recipients: List of [Text]; + CCRecipients: List of [Text]; + BCCRecipients: List of [Text]; begin -#pragma warning disable AA0205 - Create(Recipients, Subject, Body, HtmlFormatted, EmptyList, EmptyList); -#pragma warning restore AA0205 + Create(Recipients, Subject, Body, HtmlFormatted, CCRecipients, BCCRecipients, Sanitize); + + SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients); end; - procedure Create(Recipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) + procedure Create(Recipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]; Sanitize: Boolean) begin InitializeCreation(); - UpdateMessage(Recipients, Subject, Body, HtmlFormatted, '', CCRecipients, BCCRecipients); + UpdateMessage(Recipients, Subject, Body, HtmlFormatted, '', CCRecipients, BCCRecipients, Sanitize); end; procedure CreateReply(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text) var - EmptyList: List of [Text]; + Recipients: List of [Text]; + CCRecipients: List of [Text]; + BCCRecipients: List of [Text]; begin - CreateReply(EmptyList, Subject, Body, HtmlFormatted, ExternalId, EmptyList, EmptyList); + CreateReply(Recipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients); SetRecipients(Enum::"Email Recipient Type"::"To", ToRecipients); end; - procedure CreateReply(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text) - var - EmptyList: List of [Text]; + procedure CreateReply(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) begin - CreateReply(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, EmptyList, EmptyList); + InitializeCreation(); + UpdateMessage(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients, false); end; procedure CreateReplyAll(Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text) var - EmptyList: List of [Text]; + ToRecipients: List of [Text]; + CCRecipients: List of [Text]; + BCCRecipients: List of [Text]; begin - CreateReply(EmptyList, Subject, Body, HtmlFormatted, ExternalId, EmptyList, EmptyList); - end; - - procedure CreateReply(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) - begin - InitializeCreation(); - UpdateMessage(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients); + CreateReply(ToRecipients, Subject, Body, HtmlFormatted, ExternalId, CCRecipients, BCCRecipients); end; local procedure InitializeCreation() @@ -115,8 +108,11 @@ codeunit 8905 "Email Message Impl." GlobalEmailMessage.Insert(); end; - procedure UpdateMessage(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) + procedure UpdateMessage(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; ExternalId: Text; CCRecipients: List of [Text]; BCCRecipients: List of [Text]; Sanitize: Boolean) begin + if HtmlFormatted and Sanitize then + Body := SanitizeBody(Body); + SetBodyValue(Body); SetSubjectValue(Subject); SetBodyHTMLFormattedValue(HtmlFormatted); @@ -128,6 +124,14 @@ codeunit 8905 "Email Message Impl." SetRecipients(Enum::"Email Recipient Type"::Bcc, BCCRecipients); end; + local procedure SanitizeBody(Body: Text): Text + var + AppHTMLSanitizer: DotNet AppHtmlSanitizer; + begin + AppHTMLSanitizer := AppHTMLSanitizer.AppHtmlSanitizer(); + exit(AppHTMLSanitizer.SanitizeEmail(Body)); + end; + procedure Modify() var EmailMessage: Record "Email Message"; diff --git a/src/System Application/Test Library/Email/app.json b/src/System Application/Test Library/Email/app.json index 20a3312277..29291181ac 100644 --- a/src/System Application/Test Library/Email/app.json +++ b/src/System Application/Test Library/Email/app.json @@ -40,6 +40,10 @@ { "from": 134694, "to": 134694 + }, + { + "from": 134702, + "to": 134702 } ], "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520", diff --git a/src/System Application/Test Library/Email/src/TestEmailConnector.EnumExt.al b/src/System Application/Test Library/Email/src/TestEmailConnector.EnumExt.al index d6810edf0b..b438681ab3 100644 --- a/src/System Application/Test Library/Email/src/TestEmailConnector.EnumExt.al +++ b/src/System Application/Test Library/Email/src/TestEmailConnector.EnumExt.al @@ -13,8 +13,14 @@ enumextension 134684 "Test Email Connector" extends "Email Connector" { Implementation = "Email Connector" = "Test Email Connector"; } +#if not CLEAN26 value(134685; "Test Email Connector v2") { Implementation = "Email Connector" = "Test Email Connector v2"; } +#endif + value(134686; "Test Email Connector v3") + { + Implementation = "Email Connector" = "Test Email Connector v3"; + } } \ No newline at end of file diff --git a/src/System Application/Test Library/Email/src/TestEmailConnectorv2.Codeunit.al b/src/System Application/Test Library/Email/src/TestEmailConnectorv2.Codeunit.al index 7e2d9dd685..c7602641ad 100644 --- a/src/System Application/Test Library/Email/src/TestEmailConnectorv2.Codeunit.al +++ b/src/System Application/Test Library/Email/src/TestEmailConnectorv2.Codeunit.al @@ -1,3 +1,4 @@ +#if not CLEAN26 // ------------------------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. @@ -7,7 +8,9 @@ namespace System.TestLibraries.Email; using System.Email; -codeunit 134682 "Test Email Connector v2" implements "Email Connector v2", "Email Connector" // Temporary bug #540622 +#pragma warning disable AL0432 +codeunit 134682 "Test Email Connector v2" implements "Email Connector v2" +#pragma warning restore AL0432 { var @@ -87,4 +90,5 @@ codeunit 134682 "Test Email Connector v2" implements "Email Connector v2", "Emai if ConnectorMock.FailOnMarkAsRead() then Error('Failed to mark email as read'); end; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/System Application/Test Library/Email/src/TestEmailConnectorv3.Codeunit.al b/src/System Application/Test Library/Email/src/TestEmailConnectorv3.Codeunit.al new file mode 100644 index 0000000000..20c89be8aa --- /dev/null +++ b/src/System Application/Test Library/Email/src/TestEmailConnectorv3.Codeunit.al @@ -0,0 +1,90 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.TestLibraries.Email; + +using System.Email; + +codeunit 134702 "Test Email Connector v3" implements "Email Connector v3" +{ + + var + ConnectorMock: Codeunit "Connector Mock"; + + procedure Send(EmailMessage: Codeunit "Email Message"; AccountId: Guid) + begin + ConnectorMock.SetEmailMessageID(EmailMessage.GetId()); + Commit(); + if ConnectorMock.FailOnSend() then + Error('Failed to send email'); + end; + + procedure GetAccounts(var Accounts: Record "Email Account") + begin + ConnectorMock.GetAccounts(Accounts, Enum::"Email Connector"::"Test Email Connector v3"); + end; + + procedure ShowAccountInformation(AccountId: Guid) + begin + Message('Showing information for account: %1', AccountId); + end; + + procedure RegisterAccount(var EmailAccount: Record "Email Account"): Boolean + begin + if ConnectorMock.FailOnRegisterAccount() then + Error('Failed to register account'); + + if ConnectorMock.UnsuccessfulRegister() then + exit(false); + + EmailAccount."Account Id" := CreateGuid(); + EmailAccount."Email Address" := 'Test email address'; + EmailAccount.Name := 'Test account'; + + exit(true); + end; + + procedure DeleteAccount(AccountId: Guid): Boolean + var + TestEmailAccount: Record "Test Email Account"; + begin + if TestEmailAccount.Get(AccountId) then + exit(TestEmailAccount.Delete()); + exit(false); + end; + + procedure GetLogoAsBase64(): Text + begin + + end; + + procedure GetDescription(): Text[250] + begin + exit('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ornare ante a est commodo interdum. Pellentesque eu diam maximus, faucibus neque ut, viverra leo. Praesent ullamcorper nibh ut pretium dapibus. Nullam eu dui libero. Etiam ac cursus metus.') + end; + + procedure Reply(var EmailMessage: Codeunit "Email Message"; AccountId: Guid) + begin + if ConnectorMock.FailOnReply() then + Error('Failed to send email'); + end; + + procedure RetrieveEmails(AccountId: Guid; var EmailInbox: Record "Email Inbox"; var Filter: Record "Email Retrieval Filters" temporary) + begin + if ConnectorMock.FailOnRetrieveEmails() then + Error('Failed to retrieve emails'); + + ConnectorMock.CreateEmailInbox(AccountId, Enum::"Email Connector"::"Test Email Connector v3", EmailInbox); + EmailInbox.Mark(true); + ConnectorMock.CreateEmailInbox(AccountId, Enum::"Email Connector"::"Test Email Connector v3", EmailInbox); + EmailInbox.Mark(true); + end; + + procedure MarkAsRead(AccountId: Guid; ConversationId: Text) + begin + if ConnectorMock.FailOnMarkAsRead() then + Error('Failed to mark email as read'); + end; +} \ No newline at end of file diff --git a/src/System Application/Test/Email/src/EmailTest.Codeunit.al b/src/System Application/Test/Email/src/EmailTest.Codeunit.al index dc28d58a37..45ab5a8cfe 100644 --- a/src/System Application/Test/Email/src/EmailTest.Codeunit.al +++ b/src/System Application/Test/Email/src/EmailTest.Codeunit.al @@ -390,7 +390,6 @@ codeunit 134685 "Email Test" Assert.ExpectedError(EmailMessageCannotBeEditedErr); end; - [Test] [HandlerFunctions('EmailEditorHandler,OnEmailEditorClose')] procedure OpenInEditorModallyDiscardAOptionTest() @@ -1403,6 +1402,7 @@ codeunit 134685 "Email Test" var EmailAccount: Record "Email Account"; EmailInbox: Record "Email Inbox"; + TempFilters: Record "Email Retrieval Filters" temporary; ConnectorMock: Codeunit "Connector Mock"; begin // [Scenario] Retrieving emails with a V1 connector should fail @@ -1412,12 +1412,14 @@ codeunit 134685 "Email Test" // [When] Retrieving emails // [Then] An error is thrown that the connector does not support this operation - asserterror Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox); + asserterror Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox, TempFilters); Assert.ExpectedError('The selected email connector does not support retrieving emails'); end; +#if not CLEAN26 [Test] - procedure RetrieveEmails() + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] + procedure RetrieveEmailsv2() var EmailAccount: Record "Email Account"; EmailInbox: Record "Email Inbox"; @@ -1438,7 +1440,9 @@ codeunit 134685 "Email Test" InitialId := EmailInbox.Id; // [When] Retrieving emails +#pragma warning disable AL0432 Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox); +#pragma warning restore AL0432 // [Then] The EmailInbox will be filled only with new emails and not existing ones EmailInbox.FindSet(); @@ -1450,7 +1454,8 @@ codeunit 134685 "Email Test" end; [Test] - procedure RetrieveEmailsFail() + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] + procedure RetrieveEmailsFailv2() var EmailAccount: Record "Email Account"; EmailInbox: Record "Email Inbox"; @@ -1472,7 +1477,73 @@ codeunit 134685 "Email Test" ConnectorMock.FailOnRetrieveEmails(true); // [When] Retrieving emails +#pragma warning disable AL0432 asserterror Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox); +#pragma warning restore AL0432 + + // [Then] The EmailInbox will be filled only with new emails and not existing ones + Assert.ExpectedError('Failed to retrieve emails'); + end; +#endif + [Test] + procedure RetrieveEmailsv3() + var + EmailAccount: Record "Email Account"; + EmailInbox: Record "Email Inbox"; + TempFilters: Record "Email Retrieval Filters" temporary; + ConnectorMock: Codeunit "Connector Mock"; + InitialId: Integer; + begin + // [Scenario] Retrieving emails with a V3 connector will succeed and the EmailInbox will be filled only with new emails and not existing ones + PermissionsMock.Set('Email Edit'); + + // [Given] An email account with a V3 connector + // [Given] Existing emails in Email Inbox + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + + EmailInbox.DeleteAll(); + ConnectorMock.CreateEmailInbox(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox); + Assert.AreEqual(1, EmailInbox.Count(), 'Wrong number of emails in the inbox'); + InitialId := EmailInbox.Id; + + // [When] Retrieving emails + Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox, TempFilters); + + // [Then] The EmailInbox will be filled only with new emails and not existing ones + EmailInbox.FindSet(); + Assert.AreEqual(2, EmailInbox.Count(), 'Wrong number of emails in the inbox'); + + repeat + Assert.AreNotEqual(InitialId, EmailInbox.Id, 'The email should not be the same as the initial one'); + until EmailInbox.Next() = 0; + end; + + [Test] + procedure RetrieveEmailsFailv3() + var + EmailAccount: Record "Email Account"; + EmailInbox: Record "Email Inbox"; + TempFilters: Record "Email Retrieval Filters" temporary; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Retrieving emails with a V3 connector fails due to some error + PermissionsMock.Set('Email Edit'); + + // [Given] An email account with a V3 connector + // [Given] Existing emails in Email Inbox + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + + EmailInbox.DeleteAll(); + ConnectorMock.CreateEmailInbox(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox); + Assert.AreEqual(1, EmailInbox.Count(), 'Wrong number of emails in the inbox'); + + // [Given] An error occurs when retrieving emails + ConnectorMock.FailOnRetrieveEmails(true); + + // [When] Retrieving emails + asserterror Email.RetrieveEmails(EmailAccount."Account Id", EmailAccount.Connector, EmailInbox, TempFilters); // [Then] The EmailInbox will be filled only with new emails and not existing ones Assert.ExpectedError('Failed to retrieve emails'); @@ -1495,8 +1566,9 @@ codeunit 134685 "Email Test" asserterror Email.MarkAsRead(EmailAccount."Account Id", EmailAccount.Connector, Any.AlphabeticText(10)); Assert.ExpectedError('The selected email connector does not support marking emails as read'); end; - +#if not CLEAN26 [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure MarkEmailAsRead() var EmailAccount: Record "Email Account"; @@ -1514,6 +1586,7 @@ codeunit 134685 "Email Test" end; [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure MarkEmailAsReadFail() var EmailAccount: Record "Email Account"; @@ -1533,6 +1606,45 @@ codeunit 134685 "Email Test" asserterror Email.MarkAsRead(EmailAccount."Account Id", EmailAccount.Connector, Any.AlphabeticText(10)); Assert.ExpectedError('Failed to mark email as read'); end; +#endif + + [Test] + procedure MarkEmailAsReadv3() + var + EmailAccount: Record "Email Account"; + ConnectorMock: Codeunit "Connector Mock"; + Any: Codeunit Any; + begin + // [Scenario] Marking email as read with a V3 connector should succeed with no errors + // [Given] An email account with a V3 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + + // [When] Mark email as read + // [Then] No error occurs + Email.MarkAsRead(EmailAccount."Account Id", EmailAccount.Connector, Any.AlphabeticText(10)); + end; + + [Test] + procedure MarkEmailAsReadFailv3() + var + EmailAccount: Record "Email Account"; + ConnectorMock: Codeunit "Connector Mock"; + Any: Codeunit Any; + begin + // [Scenario] Marking email as read with a V3 connector fails due to some error + // [Given] An email account with a V3 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + + // [Given] Force an error to occur when marking email as read + ConnectorMock.FailOnMarkAsRead(true); + + // [When] Mark email as read + // [Then] An error occurs + asserterror Email.MarkAsRead(EmailAccount."Account Id", EmailAccount.Connector, Any.AlphabeticText(10)); + Assert.ExpectedError('Failed to mark email as read'); + end; [Test] procedure ReplyToEmailWithV1Connector() @@ -1540,7 +1652,6 @@ codeunit 134685 "Email Test" EmailAccount: Record "Email Account"; EmailMessage: Codeunit "Email Message"; ConnectorMock: Codeunit "Connector Mock"; - Any: Codeunit Any; begin // [Scenario] Replying to an email with a V1 connector should fail // [Given] An email account with a V1 connector @@ -1550,11 +1661,13 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] An error is thrown that the connector does not support this operation - asserterror Email.Reply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector); + asserterror Email.Reply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector); Assert.ExpectedError('The selected email connector does not support replying to emails'); end; +#if not CLEAN26 [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure ReplyToEmail() var EmailAccount: Record "Email Account"; @@ -1570,10 +1683,11 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - Assert.IsTrue(Email.Reply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); + Assert.IsTrue(Email.Reply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); end; [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure ReplyToEmailWithNoRecipients() var EmailAccount: Record "Email Account"; @@ -1589,7 +1703,44 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - asserterror Email.Reply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector); + asserterror Email.Reply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector); + Assert.ExpectedError('You must specify a valid email account to send the message to'); + end; +#endif + [Test] + procedure ReplyToEmailv3() + var + EmailAccount: Record "Email Account"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector should succeed with no errors + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReply(EmailMessage); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + Assert.IsTrue(Email.Reply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); + end; + + [Test] + procedure ReplyToEmailWithNoRecipientsv3() + var + EmailAccount: Record "Email Account"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector should succeed with no errors + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReply(EmailMessage, ''); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + asserterror Email.Reply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector); Assert.ExpectedError('You must specify a valid email account to send the message to'); end; @@ -1599,7 +1750,6 @@ codeunit 134685 "Email Test" EmailAccount: Record "Email Account"; EmailMessage: Codeunit "Email Message"; ConnectorMock: Codeunit "Connector Mock"; - Any: Codeunit Any; begin // [Scenario] Replying to an email with a V1 connector should fail // [Given] An email account with a V1 connector @@ -1609,11 +1759,13 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] An error is thrown that the connector does not support this operation - asserterror Email.ReplyAll(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector); + asserterror Email.ReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector); Assert.ExpectedError('The selected email connector does not support replying to emails'); end; +#if not CLEAN26 [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure ReplyAllToEmail() var EmailAccount: Record "Email Account"; @@ -1629,10 +1781,11 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - Assert.IsTrue(Email.ReplyAll(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); + Assert.IsTrue(Email.ReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); end; [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure ReplyAllToEmailFail() var EmailAccount: Record "Email Account"; @@ -1651,7 +1804,47 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - Assert.IsFalse(Email.ReplyAll(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector), 'Did succeed in replying the email when it should fail'); + Assert.IsFalse(Email.ReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did succeed in replying the email when it should fail'); + end; +#endif + + [Test] + procedure ReplyAllToEmailv3() + var + EmailAccount: Record "Email Account"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector should succeed with no errors + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReplyAll(EmailMessage); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + Assert.IsTrue(Email.ReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did not succeed in replying the email'); + end; + + [Test] + procedure ReplyAllToEmailFailv3() + var + EmailAccount: Record "Email Account"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector fails due to some error + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReplyAll(EmailMessage); + + // [Given] Force the connector to fail on reply + ConnectorMock.FailOnReply(true); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + Assert.IsFalse(Email.ReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector), 'Did succeed in replying the email when it should fail'); end; [Test] @@ -1661,7 +1854,6 @@ codeunit 134685 "Email Test" EmailOutbox: Record "Email Outbox"; EmailMessage: Codeunit "Email Message"; ConnectorMock: Codeunit "Connector Mock"; - Any: Codeunit Any; begin // [Scenario] Replying to an email with a V1 connector should fail // [Given] An email account with a V1 connector @@ -1671,11 +1863,12 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] An error is thrown that the connector does not support this operation - asserterror Email.EnqueueReply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + asserterror Email.EnqueueReply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); Assert.ExpectedError('The selected email connector does not support replying to emails'); end; - +#if not CLEAN26 [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure EnqueueReplyToEmail() var EmailAccount: Record "Email Account"; @@ -1693,12 +1886,13 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true Assert.IsTrue(IsNullGuid(EmailOutbox."Message Id"), 'The email message id in the outbox should be empty'); - Email.EnqueueReply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + Email.EnqueueReply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); Assert.AreEqual(EmailMessage.GetId(), EmailOutbox."Message Id", 'The email message id should be the same as the one in the outbox'); end; [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure EnqueueReplyToEmailWithNoRecipients() var EmailAccount: Record "Email Account"; @@ -1715,7 +1909,50 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - asserterror Email.EnqueueReply(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + asserterror Email.EnqueueReply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + Assert.ExpectedError('You must specify a valid email account to send the message to'); + end; +#endif + + [Test] + procedure EnqueueReplyToEmailv3() + var + EmailAccount: Record "Email Account"; + EmailOutbox: Record "Email Outbox"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector should succeed with no errors + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReply(EmailMessage); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + Assert.IsTrue(IsNullGuid(EmailOutbox."Message Id"), 'The email message id in the outbox should be empty'); + Email.EnqueueReply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + + Assert.AreEqual(EmailMessage.GetId(), EmailOutbox."Message Id", 'The email message id should be the same as the one in the outbox'); + end; + + [Test] + procedure EnqueueReplyToEmailWithNoRecipientsv3() + var + EmailAccount: Record "Email Account"; + EmailOutbox: Record "Email Outbox"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector should succeed with no errors + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReply(EmailMessage, ''); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + asserterror Email.EnqueueReply(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); Assert.ExpectedError('You must specify a valid email account to send the message to'); end; @@ -1726,7 +1963,6 @@ codeunit 134685 "Email Test" EmailOutbox: Record "Email Outbox"; EmailMessage: Codeunit "Email Message"; ConnectorMock: Codeunit "Connector Mock"; - Any: Codeunit Any; begin // [Scenario] Replying to an email with a V1 connector should fail // [Given] An email account with a V1 connector @@ -1736,18 +1972,19 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] An error is thrown that the connector does not support this operation - asserterror Email.EnqueueReplyAll(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + asserterror Email.EnqueueReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); Assert.ExpectedError('The selected email connector does not support replying to emails'); end; +#if not CLEAN26 [Test] + [Obsolete('v2 connector is replaced by v3 connector.', '26.0')] procedure EnqueueReplyAllToEmailFail() var EmailAccount: Record "Email Account"; EmailOutbox: Record "Email Outbox"; EmailMessage: Codeunit "Email Message"; ConnectorMock: Codeunit "Connector Mock"; - Any: Codeunit Any; begin // [Scenario] Replying to an email with a V2 connector fails due to some error // [Given] An email account with a V2 connector @@ -1760,7 +1997,30 @@ codeunit 134685 "Email Test" // [When] Reply to email // [Then] No error occurs and reply returns true - Email.EnqueueReplyAll(EmailMessage, Any.AlphabeticText(10), EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + Email.EnqueueReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); + end; +#endif + + [Test] + procedure EnqueueReplyAllToEmailFailv3() + var + EmailAccount: Record "Email Account"; + EmailOutbox: Record "Email Outbox"; + EmailMessage: Codeunit "Email Message"; + ConnectorMock: Codeunit "Connector Mock"; + begin + // [Scenario] Replying to an email with a V2 connector fails due to some error + // [Given] An email account with a V2 connector + ConnectorMock.Initialize(); + ConnectorMock.AddAccount(EmailAccount, Enum::"Email Connector"::"Test Email Connector v3"); + CreateEmailReplyAll(EmailMessage); + + // [Given] Force the connector to fail on reply + ConnectorMock.FailOnReply(true); + + // [When] Reply to email + // [Then] No error occurs and reply returns true + Email.EnqueueReplyAll(EmailMessage, EmailAccount."Account Id", EmailAccount.Connector, EmailOutbox); end; local procedure CreateEmail(var EmailMessage: Codeunit "Email Message")