From 7584061f22e0c24186249fba57234ee15f00862b Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Wed, 3 May 2023 15:17:15 +0200 Subject: [PATCH 01/33] Initial commit --- .../app/ExtensionLogo.png | Bin 0 -> 4681 bytes .../app/README.md | 0 .../app/app.json | 46 + .../app/src/BlobStorageAccount.Page.al | 84 + .../app/src/BlobStorageAccount.Table.al | 76 + .../app/src/BlobStorageAccountWizard.Page.al | 153 ++ .../app/src/BlobStorageConnector.EnumExt.al | 19 + .../src/BlobStorageConnectorImpl.Codeunit.al | 231 +++ Modules/System/File Access/README.md | 1751 +++++++++++++++++ Modules/System/File Access/app.json | 40 + .../src/Account/FileAccount.Codeunit.al | 43 + .../src/Account/FileAccount.Table.al | 66 + .../src/Account/FileAccountImpl.Codeunit.al | 228 +++ .../src/Account/FileAccountWizard.Page.al | 522 +++++ .../src/Account/FileAccounts.Page.al | 310 +++ .../src/Connector/FileConnector.Enum.al | 12 + .../src/Connector/FileConnector.Interface.al | 126 ++ .../src/Connector/FileConnector.Table.al | 37 + .../src/Connector/FileConnectorLogo.Table.al | 33 + .../src/Scenario/FileAccountScenario.Table.al | 72 + .../src/Scenario/FileScenario.Codeunit.al | 74 + .../src/Scenario/FileScenario.Enum.al | 22 + .../src/Scenario/FileScenario.Table.al | 40 + .../src/Scenario/FileScenarioImpl.Codeunit.al | 325 +++ .../src/Scenario/FileScenarioSetup.Page.al | 194 ++ .../src/Scenario/FileScenariosFactBox.Page.al | 38 + .../Scenario/FileScenariosForAccount.Page.al | 63 + 27 files changed, 4605 insertions(+) create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/ExtensionLogo.png create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/README.md create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/app.json create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al create mode 100644 Modules/System/File Access/README.md create mode 100644 Modules/System/File Access/app.json create mode 100644 Modules/System/File Access/src/Account/FileAccount.Codeunit.al create mode 100644 Modules/System/File Access/src/Account/FileAccount.Table.al create mode 100644 Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al create mode 100644 Modules/System/File Access/src/Account/FileAccountWizard.Page.al create mode 100644 Modules/System/File Access/src/Account/FileAccounts.Page.al create mode 100644 Modules/System/File Access/src/Connector/FileConnector.Enum.al create mode 100644 Modules/System/File Access/src/Connector/FileConnector.Interface.al create mode 100644 Modules/System/File Access/src/Connector/FileConnector.Table.al create mode 100644 Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al create mode 100644 Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenario.Enum.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenario.Table.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al create mode 100644 Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/ExtensionLogo.png b/Apps/W1/File - Azure BLOB Storage Connector/app/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..30941b354fa335cad3ea5426ac24cadb2ee328e5 GIT binary patch literal 4681 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 literal 0 HcmV?d00001 diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/README.md b/Apps/W1/File - Azure BLOB Storage Connector/app/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json new file mode 100644 index 0000000000..0899c085e0 --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -0,0 +1,46 @@ +{ + "id": "c9ce86fe-cb70-4b79-be03-d21856b1a4ca", + "name": "File - Azure BLOB Storage Connector", + "publisher": "Microsoft", + "brief": "Enable all users to use a Azure Blob Storage to save files from Business Central.", + "description": "This app enables all users to store files in the smae azure blob storage in Business Central.", + "version": "22.1.0.0", + "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", + "help": "https://go.microsoft.com/fwlink/?linkid=2134520", + "url": "https://go.microsoft.com/fwlink/?linkid=724011", + "logo": "ExtensionLogo.png", + "application": "22.0.0.0", + "internalsVisibleTo": [ + + ], + "dependencies": [ + { + "id": "c9c54414-80c3-4cc9-98c6-589158882774", + "name": "File Access", + "publisher": "Microsoft", + "version": "22.1.0.0" + } + ], + "screenshots": [ + + ], + "platform": "22.0.0.0", + "idRanges": [ + { + "from": 80100, + "to": 80199 + }, + { + "from": 2147483647, + "to": 2147483647 + } + ], + "target": "OnPrem", + "resourceExposurePolicy": { + "allowDebugging": false, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" +} \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al new file mode 100644 index 0000000000..0f76a52ace --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al @@ -0,0 +1,84 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Displays an account that was registered via the Blob Storage connector. +/// +page 80100 "Blob Storage Account" +{ + SourceTable = "Blob Storage Account"; + Caption = 'Azure Blob Storage Account'; + Permissions = tabledata "Blob Storage Account" = rimd; + PageType = Card; + Extensible = false; + InsertAllowed = false; + DataCaptionExpression = Rec.Name; + + layout + { + area(Content) + { + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the Storage account connection.'; + ShowMandatory = true; + NotBlank = true; + } + + field(StorageAccountNameField; Rec."Storage Account Name") + { + ApplicationArea = All; + Caption = 'Storage Account Name'; + ToolTip = 'Specifies the Azure Storage name.'; + } + + field(Password; Password) + { + ApplicationArea = All; + Caption = 'Password'; + Editable = PasswordEditable; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the shared key to access the Storage Blob.'; + + trigger OnValidate() + begin + Rec.SetPassword(Password); + end; + } + + field(ContainerNameField; Rec."Container Name") + { + ApplicationArea = All; + Caption = 'Container Name'; + ToolTip = 'Specifies the Azure Storage Container name.'; + } + } + } + + actions + { + area(processing) + { + //TODO Add File Browser + } + } + + var + [InDataSet] + PasswordEditable: Boolean; + [NonDebuggable] + [InDataSet] + Password: Text; + + trigger OnOpenPage() + begin + Rec.SetCurrentKey(Name); + + if not IsNullGuid(Rec."Password Key") then + Password := '***'; + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al new file mode 100644 index 0000000000..4dbae1c33e --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al @@ -0,0 +1,76 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Holds the information for all file accounts that are registered via the Blob Storage connector +/// +table 80100 "Blob Storage Account" +{ + Access = Internal; + + Caption = 'Azure Blob Storage Account'; + + fields + { + field(1; "Id"; Guid) + { + DataClassification = SystemMetadata; + Caption = 'Primary Key'; + } + + field(2; Name; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Name of account'; + } + field(3; "Storage Account Name"; Text[50]) + { + Caption = 'Storage Account Name'; + } + field(4; "Container Name"; Text[50]) + { + Caption = 'Container Name'; + } + field(8; "Password Key"; Guid) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Id) + { + Clustered = true; + } + } + + var + UnableToGetPasswordMsg: Label 'Unable to get Blob Storage Account Key'; + UnableToSetPasswordMsg: Label 'Unable to set Blob Storage Account Key'; + + trigger OnDelete() + begin + if not IsNullGuid(Rec."Password Key") then + if IsolatedStorage.Delete(Rec."Password Key") then; + end; + + [NonDebuggable] + procedure SetPassword(Password: Text) + begin + if IsNullGuid(Rec."Password Key") then + Rec."Password Key" := CreateGuid(); + + if not IsolatedStorage.Set(Format(Rec."Password Key"), Password, DataScope::Company) then + Error(UnableToSetPasswordMsg); + end; + + [NonDebuggable] + procedure GetPassword(PasswordKey: Guid) Password: Text + begin + if not IsolatedStorage.Get(Format(PasswordKey), DataScope::Company, Password) then + Error(UnableToGetPasswordMsg); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al new file mode 100644 index 0000000000..4d3644808f --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al @@ -0,0 +1,153 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Displays an account that is being registered via the Blob Storage connector. +/// +page 80101 "Blob Storage Account Wizard" +{ + Caption = 'Setup Azure Blob Storage Account'; + SourceTable = "Blob Storage Account"; + SourceTableTemporary = true; + Permissions = tabledata "Blob Storage Account" = rimd; + PageType = NavigatePage; + Extensible = false; + Editable = true; + + layout + { + area(Content) + { + group(TopBanner) + { + Editable = false; + ShowCaption = false; + Visible = TopBannerVisible; + field(NotDoneIcon; MediaResources."Media Reference") + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + ToolTip = ' '; + Caption = ' '; + } + } + + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the Azure Blob Storage account.'; + ShowMandatory = true; + NotBlank = true; + + trigger OnValidate() + begin + IsNextEnabled := BlobStorageConnectorImpl.IsAccountValid(Rec); + end; + } + + field(StorageAccountNameField; Rec."Storage Account Name") + { + ApplicationArea = All; + Caption = 'Storage Account Name'; + ToolTip = 'Specifies the Azure Storage name.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := BlobStorageConnectorImpl.IsAccountValid(Rec); + end; + } + + field(PasswordField; Password) + { + ApplicationArea = All; + Caption = 'Password'; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the shared key of the Storage Blob.'; + ShowMandatory = true; + } + + field(ContainerNameField; Rec."Container Name") + { + ApplicationArea = All; + Caption = 'Container Name'; + ToolTip = 'Specifies the container to use of the Storage Blob.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := BlobStorageConnectorImpl.IsAccountValid(Rec); + end; + } + } + } + + actions + { + area(processing) + { + action(Back) + { + ApplicationArea = All; + Caption = 'Back'; + ToolTip = 'Back'; + Image = Cancel; + InFooterBar = true; + + trigger OnAction() + begin + CurrPage.Close(); + end; + } + + action(Next) + { + ApplicationArea = All; + Caption = 'Next'; + Image = NextRecord; + Enabled = IsNextEnabled; + InFooterBar = true; + ToolTip = 'Next'; + + trigger OnAction() + begin + BlobStorageConnectorImpl.CreateAccount(Rec, Password, BlobStorageAccount); + CurrPage.Close(); + end; + } + } + } + + var + BlobStorageAccount: Record "File Account"; + MediaResources: Record "Media Resources"; + BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; + [NonDebuggable] + [InDataSet] + Password: Text; + IsNextEnabled: Boolean; + TopBannerVisible: Boolean; + + trigger OnOpenPage() + begin + Rec.Init(); + Rec.Insert(); + + if MediaResources.Get('ASSISTEDSETUP-NOTEXT-400PX.PNG') and (CurrentClientType() = ClientType::Web) then + TopBannerVisible := MediaResources."Media Reference".HasValue(); + end; + + internal procedure GetAccount(var FileAccount: Record "File Account"): Boolean + begin + if IsNullGuid(BlobStorageAccount."Account Id") then + exit(false); + + FileAccount := BlobStorageAccount; + + exit(true); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al new file mode 100644 index 0000000000..af0e30b992 --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Enum extension to register the Blob Storage connector. +/// +enumextension 80100 "Blob Storage Connector" extends "File Connector" +{ + /// + /// The Blob Storage connector. + /// + value(2147483647; "Blob Storage") // Max int value so it appears last //FIXME Id from Module + { + Caption = 'Blob Storage'; + Implementation = "File Connector" = "Blob Storage Connector Impl."; + } +} \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al new file mode 100644 index 0000000000..001f2179dc --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -0,0 +1,231 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" +{ + Access = Internal; + Permissions = tabledata "Blob Storage Account" = rimd; + + var + ConnectorDescriptionTxt: Label 'Use Azure Blob Storage to store and retrieve files.'; + + NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; + ConnectorBase64LogoTxt: Label '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', Locked = true; + + /// + /// Gets a List of Files stored on the provided account. + /// + /// The file path to list. + /// The file account ID which is used to get the file. + /// A list with all files stored in the path. + procedure ListFiles(Path: Text; AccountId: Guid; var Files: List of [Text]) + begin + + end; + + /// + /// Gets a file from the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to get the file. + /// The Stream were the file is read to. + procedure GetFile(Path: Text; AccountId: Guid; Stream: InStream) + begin + + end; + + /// + /// Gets a file to the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + /// The Stream were the file is read from. + procedure SetFile(Path: Text; AccountId: Guid; Stream: OutStream) + begin + + end; + + /// + /// Checks if a file exists on the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + /// Returns true if the file exists + procedure FileExists(Path: Text; AccountId: Guid): Boolean + begin + + end; + + /// + /// Deletes a file exists on the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + procedure DeleteFile(Path: Text; AccountId: Guid) + begin + + end; + + /// + /// Gets a List of Directories stored on the provided account. + /// + /// The file path to list. + /// The file account ID which is used to get the file. + /// A list with all directories stored in the path. + procedure ListDirectories(Path: Text; AccountId: Guid; var Directiories: List of [Text]) + begin + + end; + + /// + /// Creates a directory on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + procedure CreateDirectory(Path: Text; AccountId: Guid) + begin + + end; + + /// + /// Checks if a directory exists on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + /// Returns true if the directory exists + procedure DirectoryExists(Path: Text; AccountId: Guid): Boolean + begin + + end; + + /// + /// Deletes a directory exists on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + procedure DeleteDirectory(Path: Text; AccountId: Guid) + begin + + end; + + /// + /// Returns the path separator of the file account. + /// + /// The Path separator like / or \ + procedure PathSeparator(): Text + begin + exit('/'); + end; + + /// + /// Gets the registered accounts for the Blob Storage connector. + /// + /// Out parameter holding all the registered accounts for the Blob Storage connector. + procedure GetAccounts(var Accounts: Record "File Account") + var + Account: Record "Blob Storage Account"; + begin + if Account.FindSet() then + repeat + Accounts."Account Id" := Account.Id; + Accounts.Name := Account.Name; + Accounts.Connector := Enum::"File Connector"::"Blob Storage"; + Accounts.Insert(); + until Account.Next() = 0; + end; + + /// + /// Shows accounts information. + /// + /// The ID of the account to show. + procedure ShowAccountInformation(AccountId: Guid) + var + BlobStorageAccountLocal: Record "Blob Storage Account"; + begin + if not BlobStorageAccountLocal.Get(AccountId) then + Error(NotRegisteredAccountErr); + + BlobStorageAccountLocal.SetRecFilter(); + Page.Run(Page::"Blob Storage Account", BlobStorageAccountLocal); + end; + + /// + /// Register an file account for the Blob Storage connector. + /// + /// Out parameter holding details of the registered account. + /// True if the registration was successful; false - otherwise. + procedure RegisterAccount(var Account: Record "File Account"): Boolean + var + BlobStorageAccountWizard: Page "Blob Storage Account Wizard"; + begin + BlobStorageAccountWizard.RunModal(); + + exit(BlobStorageAccountWizard.GetAccount(Account)); + end; + + /// + /// Deletes an file account for the Blob Storage connector. + /// + /// The ID of the Blob Storage account + /// True if an account was deleted. + procedure DeleteAccount(AccountId: Guid): Boolean + var + BlobStorageAccountLocal: Record "Blob Storage Account"; + begin + if BlobStorageAccountLocal.Get(AccountId) then + exit(BlobStorageAccountLocal.Delete()); + + exit(false); + end; + + /// + /// Gets a description of the Blob Storage connector. + /// + /// A short description of the Blob Storage connector. + procedure GetDescription(): Text[250] + begin + exit(ConnectorDescriptionTxt); + end; + + /// + /// Gets the Blob Storage connector logo. + /// + /// A base64-formatted image to be used as logo. + procedure GetLogoAsBase64(): Text + begin + exit(ConnectorBase64LogoTxt); + end; + + internal procedure IsAccountValid(var Account: Record "Blob Storage Account" temporary): Boolean + begin + if Account.Name = '' then + exit(false); + + if Account."Storage Account Name" = '' then + exit(false); + + if Account."Container Name" = '' then + exit(false); + + exit(true); + end; + + [NonDebuggable] + internal procedure CreateAccount(var AccountToCopy: Record "Blob Storage Account"; Password: Text; var FileAccount: Record "File Account") + var + NewBlobStorageAccount: Record "Blob Storage Account"; + begin + NewBlobStorageAccount.TransferFields(AccountToCopy); + + NewBlobStorageAccount.Id := CreateGuid(); + NewBlobStorageAccount.SetPassword(Password); + + NewBlobStorageAccount.Insert(); + + FileAccount."Account Id" := NewBlobStorageAccount.Id; + FileAccount.Name := NewBlobStorageAccount.Name; + FileAccount.Connector := Enum::"File Connector"::"Blob Storage"; + end; +} \ No newline at end of file diff --git a/Modules/System/File Access/README.md b/Modules/System/File Access/README.md new file mode 100644 index 0000000000..5e7e0b886f --- /dev/null +++ b/Modules/System/File Access/README.md @@ -0,0 +1,1751 @@ +Provides an API that lets you connect email accounts to Business Central so that people can send messages without having to open their email application. The email module consists of the following main entities: + +### Email Account +An email account holds the information needed to send emails from Business Central. + +### Email Address Lookup +Email address lookup suggests email addresses to the user for the To, Cc and Bcc fields, and the suggestions are based on the related records of the email. + +### Email Connector +An email connector is an interface for creating and managing email accounts, and sending emails. Every email account belongs to an email connector. + +### Email Scenario +Email scenarios are specific business processes that involve documents or notifications. Use scenarios to seamlessly integrate email accounts with business processes. + +### Email Message +Payload for every email that is being composed or already has been sent. + +### Email Outbox +Holds draft emails, and emails that were not successfully sent. + +### Sent Email +Holds emails that have been sent. + +# Public Objects +## Email Account (Table 8902) + + A common representation of an email account. + + + +## Email Outbox (Table 8888) +Holds information about draft emails and email that are about to be sent. + + +## Sent Email (Table 8889) +Holds information about the sent emails. + +### GetMessageId (Method) + + Get the message id of the sent email. + + +#### Syntax +``` +procedure GetMessageId(): Guid +``` +#### Return Value +*[Guid](https://go.microsoft.com/fwlink/?linkid=2210122)* + +Message id. + +## Email Related Attachment (Table 8910) + + Temporary table that holds information about attachments related to an email. + + + +## Email Connector (Interface) + + An e-mail connector interface used to creating e-mail accounts and sending an e-mail. + + +### Send (Method) + + Sends an e-mail using the provided account. + + +#### Syntax +``` +procedure Send(EmailMessage: Codeunit "Email Message"; AccountId: Guid) +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message that is to be sent out. + +*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The email account ID which is used to send out the email. + +### GetAccounts (Method) + + Gets the e-mail accounts registered for the connector. + + +#### Syntax +``` +procedure GetAccounts(var Accounts: Record "Email Account") +``` +#### Parameters +*Accounts ([Record "Email Account"]())* + +Out variable that holds the registered e-mail accounts for the connector. + +### ShowAccountInformation (Method) + + Shows the information for an e-mail account. + + +#### Syntax +``` +procedure ShowAccountInformation(AccountId: Guid) +``` +#### Parameters +*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the e-mail account + +### RegisterAccount (Method) + + Registers an e-mail account for the connector. + + +The out parameter must hold the account ID of the added account. + +#### Syntax +``` +procedure RegisterAccount(var Account: Record "Email Account"): Boolean +``` +#### Parameters +*Account ([Record "Email Account"]())* + +Out parameter with the details of the registered Account. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if an account was registered. +### DeleteAccount (Method) + + Deletes an e-mail account for the connector. + + +#### Syntax +``` +procedure DeleteAccount(AccountId: Guid): Boolean +``` +#### Parameters +*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the e-mail account + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if an account was deleted. +### GetLogoAsBase64 (Method) + + Provides a custom logo for the connector that shows in the Setup Email Account Guide. + + +The recomended image size is 128x128. + +#### Syntax +``` +procedure GetLogoAsBase64(): Text +``` +#### Return Value +*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* + +Base64 encoded image. +### GetDescription (Method) + + Provides a more detailed description of the connector. + + +#### Syntax +``` +procedure GetDescription(): Text[250] +``` +#### Return Value +*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* + +A more detailed desctiption of the connector. + +## Email Account (Codeunit 8894) + + Provides functionality to work with email accounts. + + +### GetAllAccounts (Method) + + Gets all of the email accounts registered in Business Central. + + +#### Syntax +``` +procedure GetAllAccounts(LoadLogos: Boolean; var Accounts: Record "Email Account" temporary) +``` +#### Parameters +*LoadLogos ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Flag, used to determine whether to load the logos for the accounts. + +*Accounts ([Record "Email Account" temporary]())* + +Out parameter holding the email accounts. + +### GetAllAccounts (Method) + + Gets all of the email accounts registered in Business Central. + + +#### Syntax +``` +procedure GetAllAccounts(var Accounts: Record "Email Account" temporary) +``` +#### Parameters +*Accounts ([Record "Email Account" temporary]())* + +Out parameter holding the email accounts. + +### IsAnyAccountRegistered (Method) + + Checks if there is at least one email account registered in Business Central. + + +#### Syntax +``` +procedure IsAnyAccountRegistered(): Boolean +``` +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if there is any account registered in the system, otherwise - false. +### ValidateEmailAddress (Method) +The email address "%1" is not valid. + + + Validates an email address and throws an error if it is invalid. + + +If the provided email address is an empty string, the function will do nothing. + +#### Syntax +``` +[TryFunction] +procedure ValidateEmailAddress(EmailAddress: Text) +``` +#### Parameters +*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The email address to validate. + +### ValidateEmailAddress (Method) +The email address "%1" is not valid. + + + Validates an email address and throws an error if it is invalid. + + +#### Syntax +``` +[TryFunction] +procedure ValidateEmailAddress(EmailAddress: Text; AllowEmptyValue: Boolean) +``` +#### Parameters +*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The email address to validate. + +*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Indicates whether to skip the validation if the provided email address is empty. + +### ValidateEmailAddresses (Method) +The email address "%1" is not valid. + + + Validates email addresses and displays an error if any are invalid. + + +If the provided email address is an empty string, the function will do nothing. + +#### Syntax +``` +[TryFunction] +procedure ValidateEmailAddresses(EmailAddresses: Text) +``` +#### Parameters +*EmailAddresses ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The email addresses to validate, separated by semicolons. + +### ValidateEmailAddresses (Method) +The email address "%1" is not valid. + + + Validates email addresses and displays an error if any are invalid. + + +#### Syntax +``` +[TryFunction] +procedure ValidateEmailAddresses(EmailAddresses: Text; AllowEmptyValue: Boolean) +``` +#### Parameters +*EmailAddresses ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The email addresses to validate, separated by semicolons. + +*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Indicates whether to skip the validation if no email address is provided. + +### OnAfterValidateEmailAddress (Event) +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnAfterValidateEmailAddress(EmailAddress: Text; AllowEmptyValue: Boolean) +``` +#### Parameters +*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + + + +*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + + + + +## Email (Codeunit 8901) + + Provides functionality to create and send emails. + + +### SaveAsDraft (Method) + + Saves a draft email in the Outbox. + + +#### Syntax +``` +procedure SaveAsDraft(EmailMessage: Codeunit "Email Message") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to save. + +### SaveAsDraft (Method) + + Saves a draft email in the Outbox. + + +#### Syntax +``` +procedure SaveAsDraft(EmailMessage: Codeunit "Email Message"; var EmailOutbox: Record "Email Outbox") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to save. + +*EmailOutbox ([Record "Email Outbox"]())* + +The created outbox entry. + +### Enqueue (Method) + + Enqueues an email to be sent in the background. + + +The default account will be used for sending the email. + +#### Syntax +``` +procedure Enqueue(EmailMessage: Codeunit "Email Message") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +### Enqueue (Method) + + Enqueues an email to be sent in the background. + + +#### Syntax +``` +procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailScenario ([Enum "Email Scenario"]())* + +The scenario to use in order to determine the email account to use for sending the email. + +### Enqueue (Method) + + Enqueues an email to be sent in the background. + + +Both "Account Id" and Connector fields need to be set on the parameter. + +#### Syntax +``` +procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary) +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccount ([Record "Email Account" temporary]())* + +The email account to use for sending the email. + +### Enqueue (Method) + + Enqueues an email to be sent in the background. + + +#### Syntax +``` +procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email account to use for sending the email. + +*EmailConnector ([Enum "Email Connector"]())* + +The email connector to use for sending the email. + +### Send (Method) +The email message has already been queued. + + + Sends the email in the current session. + + +The default account will be used for sending the email. + +#### Syntax +``` +procedure Send(EmailMessage: Codeunit "Email Message"): Boolean +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email was successfully sent; otherwise - false. +### Send (Method) +The email message has already been queued. + + + Sends the email in the current session. + + +#### Syntax +``` +procedure Send(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario"): Boolean +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailScenario ([Enum "Email Scenario"]())* + +The scenario to use in order to determine the email account to use for sending the email. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email was successfully sent; otherwise - false. +### Send (Method) +The email message has already been queued. + + + Sends the email in the current session. + + +Both "Account Id" and Connector fields need to be set on the parameter. + +#### Syntax +``` +procedure Send(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary): Boolean +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccount ([Record "Email Account" temporary]())* + +The email account to use for sending the email. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email was successfully sent; otherwise - false +### Send (Method) +The email message has already been queued. + + + Sends the email in the current session. + + +#### Syntax +``` +procedure Send(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email account to use for sending the email. + +*EmailConnector ([Enum "Email Connector"]())* + +The email connector to use for sending the email. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email was successfully sent; otherwise - false +### OpenInEditor (Method) + + Opens an email message in "Email Editor" page. + + +#### Syntax +``` +procedure OpenInEditor(EmailMessage: Codeunit "Email Message") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +### OpenInEditor (Method) + + Opens an email message in "Email Editor" page. + + +#### Syntax +``` +procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailScenario ([Enum "Email Scenario"]())* + +The scenario to use in order to determine the email account to use on the page. + +### OpenInEditor (Method) + + Opens an email message in "Email Editor" page. + + +Both "Account Id" and Connector fields need to be set on the parameter. + +#### Syntax +``` +procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary) +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccount ([Record "Email Account" temporary]())* + +The email account to fill in. + +### OpenInEditor (Method) + + Opens an email message in "Email Editor" page. + + +#### Syntax +``` +procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email account to use on the page. + +*EmailConnector ([Enum "Email Connector"]())* + +The email connector to use on the page. + +### OpenInEditorModally (Method) + + Opens an email message in "Email Editor" page modally. + + +#### Syntax +``` +procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"): Enum "Email Action" +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +#### Return Value +*[Enum "Email Action"]()* + +The action that the user performed with the email message. +### OpenInEditorModally (Method) + + Opens an email message in "Email Editor" page modally. + + +#### Syntax +``` +procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario"): Enum "Email Action" +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailScenario ([Enum "Email Scenario"]())* + +The scenario to use in order to determine the email account to use on the page. + +#### Return Value +*[Enum "Email Action"]()* + +The action that the user performed with the email message. +### OpenInEditorModally (Method) + + Opens an email message in "Email Editor" page modally. + + +Both "Account Id" and Connector fields need to be set on the parameter. + +#### Syntax +``` +procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary): Enum "Email Action" +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccount ([Record "Email Account" temporary]())* + +The email account to fill in. + +#### Return Value +*[Enum "Email Action"]()* + +The action that the user performed with the email message. +### OpenInEditorModally (Method) + + Opens an email message in "Email Editor" page modally. + + +#### Syntax +``` +procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Enum "Email Action" +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message to use as payload. + +*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email account to use on the page. + +*EmailConnector ([Enum "Email Connector"]())* + +The email connector to use on the page. + +#### Return Value +*[Enum "Email Action"]()* + +The action that the user performed with the email message. +### GetSentEmailsForRecord (Method) + + Gets the sent emails related to a record. + + +#### Syntax +``` +[Obsolete('Use GetSentEmailsForRecord(TableId: Integer; SystemId: Guid; var ResultEmailOutbox: Record "Email Outbox" temporary) instead.','19.0')] +procedure GetSentEmailsForRecord(TableId: Integer; SystemId: Guid)ResultSentEmails: Record "Sent Email" temporary +``` +#### Parameters +*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table ID of the record. + +*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the record. + +#### Return Value +*[Record "Sent Email" temporary]()* + +The sent email related to a record. +### GetSentEmailsForRecord (Method) + + Gets the sent emails related to a record. + + +#### Syntax +``` +procedure GetSentEmailsForRecord(TableId: Integer; SystemId: Guid; var ResultSentEmails: Record "Sent Email" temporary) +``` +#### Parameters +*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table ID of the record. + +*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the record. + +*ResultSentEmails ([Record "Sent Email" temporary]())* + + + +### GetSentEmailsForRecord (Method) + + Gets the sent emails related to a record. + + +#### Syntax +``` +procedure GetSentEmailsForRecord(RecordVariant: Variant; var ResultSentEmails: Record "Sent Email" temporary) +``` +#### Parameters +*RecordVariant ([Variant](https://go.microsoft.com/fwlink/?linkid=2210243))* + +Source Record. + +*ResultSentEmails ([Record "Sent Email" temporary]())* + +The sent email related to a record. + +### GetEmailOutboxForRecord (Method) + + Gets the outbox emails related to a record. + + +#### Syntax +``` +procedure GetEmailOutboxForRecord(RecordVariant: Variant; var ResultEmailOutbox: Record "Email Outbox" temporary) +``` +#### Parameters +*RecordVariant ([Variant](https://go.microsoft.com/fwlink/?linkid=2210243))* + +Source Record. + +*ResultEmailOutbox ([Record "Email Outbox" temporary]())* + +The outbox emails related to a record. + +### OpenSentEmails (Method) + + Open the sent emails page for a source record given by its table ID and system ID. + + +#### Syntax +``` +procedure OpenSentEmails(TableId: Integer; SystemId: Guid) +``` +#### Parameters +*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table ID of the record. + +*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the record. + +### GetOutboxEmailRecordStatus (Method) + + Gets the outbox email status. + + +#### Syntax +``` +procedure GetOutboxEmailRecordStatus(MessageId: Guid)ResultStatus: Enum "Email Status" +``` +#### Parameters +*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The MessageId of the record. + +#### Return Value +*[Enum "Email Status"]()* + +Email Status of the record. +### AddRelation (Method) + + Adds a relation between an email message and a record. + + +#### Syntax +``` +procedure AddRelation(EmailMessage: Codeunit "Email Message"; TableId: Integer; SystemId: Guid; RelationType: Enum "Email Relation Type") +``` +#### Parameters +*EmailMessage ([Codeunit "Email Message"]())* + +The email message for which to create the relation. + +*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table ID of the record. + +*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the record. + +*RelationType ([Enum "Email Relation Type"]())* + +The relation type to set. + +### OnGetTestEmailBody (Event) + + Integration event to override the default email body for test messages. + + +#### Syntax +``` +[Obsolete('The event will be removed. Subscribe to OnGetBodyForTestEmail instead', '17.3')] +[IntegrationEvent(false, false)] +procedure OnGetTestEmailBody(Connector: Enum "Email Connector"; var Body: Text) +``` +#### Parameters +*Connector ([Enum "Email Connector"]())* + +The connector used to send the email message. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +Out param to set the email body to a new value. + +### OnShowSource (Event) + + Integration event to show an email source record. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnShowSource(SourceTableId: Integer; SourceSystemId: Guid; var IsHandled: Boolean) +``` +#### Parameters +*SourceTableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + + + +*SourceSystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the source record. + +*IsHandled ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Out parameter to set if the event was handled. + +### OnGetBodyForTestEmail (Event) + + Integration event to override the default email body for test messages. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnGetBodyForTestEmail(Connector: Enum "Email Connector"; AccountId: Guid; var Body: Text) +``` +#### Parameters +*Connector ([Enum "Email Connector"]())* + +The connector used to send the email message. + +*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The account ID of the email account used to send the email message. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031 + +Out param to set the email body to a new value. + +### OnAfterSendEmail (Event) + + Integration event that notifies senders about whether their email was successfully sent in the background. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnAfterSendEmail(MessageId: Guid; Status: Boolean) +``` +#### Parameters +*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email in the queue. + +*Status ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +True if the message was successfully sent. + +### OnFindRelatedAttachments (Event) + + Integration event to get the names and IDs of attachments related to a source record. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnFindRelatedAttachments(SourceTableId: Integer; SourceSystemID: Guid; var EmailRelatedAttachments: Record "Email Related Attachment") +``` +#### Parameters +*SourceTableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table number of the source record. + +*SourceSystemID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the source record. + +*EmailRelatedAttachments ([Record "Email Related Attachment"]())* + +Out parameter to return attachments related to the source record. + +### OnGetAttachment (Event) + + Integration event that requests an attachment to be added to an email. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnGetAttachment(AttachmentTableID: Integer; AttachmentSystemID: Guid; MessageID: Guid) +``` +#### Parameters +*AttachmentTableID ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* + +The table number of the attachment. + +*AttachmentSystemID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The system ID of the attachment. + +*MessageID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email to add an attachment to. + +### OnEnqueuedInOutbox (Event) + + Integration event to implement additional validation after the email message has been enqueued in the email outbox. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnEnqueuedInOutbox(MessageId: Guid) +``` +#### Parameters +*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email that has been queued + + +## Email Message (Codeunit 8904) + + Codeunit to create and manage email messages. + + +### Create (Method) + + Creates the email with recipients, subject, and body. + + +#### Syntax +``` +procedure Create(ToRecipients: Text; Subject: Text; Body: Text) +``` +#### Parameters +*ToRecipients ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The recipient(s) of the email. A string containing the email addresses of the recipients separated by semicolon. + +*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The subject of the email. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +Raw text that will be used as body of the email. + +### Create (Method) + + Creates the email with recipients, subject, and body. + + +#### Syntax +``` +procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean) +``` +#### Parameters +*ToRecipients ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The recipient(s) of the email. A string containing the email addresses of the recipients separated by semicolon. + +*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The subject of the email. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The body of the email. + +*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Whether the body is HTML formatted. + +### Create (Method) + + Creates the email with recipients, subject, and body. + + +#### Syntax +``` +procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean) +``` +#### Parameters +*ToRecipients ([List of [Text]]())* + +The recipient(s) of the email. A list of email addresses the email will be send directly to. + +*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The subject of the email. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The body of the email + +*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Whether the body is HTML formatted + +### Create (Method) + + Creates the email with recipients, subject, and body. + + +#### Syntax +``` +procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) +``` +#### Parameters +*ToRecipients ([List of [Text]]())* + +The recipient(s) of the email. A list of email addresses the email will be send directly to. + +*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The subject of the email. + +*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The body of the email. + +*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Whether the body is HTML formatted. + +*CCRecipients ([List of [Text]]())* + +The CC recipient(s) of the email. A list of email addresses that will be listed as CC. + +*BCCRecipients ([List of [Text]]())* + +TThe BCC recipient(s) of the email. A list of email addresses that will be listed as BCC. + +### Get (Method) + + Gets the email message with the given ID. + + +#### Syntax +``` +procedure Get(MessageId: Guid): Boolean +``` +#### Parameters +*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +The ID of the email message to get. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email was found; otherwise - false. +### GetBody (Method) + + Gets the body of the email message. + + +#### Syntax +``` +procedure GetBody(): Text +``` +#### Return Value +*[Text](https://go.microsoft.com/fwlink/?linkid=2210031 + +The body of the email. +### GetSubject (Method) + + Gets the subject of the email message. + + +#### Syntax +``` +procedure GetSubject(): Text[2048] +``` +#### Return Value +*[Text[2048]](https://go.microsoft.com/fwlink/?linkid=2210031)* + +The subject of the email. +### IsBodyHTMLFormatted (Method) + + Checks if the email body is formatted in HTML. + + +#### Syntax +``` +procedure IsBodyHTMLFormatted(): Boolean +``` +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the email body is formatted in HTML; otherwise - false. +### GetId (Method) + + Gets the ID of the email message. + + +#### Syntax +``` +procedure GetId(): Guid +``` +#### Return Value +*[Guid](https://go.microsoft.com/fwlink/?linkid=2210122)* + +The ID of the email. +### GetRecipients (Method) + + Gets the recipents of a certain type of the email message. + + +#### Syntax +``` +procedure GetRecipients(RecipientType: Enum "Email Recipient Type"; var Recipients: list of [Text]) +``` +#### Parameters +*RecipientType ([Enum "Email Recipient Type"]())* + +Specifies the type of the recipients. + +*Recipients ([list of [Text]]())* + +Out parameter filled with the recipients' email addresses. + +### AddAttachment (Method) + + Adds a file attachment to the email message. + + +#### Syntax +``` +procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentBase64: Text) +``` +#### Parameters +*AttachmentName ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The name of the file attachment. + +*ContentType ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The Content Type of the file attachment. + +*AttachmentBase64 ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The Base64 text representation of the attachment. + +### AddAttachment (Method) + + Adds a file attachment to the email message. + + +#### Syntax +``` +procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentStream: InStream) +``` +#### Parameters +*AttachmentName ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The name of the file attachment. + +*ContentType ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The Content Type of the file attachment. + +*AttachmentStream ([InStream](https://go.microsoft.com/fwlink/?linkid=2210033))* + +The instream of the attachment. + +### Attachments_DeleteContent (Method) + + Deletes the contents of the currently selected attachment. + + +#### Syntax +``` +procedure Attachments_DeleteContent(): Boolean +``` +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +Returns true if contents was successfully deleted, otherwise false. +### Attachments_First (Method) + + Finds the first attachment of the email message. + + +#### Syntax +``` +procedure Attachments_First(): Boolean +``` +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if there is any attachment; otherwise - false. +### Attachments_Next (Method) + + Finds the next attachment of the email message. + + +#### Syntax +``` +procedure Attachments_Next(): Integer +``` +#### Return Value +*[Integer](https://go.microsoft.com/fwlink/?linkid=2209956)* + +The ID of the next attachment if it was found; otherwise - 0. +### Attachments_GetName (Method) + + Gets the name of the current attachment. + + +#### Syntax +``` +procedure Attachments_GetName(): Text[250] +``` +#### Return Value +*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* + +The name of the current attachment. +### Attachments_GetContent (Method) + + Gets the content of the current attachment. + + +#### Syntax +``` +procedure Attachments_GetContent(var AttachmentStream: InStream) +``` +#### Parameters +*AttachmentStream ([InStream](https://go.microsoft.com/fwlink/?linkid=2210033))* + +Out parameter with the content of the current attachment. + +### Attachments_GetContentBase64 (Method) + + Gets the content of the current attachment in Base64 encoding. + + +#### Syntax +``` +procedure Attachments_GetContentBase64(): Text +``` +#### Return Value +*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* + +The content of the current attachment in Base64 encoding. +### Attachments_GetContentType (Method) + + Gets the content type of the current attachment. + + +#### Syntax +``` +procedure Attachments_GetContentType(): Text[250] +``` +#### Return Value +*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* + +The content type of the current attachment. +### Attachments_GetContentId (Method) + + Gets the content ID of the current attachment. + + +This value is filled only if the attachment is inline the email body. + +#### Syntax +``` +procedure Attachments_GetContentId(): Text[40] +``` +#### Return Value +*[Text[40]](https://go.microsoft.com/fwlink/?linkid=2210031)* + +The content ID of the current attachment. +### Attachments_GetLength (Method) + + Gets the content length of the current attachment. + + +#### Syntax +``` +procedure Attachments_GetLength(): Integer +``` +#### Return Value +*[Integer](https://go.microsoft.com/fwlink/?linkid=2209956)* + +The content length of the current attachment. +### Attachments_IsInline (Method) + + Checks if the attachment is inline the message body. + + +#### Syntax +``` +procedure Attachments_IsInline(): Boolean +``` +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if the attachment is inline the message body; otherwise - false. +### OnGetAttachmentContent (Event) + + Integration event to provide the stream of data for a given MediaID. If attachment content has been deleted, this event makes it possible to provide + the data from elsewhere. + + +#### Syntax +``` +[IntegrationEvent(false, false)] +internal procedure OnGetAttachmentContent(MediaID: Guid; var InStream: Instream; var Handled: Boolean) +``` +#### Parameters +*MediaID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* + +Id of the underlying media field that contains the attachment data. + +*InStream ([Instream]())* + +Stream to that should pointed to the attachment data. + +*Handled ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* + +Was the attachment content added to the stream. + + +## Email Scenario (Codeunit 8893) + + Provides functionality to work with email scenarios. + + +### GetDefaultEmailAccount (Method) + + Gets the default email account. + + +#### Syntax +``` +procedure GetDefaultEmailAccount(var EmailAccount: Record "Email Account"): Boolean +``` +#### Parameters +*EmailAccount ([Record "Email Account"]())* + +Out parameter holding information about the default email account. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if an account for the the default scenario was found; otherwise - false. +### GetEmailAccount (Method) + + Gets the email account used by the given email scenario. + If the no account is defined for the provided scenario, the default account (if defined) will be returned. + + +#### Syntax +``` +procedure GetEmailAccount(Scenario: Enum "Email Scenario"; var EmailAccount: Record "Email Account"): Boolean +``` +#### Parameters +*Scenario ([Enum "Email Scenario"]())* + +The scenario to look for. + +*EmailAccount ([Record "Email Account"]())* + +Out parameter holding information about the email account. + +#### Return Value +*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* + +True if an account for the specified scenario was found; otherwise - false. +### SetDefaultEmailAccount (Method) + + Sets a default email account. + + +#### Syntax +``` +procedure SetDefaultEmailAccount(EmailAccount: Record "Email Account") +``` +#### Parameters +*EmailAccount ([Record "Email Account"]())* + +The email account to use. + +### SetEmailAccount (Method) + + Sets an email account to be used by the given email scenario. + + +#### Syntax +``` +procedure SetEmailAccount(Scenario: Enum "Email Scenario"; EmailAccount: Record "Email Account") +``` +#### Parameters +*Scenario ([Enum "Email Scenario"]())* + +The scenario for which to set an email account. + +*EmailAccount ([Record "Email Account"]())* + +The email account to use. + +### UnassignScenario (Method) + + Unassign an email scenario. The scenario will then use the default email account. + + +#### Syntax +``` +procedure UnassignScenario(Scenario: Enum "Email Scenario") +``` +#### Parameters +*Scenario ([Enum "Email Scenario"]())* + +The scenario to unassign. + + +## Email Test Mail (Codeunit 8887) + + Sends a test email to a specified account. + + + +## Email Accounts (Page 8887) + + Lists all of the registered email accounts + + +### GetAccount (Method) + + Gets the selected email account. + + +#### Syntax +``` +procedure GetAccount(var Account: Record "Email Account") +``` +#### Parameters +*Account ([Record "Email Account"]())* + +The selected email account + +### SetAccount (Method) + + Sets an email account to be selected. + + +#### Syntax +``` +procedure SetAccount(var Account: Record "Email Account") +``` +#### Parameters +*Account ([Record "Email Account"]())* + +The email account to be initially selected on the page + +### EnableLookupMode (Method) + + Enables the lookup mode on the page. + + +#### Syntax +``` +procedure EnableLookupMode() +``` + +## Email Account Wizard (Page 8886) + + Step by step guide for adding a new email account in Business Central + + + +## Email Activities (Page 8885) + + Provides information about the status of the emails. + + + +## Email Relation Picker (Page 8910) + +## Email Editor (Page 13) + + A page to create, edit and send e-mails. + + + +## Email Outbox (Page 8882) + + Displays information about email that are queued for sending. + + + +## Email Viewer (Page 12) + + A page to view sent emails. + + + +## Sent Emails (Page 8883) + + Provides an overview of all e-mail that were sent out. + + + +## Email Attachments (Page 8889) + +## Email Related Attachments (Page 8890) + + Displays a list of related attachments to an email. + + + +## Email Scenario Setup (Page 8893) + + Page is used to display email scenarios usage by email accounts. + + + +## Email Scenarios FactBox (Page 8895) + + Lists of all scenarios assigned to an account. + + + +## Email Scenarios for Account (Page 8894) + + Displays the scenarios that could be linked to a provided e-mail account. + + + +## Email User-Specified Address (Page 8884) + + A page to enter an email address. + + +### GetEmailAddress (Method) + + Gets the email address(es) that has been entered. + + +#### Syntax +``` +procedure GetEmailAddress(): Text +``` +#### Return Value +*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* + +Email address(es) +### SetEmailAddress (Method) + + Sets the inital value to be displayed. + + +#### Syntax +``` +procedure SetEmailAddress(Address: Text) +``` +#### Parameters +*Address ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* + +The value to be prefilled + + +## Email Connector (Enum 8889) + + Enum that holds all of the available email connectors. + + + +## Email Relation Type (Enum 8908) + + Represent the type of relation between an email and a source record. + + +### Primary Source (value: 0) + + + Primary source of an email. There should only be one primary source for an email. + + +### Related Entity (value: 1) + + + Related entity of an email. An email can have many record relations of this type. + + + +## Email Action (Enum 8891) + + Defines the action that the user can take when open an email message in the editor modally. + + +### Saved As Draft (value: 0) + + + The email was saved as draft. + + +### Discarded (value: 1) + + + The email was discarded. + + +### Sent (value: 2) + + + The email was sent. + + + +## Email Status (Enum 8888) + + Specifies the status of an email when it is in the outbox. + + +### (value: 0) + + + An uninitialized email. + + +### Draft (value: 1) + + + An email waiting to be queued up for sending. + + +### Queued (value: 2) + + + An email queued up and waiting to be processed. + + +### Processing (value: 3) + + + An email that is currently being processed and sent. + + +### Failed (value: 4) + + + An email that had an error occur during processing. + + + +## Email Recipient Type (Enum 8901) + + Specifies the type of an email recipient. + + +### To (value: 0) + + + Recipient type 'To'. + + +### Cc (value: 1) + + + Recipient type 'Cc'. + + +### Bcc (value: 3) + + + Recipient type 'Bcc'. + + + +## Email Scenario (Enum 8890) + + Email scenarios. + Used to decouple email accounts from sending emails. + + +### Default (value: 0) + + + The default email scenario. + Used in the cases where no other scenario is defined. + + diff --git a/Modules/System/File Access/app.json b/Modules/System/File Access/app.json new file mode 100644 index 0000000000..9fe1743127 --- /dev/null +++ b/Modules/System/File Access/app.json @@ -0,0 +1,40 @@ +{ + "id": "c9c54414-80c3-4cc9-98c6-589158882774", + "name": "File Access", + "publisher": "Microsoft", + "brief": "Enables user to send emails from Business Central.", + "description": "Enables user to send emails from Business Central.", + "version": "22.1.0.0", + "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", + "help": "https://go.microsoft.com/fwlink/?linkid=2103698", + "url": "https://go.microsoft.com/fwlink/?linkid=724011", + "logo": "", + "dependencies": [ + ], + "internalsVisibleTo": [ + { + "id": "eb303ff1-7a68-45b5-b7de-bf6965244dd7", + "name": "Email Test", + "publisher": "Microsoft" + }, + { + "id": "949b9041-d2cb-4e69-bf31-c1e8fcb9462b", + "name": "Email Test Library", + "publisher": "Microsoft" + } + ], + "screenshots": [ + + ], + "platform": "22.0.0.0", + "application": "22.0.0.0", + "idRanges": [ + { + "from": 70000, + "to": 70199 + } + ], + "target": "OnPrem", + "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccount.Codeunit.al b/Modules/System/File Access/src/Account/FileAccount.Codeunit.al new file mode 100644 index 0000000000..5c7e7c53cb --- /dev/null +++ b/Modules/System/File Access/src/Account/FileAccount.Codeunit.al @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Provides functionality to work with file accounts. +/// +codeunit 70000 "File Account" +{ + Access = Public; + + /// + /// Gets all of the file accounts registered in Business Central. + /// + /// Flag, used to determine whether to load the logos for the accounts. + /// Out parameter holding the file accounts. + procedure GetAllAccounts(LoadLogos: Boolean; var TempFileAccount: Record "File Account" temporary) + begin + FileAccountImpl.GetAllAccounts(LoadLogos, TempFileAccount); + end; + + /// + /// Gets all of the file accounts registered in Business Central. + /// + /// Out parameter holding the file accounts. + procedure GetAllAccounts(var TempFileAccount: Record "File Account" temporary) + begin + FileAccountImpl.GetAllAccounts(false, TempFileAccount); + end; + + /// + /// Checks if there is at least one file account registered in Business Central. + /// + /// True if there is any account registered in the system, otherwise - false. + procedure IsAnyAccountRegistered(): Boolean + begin + exit(FileAccountImpl.IsAnyAccountRegistered()); + end; + + var + FileAccountImpl: Codeunit "File Account Impl."; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccount.Table.al b/Modules/System/File Access/src/Account/FileAccount.Table.al new file mode 100644 index 0000000000..9ab68f08af --- /dev/null +++ b/Modules/System/File Access/src/Account/FileAccount.Table.al @@ -0,0 +1,66 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// A common representation of an file account. +/// +table 70000 "File Account" +{ + Extensible = false; + TableType = Temporary; + + fields + { + field(1; "Account Id"; Guid) + { + DataClassification = SystemMetadata; + } + + field(2; Name; Text[250]) + { + DataClassification = SystemMetadata; // Field only in Memory + } + + field(4; Connector; Enum "File Connector") + { + DataClassification = SystemMetadata; + } + + field(5; Logo; Media) + { + Access = Internal; + DataClassification = SystemMetadata; + } + + field(6; LogoBlob; Blob) + { + Access = Internal; + DataClassification = SystemMetadata; + Subtype = Bitmap; + } + } + + keys + { + key(PK; "Account Id", Connector) + { + Clustered = true; + } + + key(Name; Name) + { + Description = 'Used for sorting'; + } + } + + fieldgroups + { + fieldgroup(Brick; Logo, Name) + { + + } + } + +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al new file mode 100644 index 0000000000..1818fac02e --- /dev/null +++ b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al @@ -0,0 +1,228 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +codeunit 70001 "File Account Impl." +{ + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + Permissions = tabledata "File Connector Logo" = rimd, + tabledata "File Scenario" = imd; + + procedure GetAllAccounts(LoadLogos: Boolean; var TempFileAccount: Record "File Account" temporary) + var + FileAccounts: Record "File Account"; + IFileConnector: Interface "File Connector"; + Connector: Enum "File Connector"; + begin + TempFileAccount.Reset(); + TempFileAccount.DeleteAll(); + + foreach Connector in Connector.Ordinals do begin + IFileConnector := Connector; + + FileAccounts.DeleteAll(); + IFileConnector.GetAccounts(FileAccounts); + + if FileAccounts.FindSet() then + repeat + TempFileAccount := FileAccounts; + TempFileAccount.Connector := Connector; + + if LoadLogos then begin + ImportLogo(TempFileAccount, Connector); + ImportLogoBlob(TempFileAccount, Connector); + end; + + if not TempFileAccount.Insert() then; + until FileAccounts.Next() = 0; + end; + + // Sort by account name + TempFileAccount.SetCurrentKey(Name); + end; + + procedure DeleteAccounts(var FileAccountsToDelete: Record "File Account") + var + CurrentDefaultFileAccount: Record "File Account"; + ConfirmManagement: Codeunit "Confirm Management"; + FileScenario: Codeunit "File Scenario"; + FileConnector: Interface "File Connector"; + begin + CheckPermissions(); + + if not ConfirmManagement.GetResponseOrDefault(ConfirmDeleteQst, true) then + exit; + + if not FileAccountsToDelete.FindSet() then + exit; + + // Get the current default account to track if it was deleted + FileScenario.GetDefaultFileAccount(CurrentDefaultFileAccount); + + // Delete all selected acounts + repeat + // Check to validate that the connector is still installed + // The connector could have been uninstalled by another user/session + if IsValidConnector(FileAccountsToDelete.Connector) then begin + FileConnector := FileAccountsToDelete.Connector; + FileConnector.DeleteAccount(FileAccountsToDelete."Account Id"); + end; + until FileAccountsToDelete.Next() = 0; + + HandleDefaultAccountDeletion(CurrentDefaultFileAccount."Account Id", CurrentDefaultFileAccount.Connector); + end; + + local procedure HandleDefaultAccountDeletion(CurrentDefaultAccountId: Guid; Connector: Enum "File Connector") + var + AllFileAccounts: Record "File Account"; + NewDefaultFileAccount: Record "File Account"; + FileScenario: Codeunit "File Scenario"; + begin + GetAllAccounts(false, AllFileAccounts); + + if AllFileAccounts.IsEmpty() then + exit; //All of the accounts were deleted, nothing to do + + if AllFileAccounts.Get(CurrentDefaultAccountId, Connector) then + exit; // The default account was not deleted or it never existed + + // In case there's only one account, set it as default + if AllFileAccounts.Count() = 1 then begin + MakeDefault(AllFileAccounts); + exit; + end; + + Commit(); // Commit the accounts deletion in order to prompt for new default account + if PromptNewDefaultAccountChoice(NewDefaultFileAccount) then + MakeDefault(NewDefaultFileAccount) + else + FileScenario.UnassignScenario(Enum::"File Scenario"::Default); // remove the default scenario as it is pointing to a non-existent account + end; + + local procedure PromptNewDefaultAccountChoice(var NewDefaultFileAccount: Record "File Account"): Boolean + var + FileAccountsPage: Page "File Accounts"; + begin + FileAccountsPage.LookupMode(true); + FileAccountsPage.EnableLookupMode(); + FileAccountsPage.Caption(ChooseNewDefaultTxt); + if FileAccountsPage.RunModal() = Action::LookupOK then begin + FileAccountsPage.GetAccount(NewDefaultFileAccount); + exit(true); + end; + + exit(false); + end; + + local procedure ImportLogo(var FileAccount: Record "File Account"; Connector: Interface "File Connector") + var + FileConnectorLogo: Record "File Connector Logo"; + TempBlob: Codeunit "Temp Blob"; + Base64Convert: Codeunit "Base64 Convert"; + ConnectorLogoBase64: Text; + OutStream: Outstream; + InStream: InStream; + ConnectorLogoDescriptionTxt: Label '%1 Logo', Locked = true; + begin + ConnectorLogoBase64 := Connector.GetLogoAsBase64(); + + if ConnectorLogoBase64 = '' then + exit; + if not FileConnectorLogo.Get(FileAccount.Connector) then begin + TempBlob.CreateOutStream(OutStream); + Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); + TempBlob.CreateInStream(InStream); + FileConnectorLogo.Connector := FileAccount.Connector; + FileConnectorLogo.Logo.ImportStream(InStream, StrSubstNo(ConnectorLogoDescriptionTxt, FileAccount.Connector)); + if FileConnectorLogo.Insert() then; + end; + FileAccount.Logo := FileConnectorLogo.Logo + end; + + procedure IsAnyAccountRegistered(): Boolean + var + FileAccount: Record "File Account"; + begin + GetAllAccounts(false, FileAccount); + + exit(not FileAccount.IsEmpty()); + end; + + internal procedure IsUserFileAdmin(): Boolean + var + FileScenario: Record "File Scenario"; + begin + exit(FileScenario.WritePermission()); + end; + + procedure FindAllConnectors(var FileConnector: Record "File Connector") + var + Base64Convert: Codeunit "Base64 Convert"; + ConnectorInterface: Interface "File Connector"; + Connector: Enum "File Connector"; + ConnectorLogoBase64: Text; + OutStream: Outstream; + begin + foreach Connector in Enum::"File Connector".Ordinals() do begin + ConnectorInterface := Connector; + ConnectorLogoBase64 := ConnectorInterface.GetLogoAsBase64(); + FileConnector.Connector := Connector; + FileConnector.Description := ConnectorInterface.GetDescription(); + if ConnectorLogoBase64 <> '' then begin + FileConnector.Logo.CreateOutStream(OutStream); + Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); + end; + FileConnector.Insert(); + end; + end; + + procedure IsValidConnector(Connector: Enum "File Connector"): Boolean + begin + exit("File Connector".Ordinals().Contains(Connector.AsInteger())); + end; + + procedure MakeDefault(var FileAccount: Record "File Account") + var + FileScenario: Codeunit "File Scenario"; + begin + CheckPermissions(); + + if IsNullGuid(FileAccount."Account Id") then + exit; + + FileScenario.SetDefaultFileAccount(FileAccount); + end; + + internal procedure CheckPermissions() + begin + if not IsUserFileAdmin() then + Error(CannotManageSetupErr); + end; + + local procedure ImportLogoBlob(var FileAccount: Record "File Account"; Connector: Interface "File Connector") + var + Base64Convert: Codeunit "Base64 Convert"; + ConnectorLogoBase64: Text; + OutStream: Outstream; + begin + ConnectorLogoBase64 := Connector.GetLogoAsBase64(); + + if ConnectorLogoBase64 <> '' then begin + FileAccount.LogoBlob.CreateOutStream(OutStream); + Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); + end; + end; + + [InternalEvent(false)] + internal procedure OnAfterSetSelectionFilter(var FileAccount: Record "File Account") + begin + end; + + var + ConfirmDeleteQst: Label 'Go ahead and delete?'; + ChooseNewDefaultTxt: Label 'Choose a Default Account'; + CannotManageSetupErr: Label 'Your user account does not give you permission to set up file. Please contact your administrator.'; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al new file mode 100644 index 0000000000..849fe6105f --- /dev/null +++ b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al @@ -0,0 +1,522 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Step by step guide for adding a new file account in Business Central +/// +page 70001 "File Account Wizard" +{ + PageType = NavigatePage; + ApplicationArea = All; + UsageCategory = Administration; + Caption = 'Set Up File'; + SourceTable = "File Connector"; + SourceTableTemporary = true; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Editable = true; + ShowFilter = false; + LinksAllowed = false; + Permissions = tabledata Media = r, + tabledata "Media Resources" = r; + + layout + { + area(Content) + { + + group(Done) + { + Editable = false; + ShowCaption = false; + Visible = not DoneVisible and TopBannerVisible; + field(NotDoneIcon; MediaResourcesStandard."Media Reference") + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + ToolTip = ' '; + Caption = ' '; + } + } + group(NotDone) + { + Editable = false; + ShowCaption = false; + Visible = DoneVisible and TopBannerVisible; + field(DoneIcon; MediaResourcesDone."Media Reference") + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + ToolTip = ' '; + Caption = ' '; + } + } + + group(Header) + { + ShowCaption = false; + Visible = WelcomeVisible; + + group(HeaderText) + { + Caption = 'Welcome to file in Business Central'; + InstructionalText = 'Make file communications easier by connecting file accounts to Business Central. For example, store sales quotes and orders pdfs without opening an file app.'; + } + + field(LearnMoreHeader; LearnMoreTok) + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + Caption = ' '; + ToolTip = 'View information about how to set up the file capabilities.'; + + trigger OnDrillDown() + begin + Hyperlink(LearnMoreURLTxt); + end; + } + + group(Privacy) + { + Caption = 'Privacy notice'; + InstructionalText = 'By adding an file account you acknowledge that the file provider might be able to access the data you send in files from Business Central.'; + } + + group(GetStartedText) + { + Caption = 'Let''s go!'; + InstructionalText = 'Choose Next to get started.'; + } + } + + group(ConnectorHeader) + { + ShowCaption = false; + Visible = ChooseConnectorVisible and ConnectorsAvailable; + + label(UsageWarning) + { + Caption = 'Use caution when adding file accounts. Depending on your setup, accounts can be available to all users.'; + } + } + + group(ConnectorsGroup) + { + Visible = ChooseConnectorVisible and ConnectorsAvailable; + label("Specify the type of file account to add") + { + Caption = 'Specify the type of file account to add'; + ApplicationArea = All; + } + + repeater(Connectors) + { + ShowCaption = false; + Visible = ChooseConnectorVisible and ConnectorsAvailable; + FreezeColumn = Name; + Editable = false; + + field(Logo; Rec.Logo) + { + ApplicationArea = All; + Caption = ' '; + Editable = false; + Visible = ChooseConnectorVisible; + ToolTip = 'Select the type of account you want to create.'; + ShowCaption = false; + Width = 1; + } + + field(Name; Rec.Connector) + { + ApplicationArea = All; + Caption = 'Account Type'; + ToolTip = 'Specifies the type of the account you want to create.'; + Editable = false; + } + + field(Details; Rec.Description) + { + ApplicationArea = All; + Caption = 'Details'; + ToolTip = 'Specifies more details about the account type.'; + Editable = false; + Width = 50; + } + } + } + + group(NoConnectrosAvailableGroup) + { + Visible = ChooseConnectorVisible and not ConnectorsAvailable; + label(NoConnectorsAvailable) + { + ApplicationArea = All; + Caption = 'There are no file apps available. To use this feature you must install an file app.'; + } + + label(NoConnectorsAvailable2) + { + ApplicationArea = All; + Caption = 'File apps are available in Extension Management and AppSource.'; + } + + field(ExtensionManagement; ExtensionManagementTok) + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + Caption = ' '; + ToolTip = 'Navigate to Extension Management page.'; + + trigger OnDrillDown() + begin + Page.Run(Page::"Extension Management"); + end; + } + + field(AppSource; AppSourceTok) + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + Visible = AppSourceAvailable; + Caption = ' '; + ToolTip = 'Navigate to AppSource.'; + + trigger OnDrillDown() + begin + //FIXME + /* + AppSource := AppSource.Create(); + AppSource.ShowAppSource(); + */ + end; + } + + label(NoConnectorsAvailable3) + { + ApplicationArea = All; + Caption = 'View a list of the available file apps'; + } + + field(LearnMore; LearnMoreTok) + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + Caption = ' '; + ToolTip = 'View information about how to set up the file capabilities.'; + + trigger OnDrillDown() + begin + Hyperlink(LearnMoreURLTxt); + end; + } + } + + group(LastPage) + { + Visible = DoneVisible; + + group(AllSet) + { + Caption = 'Congratulations!'; + InstructionalText = 'You have successfully added the file account. To check that it is working, send a test file.'; + } + + group(Account) + { + Caption = 'Account'; + field(Namefield; RegisteredAccount.Name) + { + ApplicationArea = All; + Editable = false; + Caption = 'Name'; + ToolTip = 'Specifies the name of the account registered.'; + } + } + + group(Default) + { + Caption = ''; + + field(DefaultField; SetAsDefault) + { + ApplicationArea = All; + Editable = true; + Enabled = true; + Caption = 'Set as default'; + ToolTip = 'Use this account for all scenarios for which an account is not specified. Scenarios are processes that involve sending documents or notifications by file.'; + } + } + } + } + } + + actions + { + area(Processing) + { + + action(Cancel) + { + ApplicationArea = All; + Visible = CancelActionVisible; + Caption = 'Cancel'; + ToolTip = 'Cancel'; + InFooterBar = true; + Image = Cancel; + + trigger OnAction() + begin + CurrPage.Close(); + end; + } + + action(Back) + { + ApplicationArea = All; + Visible = BackActionVisible; + Enabled = BackActionEnabled; + Caption = 'Back'; + ToolTip = 'Back'; + InFooterBar = true; + Image = PreviousRecord; + + trigger OnAction() + begin + NextStep(true); + end; + } + + action(Next) + { + ApplicationArea = All; + Visible = NextActionVisible; + Enabled = NextActionEnabled; + Caption = 'Next'; + ToolTip = 'Next'; + InFooterBar = true; + Image = NextRecord; + + trigger OnAction() + begin + NextStep(false); + end; + } + + action(Finish) + { + ApplicationArea = All; + Visible = FinishActionVisible; + Caption = 'Finish'; + ToolTip = 'Finish'; + InFooterBar = true; + Image = NextRecord; + + trigger OnAction() + var + FileAccountImpl: Codeunit "File Account Impl."; + begin + if SetAsDefault then + FileAccountImpl.MakeDefault(RegisteredAccount); + + CurrPage.Close(); + end; + } + } + } + + trigger OnOpenPage() + begin + StartTime := CurrentDateTime(); + end; + + trigger OnQueryClosePage(CloseAction: Action): Boolean + var + DurationAsInt: Integer; + begin + DurationAsInt := CurrentDateTime() - StartTime; + if Step = Step::Done then + Session.LogMessage('0000CTK', StrSubstNo(AccountCreationSuccessfullyCompletedDurationLbl, DurationAsInt), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl) + else + Session.LogMessage('0000CTL', StrSubstNo(AccountCreationFailureDurationLbl, DurationAsInt), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); + end; + + trigger OnInit() + var + DefaultAccount: Record "File Account"; + FileAccountImpl: Codeunit "File Account Impl."; + FileScenario: Codeunit "File Scenario"; + begin + FileAccountImpl.CheckPermissions(); + + Step := Step::Welcome; + SetDefaultControls(); + ShowWelcomeStep(); + + FileAccountImpl.FindAllConnectors(Rec); + + FileRateLimitDisplay := NoLimitTxt; + + if not FileScenario.GetDefaultFileAccount(DefaultAccount) then + SetAsDefault := true; + + ConnectorsAvailable := Rec.FindFirst(); // Set the focus on the first record + AppSourceAvailable := false; //FIXME AppSource.IsAvailable(); + LoadTopBanners(); + end; + + local procedure NextStep(Backwards: Boolean) + begin + if Backwards then + Step -= 1 + else + Step += 1; + + SetDefaultControls(); + + case Step of + Step::Welcome: + ShowWelcomeStep(); + Step::"Choose Connector": + ShowChooseConnectorStep(); + Step::"Register Account": + ShowRegisterAccountStep(); + Step::"Done": + ShowDoneStep(); + end; + end; + + local procedure ShowWelcomeStep() + begin + WelcomeVisible := true; + BackActionEnabled := false; + end; + + local procedure ShowChooseConnectorStep() + begin + if not ConnectorsAvailable then + NextActionEnabled := false; + + ChooseConnectorVisible := true; + end; + + local procedure ShowRegisterAccountStep() + var + FeatureTelemetry: Codeunit "Feature Telemetry"; + DefaultFileRateLimit: Integer; + AccountWasRegistered: Boolean; + ConnectorSucceeded: Boolean; + begin + ConnectorSucceeded := TryRegisterAccount(AccountWasRegistered); + + if AccountWasRegistered then begin + FeatureTelemetry.LogUptake('0000CTF', 'File Access', Enum::"Feature Uptake Status"::"Set up"); + Session.LogMessage('0000CTH', Format(Rec.Connector) + ' account has been setup.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); + NextStep(false); + end else begin + Session.LogMessage('0000CTI', StrSubstNo(Format(Rec.Connector) + ' account has failed to setup. Error: %1', GetLastErrorCallStack()), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); + NextStep(true); + end; + + if not ConnectorSucceeded then + Error(GetLastErrorText()); + end; + + [TryFunction] + local procedure TryRegisterAccount(var AccountWasRegistered: Boolean) + var + FileAccountImpl: Codeunit "File Account Impl."; + FileConnector: Interface "File Connector"; + begin + // Check to validate that the connector is still installed + // The connector could have been uninstalled by another user/session + if not FileAccountImpl.IsValidConnector(Rec.Connector) then + Error(FileConnectorHasBeenUninstalledMsg); + + FileConnector := Rec.Connector; + + ClearLastError(); + AccountWasRegistered := FileConnector.RegisterAccount(RegisteredAccount); + RegisteredAccount.Connector := Rec.Connector; + end; + + local procedure ShowDoneStep() + begin + DoneVisible := true; + BackActionVisible := false; + NextActionVisible := false; + CancelActionVisible := false; + FinishActionVisible := true; + TestFileActionVisible := true; + end; + + local procedure SetDefaultControls() + begin + // Actions + BackActionVisible := true; + BackActionEnabled := true; + NextActionVisible := true; + NextActionEnabled := true; + CancelActionVisible := true; + FinishActionVisible := false; + TestFileActionVisible := false; + + // Groups + WelcomeVisible := false; + ChooseConnectorVisible := false; + DoneVisible := false; + end; + + local procedure LoadTopBanners() + begin + if MediaResourcesStandard.Get('ASSISTEDSETUP-NOTEXT-400PX.PNG') and + MediaResourcesDone.Get('ASSISTEDSETUPDONE-NOTEXT-400PX.PNG') and (CurrentClientType() = ClientType::Web) + then + TopBannerVisible := MediaResourcesDone."Media Reference".HasValue(); + end; + + var + RegisteredAccount: Record "File Account"; + MediaResourcesStandard: Record "Media Resources"; + MediaResourcesDone: Record "Media Resources"; + //FIXME [RunOnClient] + //FIXME AppSource: DotNet AppSource; + Step: Option Welcome,"Choose Connector","Register Account",Done; + RateLimit: Integer; + AppSourceTok: Label 'AppSource'; + ExtensionManagementTok: Label 'Extension Management'; + FileCategoryLbl: Label 'File', Locked = true; + LearnMoreURLTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2134520', Locked = true; //FIXME + LearnMoreTok: Label 'Learn more'; + NoLimitTxt: Label 'No limit'; + AccountCreationSuccessfullyCompletedDurationLbl: Label 'Successful creation of account completed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; + AccountCreationFailureDurationLbl: Label 'Creation of account failed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; + FileConnectorHasBeenUninstalledMsg: Label 'The selected file extension has been uninstalled. You must reinstall the extension to add an account with it.'; + [InDataSet] + AppSourceAvailable: Boolean; + [InDataSet] + TopBannerVisible: Boolean; + BackActionVisible: Boolean; + BackActionEnabled: Boolean; + NextActionVisible: Boolean; + NextActionEnabled: Boolean; + CancelActionVisible: Boolean; + FinishActionVisible: Boolean; + TestFileActionVisible: Boolean; + WelcomeVisible: Boolean; + ChooseConnectorVisible: Boolean; + DoneVisible: Boolean; + ConnectorsAvailable: Boolean; + SetAsDefault: Boolean; + StartTime: DateTime; + FileRateLimitDisplay: Text[250]; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccounts.Page.al b/Modules/System/File Access/src/Account/FileAccounts.Page.al new file mode 100644 index 0000000000..e0e18a9b36 --- /dev/null +++ b/Modules/System/File Access/src/Account/FileAccounts.Page.al @@ -0,0 +1,310 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Lists all of the registered file accounts +/// +page 70000 "File Accounts" +{ + PageType = List; + Caption = 'File Accounts'; + ApplicationArea = All; + UsageCategory = Administration; + SourceTable = "File Account"; + SourceTableTemporary = true; + PromotedActionCategories = 'New,Process,Report,Navigate'; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Editable = false; + ShowFilter = false; + LinksAllowed = false; + RefreshOnActivate = true; + + layout + { + area(Content) + { + repeater(Accounts) + { + Visible = ShowLogo; + FreezeColumn = NameField; + field(LogoField; Rec.LogoBlob) + { + ApplicationArea = All; + ShowCaption = false; + Caption = ' '; + Visible = ShowLogo; + ToolTip = 'Specifies the logo for the type of file account.'; + Width = 1; + } + + field(NameField; Rec.Name) + { + ApplicationArea = All; + ToolTip = 'Specifies the name of the account.'; + Visible = not IsInLookupMode; + + trigger OnDrillDown() + begin + ShowAccountInformation(); + end; + } + + field(NameFieldLookup; Rec.Name) + { + ApplicationArea = All; + ToolTip = 'Specifies the name of the account.'; + Visible = IsInLookupMode; + } + + field(DefaultField; DefaultTxt) + { + ApplicationArea = All; + Caption = 'Default'; + ToolTip = 'Specifies whether the file account will be used for all scenarios for which an account is not specified. You must have a default file account, even if you have only one account.'; + Visible = not IsInLookupMode; + } + + field(FileConnector; Rec.Connector) + { + ApplicationArea = All; + ToolTip = 'Specifies the type of file extension that the account is added to.'; + Visible = false; + } + } + } + + area(factboxes) + { + part(Scenarios; "File Scenarios FactBox") + { + Caption = 'File Scenarios'; + SubPageLink = "Account Id" = field("Account Id"), Connector = field(Connector), Scenario = filter(<> 0); // Do not show Default scenario + ApplicationArea = All; + } + } + } + + actions + { + area(Creation) + { + action(View) + { + ApplicationArea = All; + Image = View; + ToolTip = 'View settings for the file account.'; + ShortcutKey = return; + Visible = false; + + trigger OnAction() + begin + ShowAccountInformation(); + end; + } + + action(AddAccount) + { + ApplicationArea = All; + Promoted = true; + PromotedOnly = true; + PromotedCategory = New; + Image = Add; + Caption = 'Add an file account'; + ToolTip = 'Add an file account.'; + Visible = (not IsInLookupMode) and CanUserManageFileSetup; + + trigger OnAction() + begin + Page.RunModal(Page::"File Account Wizard"); + + UpdateFileAccounts(); + end; + } + } + + area(Processing) + { + //TODO add file browser here + action(MakeDefault) + { + ApplicationArea = All; + Image = Default; + Caption = 'Set as default'; + ToolTip = 'Mark the selected file account as the default account. This account will be used for all scenarios for which an account is not specified.'; + Visible = (not IsInLookupMode) and CanUserManageFileSetup; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + PromotedIsBig = true; + Scope = Repeater; + Enabled = not IsDefault; + + trigger OnAction() + begin + FileAccountImpl.MakeDefault(Rec); + + UpdateAccounts := true; + CurrPage.Update(false); + end; + } + + action(Delete) + { + ApplicationArea = All; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + Image = Delete; + Caption = 'Delete file account'; + ToolTip = 'Delete the file account.'; + Visible = (not IsInLookupMode) and CanUserManageFileSetup; + Scope = Repeater; + + trigger OnAction() + begin + CurrPage.SetSelectionFilter(Rec); + FileAccountImpl.OnAfterSetSelectionFilter(Rec); + + FileAccountImpl.DeleteAccounts(Rec); + + UpdateFileAccounts(); + end; + } + } + + area(Navigation) + { + action(FileScenarioSetup) + { + ApplicationArea = All; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Category4; + Image = Answers; + Caption = 'File Scenarios'; + ToolTip = 'Assign scenarios to the file accounts.'; + Visible = not IsInLookupMode; + + trigger OnAction() + var + FileScenarioSetup: Page "File Scenario Setup"; + begin + FileScenarioSetup.SetFileAccountId(Rec."Account Id", Rec.Connector); + FileScenarioSetup.Run(); + end; + } + } + } + + trigger OnOpenPage() + var + FeatureTelemetry: Codeunit "Feature Telemetry"; + begin + FeatureTelemetry.LogUptake('0000CTA', 'Fileing', Enum::"Feature Uptake Status"::Discovered); + CanUserManageFileSetup := FileAccountImpl.IsUserFileAdmin(); + Rec.SetCurrentKey("Account Id", Connector); + UpdateFileAccounts(); + ShowLogo := true; + end; + + trigger OnAfterGetRecord() + begin + // Updating the accounts is done via OnAfterGetRecord in the cases when an account was changed from the corresponding connector's page + if UpdateAccounts then begin + UpdateAccounts := false; + UpdateFileAccounts(); + end; + + DefaultTxt := ''; + + IsDefault := DefaultFileAccount."Account Id" = Rec."Account Id"; + if IsDefault then + DefaultTxt := '✓'; + end; + + local procedure UpdateFileAccounts() + var + FileAccount: Codeunit "File Account"; + FileScenario: Codeunit "File Scenario"; + IsSelected: Boolean; + SelectedAccountId: Guid; + begin + // We need this code block to maintain the same selected record. + SelectedAccountId := Rec."Account Id"; + IsSelected := not IsNullGuid(SelectedAccountId); + + FileAccount.GetAllAccounts(true, Rec); // Refresh the file accounts + FileScenario.GetDefaultFileAccount(DefaultFileAccount); // Refresh the default file account + + if IsSelected then begin + Rec."Account Id" := SelectedAccountId; + if Rec.Find() then; + end else + if Rec.FindFirst() then; + + HasFileAccount := not Rec.IsEmpty(); + + CurrPage.Update(false); + end; + + local procedure ShowAccountInformation() + var + FileAccountImpl: Codeunit "File Account Impl."; + Connector: Interface "File Connector"; + begin + UpdateAccounts := true; + +#pragma warning disable AL0603 + if not FileAccountImpl.IsValidConnector(Rec.Connector.AsInteger()) then +#pragma warning restore AL0603 + Error(FileConnectorHasBeenUninstalledMsg); + + Connector := Rec.Connector; + Connector.ShowAccountInformation(Rec."Account Id"); + end; + + /// + /// Gets the selected file account. + /// + /// The selected file account + procedure GetAccount(var FileAccount: Record "File Account") + begin + FileAccount := Rec; + end; + + /// + /// Sets an file account to be selected. + /// + /// The file account to be initially selected on the page + procedure SetAccount(var FileAccount: Record "File Account") + begin + Rec := FileAccount; + end; + + /// + /// Enables the lookup mode on the page. + /// + procedure EnableLookupMode() + begin + IsInLookupMode := true; + CurrPage.LookupMode(true); + end; + + var + DefaultFileAccount: Record "File Account"; + FileAccountImpl: Codeunit "File Account Impl."; + [InDataSet] + IsDefault: Boolean; + [InDataSet] + CanUserManageFileSetup: Boolean; + DefaultTxt: Text; + UpdateAccounts: Boolean; + ShowLogo: Boolean; + IsInLookupMode: Boolean; + HasFileAccount: Boolean; + FileConnectorHasBeenUninstalledMsg: Label 'The selected file extension has been uninstalled. To view information about the file account, you must reinstall the extension.'; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnector.Enum.al b/Modules/System/File Access/src/Connector/FileConnector.Enum.al new file mode 100644 index 0000000000..2497a1c764 --- /dev/null +++ b/Modules/System/File Access/src/Connector/FileConnector.Enum.al @@ -0,0 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Enum that holds all of the available file connectors. +/// +enum 70000 "File Connector" implements "File Connector" +{ + Extensible = true; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnector.Interface.al b/Modules/System/File Access/src/Connector/FileConnector.Interface.al new file mode 100644 index 0000000000..9930434f49 --- /dev/null +++ b/Modules/System/File Access/src/Connector/FileConnector.Interface.al @@ -0,0 +1,126 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// An file connector interface used to creating file accounts and handle external files. +/// +interface "File Connector" +{ + /// + /// Gets a List of Files stored on the provided account. + /// + /// The file path to list. + /// The file account ID which is used to get the file. + /// A list with all files stored in the path. + procedure ListFiles(Path: Text; AccountId: Guid; var Files: List of [Text]); + + /// + /// Gets a file from the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to get the file. + /// The Stream were the file is read to. + procedure GetFile(Path: Text; AccountId: Guid; Stream: InStream); + + /// + /// Gets a file to the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + /// The Stream were the file is read from. + procedure SetFile(Path: Text; AccountId: Guid; Stream: OutStream); + + /// + /// Checks if a file exists on the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + /// Returns true if the file exists + procedure FileExists(Path: Text; AccountId: Guid): Boolean; + + /// + /// Deletes a file exists on the provided account. + /// + /// The file path inside the file account. + /// The file account ID which is used to send out the file. + procedure DeleteFile(Path: Text; AccountId: Guid); + + /// + /// Gets a List of Directories stored on the provided account. + /// + /// The file path to list. + /// The file account ID which is used to get the file. + /// A list with all directories stored in the path. + procedure ListDirectories(Path: Text; AccountId: Guid; var Directiories: List of [Text]); + + /// + /// Creates a directory on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + procedure CreateDirectory(Path: Text; AccountId: Guid); + + /// + /// Checks if a directory exists on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + /// Returns true if the directory exists + procedure DirectoryExists(Path: Text; AccountId: Guid): Boolean; + + /// + /// Deletes a directory exists on the provided account. + /// + /// The directory path inside the file account. + /// The file account ID which is used to send out the file. + procedure DeleteDirectory(Path: Text; AccountId: Guid); + + /// + /// Returns the path separator of the file account. + /// + /// The Path separator like / or \ + procedure PathSeparator(): Text; + + /// + /// Gets the file accounts registered for the connector. + /// + /// Out variable that holds the registered file accounts for the connector. + procedure GetAccounts(var Accounts: Record "File Account"); + + /// + /// Shows the information for an file account. + /// + /// The ID of the file account + procedure ShowAccountInformation(AccountId: Guid); + + /// + /// Registers an file account for the connector. + /// + /// The out parameter must hold the account ID of the added account. + /// Out parameter with the details of the registered Account. + /// True if an account was registered. + procedure RegisterAccount(var FileAccount: Record "File Account"): Boolean + + /// + /// Deletes an file account for the connector. + /// + /// The ID of the file account + /// True if an account was deleted. + procedure DeleteAccount(AccountId: Guid): Boolean + + /// + /// Provides a custom logo for the connector that shows in the Setup File Account Guide. + /// + /// Base64 encoded image. + /// The recomended image size is 128x128. + /// The logo of the connector is Base64 format + procedure GetLogoAsBase64(): Text; + + /// + /// Provides a more detailed description of the connector. + /// + /// A more detailed description of the connector. + procedure GetDescription(): Text[250]; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnector.Table.al b/Modules/System/File Access/src/Connector/FileConnector.Table.al new file mode 100644 index 0000000000..0d29b46629 --- /dev/null +++ b/Modules/System/File Access/src/Connector/FileConnector.Table.al @@ -0,0 +1,37 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +table 70001 "File Connector" +{ + TableType = Temporary; + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + + fields + { + field(1; Connector; Enum "File Connector") + { + DataClassification = SystemMetadata; + } + field(2; Logo; Blob) + { + DataClassification = SystemMetadata; + Subtype = Bitmap; + } + field(3; Description; Text[250]) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Connector) + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al b/Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al new file mode 100644 index 0000000000..5a0ee947b9 --- /dev/null +++ b/Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al @@ -0,0 +1,33 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +table 70002 "File Connector Logo" +{ + DataClassification = SystemMetadata; + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + + fields + { + field(1; Connector; Enum "File Connector") + { + DataClassification = SystemMetadata; + } + field(2; Logo; Media) + { + DataClassification = CustomerContent; + } + } + + keys + { + key(PK; Connector) + { + Clustered = true; + } + } + +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al b/Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al new file mode 100644 index 0000000000..1354dcbd80 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Temporary table used to display the tree sctructure in "File Scenario Setup". +/// +table 70003 "File Account Scenario" +{ + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + TableType = Temporary; + + fields + { + field(1; Scenario; Integer) + { + DataClassification = SystemMetadata; + } + + field(2; Connector; Enum "File Connector") + { + DataClassification = SystemMetadata; + } + + field(3; "Account Id"; Guid) + { + DataClassification = SystemMetadata; + } + + field(4; "Display Name"; Text[2048]) + { + DataClassification = SystemMetadata; + } + + field(5; Default; Boolean) + { + DataClassification = SystemMetadata; + } + + field(6; EntryType; Option) + { + DataClassification = SystemMetadata; + OptionMembers = Account,Scenario; + } + + field(7; Position; Integer) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Scenario, "Account Id", Connector) + { + Clustered = true; + } + + key(Position; Position) + { + + } + + key(Name; "Display Name") + { + Description = 'Used for sorting by Dispay Name'; + } + } +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al b/Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al new file mode 100644 index 0000000000..5544efd605 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al @@ -0,0 +1,74 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Provides functionality to work with file scenarios. +/// +codeunit 70002 "File Scenario" +{ + /// + /// Gets the default file account. + /// + /// Out parameter holding information about the default file account. + /// True if an account for the the default scenario was found; otherwise - false. + procedure GetDefaultFileAccount(var FileAccount: Record "File Account"): Boolean + begin + exit(FileScenarioImpl.GetFileAccount(Enum::"File Scenario"::Default, FileAccount)); + end; + + /// + /// Gets the file account used by the given file scenario. + /// If the no account is defined for the provided scenario, the default account (if defined) will be returned. + /// + /// The scenario to look for. + /// Out parameter holding information about the file account. + /// True if an account for the specified scenario was found; otherwise - false. + procedure GetFileAccount(Scenario: Enum "File Scenario"; var FileAccount: Record "File Account"): Boolean + begin + exit(FileScenarioImpl.GetFileAccount(Scenario, FileAccount)); + end; + + /// + /// Sets a default file account. + /// + /// The file account to use. + procedure SetDefaultFileAccount(FileAccount: Record "File Account") + begin + FileScenarioImpl.SetFileAccount(Enum::"File Scenario"::Default, FileAccount); + end; + + /// + /// Sets an file account to be used by the given file scenario. + /// + /// The scenario for which to set an file account. + /// The file account to use. + procedure SetFileAccount(Scenario: Enum "File Scenario"; FileAccount: Record "File Account") + begin + FileScenarioImpl.SetFileAccount(Scenario, FileAccount); + end; + + /// + /// Unassign an file scenario. The scenario will then use the default file account. + /// + /// The scenario to unassign. + procedure UnassignScenario(Scenario: Enum "File Scenario") + begin + FileScenarioImpl.UnassignScenario(Scenario); + end; + + /// + /// Event for changing whether an file scenario should be added to the list of assignable scenarios. + /// If the scenario has already been assigned or is the default scenario, this event won't be published. + /// + /// The scenario that is going to be added to the list of assignable scenarios. + /// The return for whether this scenario should be listed in the assignable scenarios list. + [IntegrationEvent(false, false, true)] + internal procedure OnBeforeInsertAvailableFileScenario(Scenario: Enum "File Scenario"; var IsAvailable: Boolean) + begin + end; + + var + FileScenarioImpl: Codeunit "File Scenario Impl."; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Enum.al b/Modules/System/File Access/src/Scenario/FileScenario.Enum.al new file mode 100644 index 0000000000..e4d2e950e9 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenario.Enum.al @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// File scenarios. +/// Used to decouple file accounts from sending files. +/// +enum 70001 "File Scenario" +{ + Extensible = true; + + /// + /// The default file scenario. + /// Used in the cases where no other scenario is defined. + /// + value(0; Default) + { + Caption = 'Default'; + } +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Table.al b/Modules/System/File Access/src/Scenario/FileScenario.Table.al new file mode 100644 index 0000000000..60723a965e --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenario.Table.al @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Holds the mapping between file account and scenarios. +/// One scenarios is mapped to one file account. +/// One file account can be used for multiple scenarios. +/// +table 70004 "File Scenario" +{ + Access = Internal; + + fields + { + field(1; Scenario; Enum "File Scenario") + { + DataClassification = SystemMetadata; + } + + field(2; Connector; Enum "File Connector") + { + DataClassification = SystemMetadata; + } + + field(3; "Account Id"; Guid) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Scenario) + { + Clustered = true; + } + } +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al b/Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al new file mode 100644 index 0000000000..2fa9d3e697 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al @@ -0,0 +1,325 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +codeunit 70003 "File Scenario Impl." +{ + Access = Internal; + InherentPermissions = X; + InherentEntitlements = X; + Permissions = TableData "File Scenario" = rimd; + + procedure GetFileAccount(Scenario: Enum "File Scenario"; var FileAccount: Record "File Account"): Boolean + var + FileScenario: Record "File Scenario"; + AllFileAccounts: Record "File Account"; + FileAccounts: Codeunit "File Account"; + begin + FileAccounts.GetAllAccounts(AllFileAccounts); + + // Find the account for the provided scenario + if FileScenario.Get(Scenario) then + if AllFileAccounts.Get(FileScenario."Account Id", FileScenario.Connector) then begin + FileAccount := AllFileAccounts; + exit(true); + end; + + // Fallback to the default account if the scenario isn't mapped or the mapped account doesn't exist + if FileScenario.Get(Enum::"File Scenario"::Default) then + if AllFileAccounts.Get(FileScenario."Account Id", FileScenario.Connector) then begin + FileAccount := AllFileAccounts; + exit(true); + end; + + exit(false); + end; + + procedure SetFileAccount(Scenario: Enum "File Scenario"; FileAccount: Record "File Account") + var + FileScenario: Record "File Scenario"; + begin + if not FileScenario.Get(Scenario) then begin + FileScenario.Scenario := Scenario; + FileScenario.Insert(); + end; + + FileScenario."Account Id" := FileAccount."Account Id"; + FileScenario.Connector := FileAccount.Connector; + + FileScenario.Modify(); + end; + + procedure UnassignScenario(Scenario: Enum "File Scenario") + var + FileScenario: Record "File Scenario"; + begin + if FileScenario.Get(Scenario) then + FileScenario.Delete(); + end; + + /// + /// Get a list of entries, representing a tree structure with file accounts and the scenarios, assigned to each accout. + /// + /// + /// Account sales@cronus.com has scenarios "Sales Quote" and "Sales Credit Memo" assigned. + /// Account purchase@cronus.com has scenarios "Purchase Quote" and "Purchase Invoice" assigned. + /// The result of calling the function will be: + /// sales@cronus.com, "Sales Quote", "Sales Credit Memo", purchase@cronus.com, "Purchase Quote", "Purchase Invoice" + /// + /// A flatten tree structure representing the all the file accounts and the scenarios assigned to them. + procedure GetScenariosByFileAccount(var Result: Record "File Account Scenario") + var + FileAccounts: Record "File Account"; + FileAccountScenarios: Record "File Account Scenario"; + DefaultAccount: Record "File Account"; + FileAccount: Codeunit "File Account"; + DisplayName: Text[2048]; + Position: Integer; + Default: Boolean; + begin + Result.Reset(); + Result.DeleteAll(); + + FileAccount.GetAllAccounts(FileAccounts); + + if not FileAccounts.FindSet() then + exit; // No accounts, nothing to do + + // The position is set in order to be able to properly sort the entries (by order of insertion) + Position := 1; + GetDefaultAccount(DefaultAccount); + + repeat + Default := (FileAccounts."Account Id" = DefaultAccount."Account Id") and (FileAccounts.Connector = DefaultAccount.Connector); + DisplayName := FileAccounts.Name; + + // Add entry for the file account. Scenario is -1, because it isn't needed when displaying the file account. + AddEntry(Result, Result.EntryType::Account, -1, FileAccounts."Account Id", FileAccounts.Connector, DisplayName, Default, Position); + + // Get the file scenarios assigned to the current file account, sorted by "Display Name" + GetFileScenariosForAccount(FileAccounts, FileAccountScenarios); + + if FileAccountScenarios.FindSet() then + repeat + // Add entry for every scenario that is assigned to the current file account + AddEntry(Result, FileAccountScenarios.EntryType::Scenario, FileAccountScenarios.Scenario, FileAccountScenarios."Account Id", FileAccountScenarios.Connector, FileAccountScenarios."Display Name", false, Position); + until FileAccountScenarios.Next() = 0; + until FileAccounts.Next() = 0; + + // Order by position to show accurate results + Result.SetCurrentKey(Position); + end; + + local procedure GetFileScenariosForAccount(FileAccount: Record "File Account"; var FileAccountScenarios: Record "File Account Scenario") + var + FileScenarios: Record "File Scenario"; + ValidFileScenarios: DotNet Hashtable; + IsScenarioValid: Boolean; + Scenario: Integer; + begin + FileAccountScenarios.Reset(); + FileAccountScenarios.DeleteAll(); + + // Get all file scenarios assigned to the file account + FileScenarios.SetRange("Account Id", FileAccount."Account Id"); + FileScenarios.SetRange(Connector, FileAccount.Connector); + + if not FileScenarios.FindSet() then + exit; + + // Find all valid scenarios. Invalid scenario may occur if the extension that added them was removed. + ValidFileScenarios := ValidFileScenarios.Hashtable(); + foreach Scenario in Enum::"File Scenario".Ordinals() do + ValidFileScenarios.Add(Scenario, Scenario); + + // Convert File Scenario-s to File Account Scenario-s so they can be sorted by "Display Name" + repeat + IsScenarioValid := ValidFileScenarios.Contains(FileScenarios.Scenario.AsInteger()); + + // Add entry for every scenario that exists and uses the file account. Skip the default scenario. + if (FileScenarios.Scenario <> Enum::"File Scenario"::Default) and IsScenarioValid then begin + FileAccountScenarios.Scenario := FileScenarios.Scenario.AsInteger(); + FileAccountScenarios."Account Id" := FileScenarios."Account Id"; + FileAccountScenarios.Connector := FileScenarios.Connector; + FileAccountScenarios."Display Name" := Format(FileScenarios.Scenario); + + FileAccountScenarios.Insert(); + end; + until FileScenarios.Next() = 0; + + FileAccountScenarios.SetCurrentKey("Display Name"); // sort scenarios by "Display Name" + end; + + local procedure AddEntry(var Result: Record "File Account Scenario"; EntryType: Option; Scenario: Integer; AccountId: Guid; Connector: Enum "File Connector"; DisplayName: Text[2048]; Default: Boolean; var Position: Integer) + begin + // Add entry to the result while maintaining the position so that the tree represents the data correctly + Result.EntryType := EntryType; + Result.Scenario := Scenario; + Result."Account Id" := AccountId; + Result.Connector := Connector; + Result."Display Name" := DisplayName; + Result.Default := Default; + Result.Position := Position; + + Result.Insert(); + + Position := Position + 1; + end; + + procedure AddScenarios(FileAccount: Record "File Account Scenario"): Boolean + var + FileScenario: Record "File Scenario"; + SelectedScenarios: Record "File Account Scenario"; + ScenariosForAccount: Page "File Scenarios For Account"; + begin + FileAccountImpl.CheckPermissions(); + + if FileAccount.EntryType <> FileAccount.EntryType::Account then // wrong entry, the entry should be of type "Account" + exit; + + ScenariosForAccount.Caption := StrSubstNo(ScenariosForAccountCaptionTxt, FileAccount."Display Name"); + ScenariosForAccount.LookupMode(true); + ScenariosForAccount.SetRecord(FileAccount); + + if ScenariosForAccount.RunModal() <> Action::LookupOK then + exit(false); + + ScenariosForAccount.GetSelectedScenarios(SelectedScenarios); + + if not SelectedScenarios.FindSet() then + exit(false); + + repeat + if not FileScenario.Get(SelectedScenarios.Scenario) then begin + FileScenario."Account Id" := FileAccount."Account Id"; + FileScenario.Connector := FileAccount.Connector; + FileScenario.Scenario := Enum::"File Scenario".FromInteger(SelectedScenarios.Scenario); + + FileScenario.Insert(); + end else begin + FileScenario."Account Id" := FileAccount."Account Id"; + FileScenario.Connector := FileAccount.Connector; + + FileScenario.Modify(); + end; + until SelectedScenarios.Next() = 0; + + exit(true); + end; + + procedure GetAvailableScenariosForAccount(FileAccount: Record "File Account Scenario"; var FileScenarios: Record "File Account Scenario") + var + Scenario: Record "File Scenario"; + FileScenario: Codeunit "File Scenario"; + CurrentScenario, i : Integer; + IsAvailable: Boolean; + begin + FileScenarios.Reset(); + FileScenarios.DeleteAll(); + i := 1; + + foreach CurrentScenario in Enum::"File Scenario".Ordinals() do begin + Clear(Scenario); + Scenario.SetRange("Account Id", FileAccount."Account Id"); + Scenario.SetRange(Connector, FileAccount.Connector); + Scenario.SetRange(Scenario, CurrentScenario); + + // If the scenario isn't already connected to the file account, then it's available. Natually, we skip the default scenario + IsAvailable := Scenario.IsEmpty() and (not (CurrentScenario = Enum::"File Scenario"::Default.AsInteger())); + + // If the scenario is available, allow partner to determine if it should be shown + if IsAvailable then + FileScenario.OnBeforeInsertAvailableFileScenario(Enum::"File Scenario".FromInteger(CurrentScenario), IsAvailable); + + if IsAvailable then begin + FileScenarios."Account Id" := FileAccount."Account Id"; + FileScenarios.Connector := FileAccount.Connector; + FileScenarios.Scenario := CurrentScenario; + FileScenarios."Display Name" := Format(Enum::"File Scenario".FromInteger(Enum::"File Scenario".Ordinals().Get(i))); + + FileScenarios.Insert(); + end; + + i := i + 1; + end; + end; + + procedure ChangeAccount(var FileScenario: Record "File Account Scenario"): Boolean + var + SelectedAccount: Record "File Account"; + Scenario: Record "File Scenario"; + FileAccount: Codeunit "File Account"; + AccountsPage: Page "File Accounts"; + begin + FileAccountImpl.CheckPermissions(); + + if not FileScenario.FindSet() then + exit(false); + + FileAccount.GetAllAccounts(false, SelectedAccount); + if SelectedAccount.Get(FileScenario."Account Id", FileScenario.Connector) then; + + AccountsPage.EnableLookupMode(); + AccountsPage.SetRecord(SelectedAccount); + AccountsPage.Caption := ChangeFileAccountForScenarioTxt; + + if AccountsPage.RunModal() <> Action::LookupOK then + exit(false); + + AccountsPage.GetAccount(SelectedAccount); + + if IsNullGuid(SelectedAccount."Account Id") then // defensive check, no account was selected + exit; + + repeat + if Scenario.Get(FileScenario.Scenario) then begin + Scenario."Account Id" := SelectedAccount."Account Id"; + Scenario.Connector := SelectedAccount.Connector; + + Scenario.Modify(); + end; + until FileScenario.Next() = 0; + + exit(true); + end; + + procedure DeleteScenario(var FileScenario: Record "File Account Scenario"): Boolean + var + Scenario: Record "File Scenario"; + begin + FileAccountImpl.CheckPermissions(); + + if not FileScenario.FindSet() then + exit(false); + + repeat + if FileScenario.EntryType = FileScenario.EntryType::Scenario then begin + Scenario.SetRange(Scenario, FileScenario.Scenario); + Scenario.SetRange("Account Id", FileScenario."Account Id"); + Scenario.SetRange(Connector, FileScenario.Connector); + + Scenario.DeleteAll(); + end; + until FileScenario.Next() = 0; + + exit(true); + end; + + local procedure GetDefaultAccount(var FileAccount: Record "File Account") + var + Scenario: Record "File Scenario"; + begin + if not Scenario.Get(Enum::"File Scenario"::Default) then + exit; + + FileAccount."Account Id" := Scenario."Account Id"; + FileAccount.Connector := Scenario.Connector; + end; + + var + FileAccountImpl: Codeunit "File Account Impl."; + AccountDisplayLbl: Label '%1 (%2)', Locked = true; + ChangeFileAccountForScenarioTxt: Label 'Change file account used for the selected scenarios'; + ScenariosForAccountCaptionTxt: Label 'Assign scenarios to account %1', Comment = '%1 = the name of the e-file account'; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al b/Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al new file mode 100644 index 0000000000..cf568cea5d --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al @@ -0,0 +1,194 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Page is used to display file scenarios usage by file accounts. +/// +page 70002 "File Scenario Setup" +{ + Caption = 'File Scenario Assignment'; + PageType = List; + UsageCategory = Administration; + ApplicationArea = All; + Extensible = false; + Editable = false; + DeleteAllowed = false; + InsertAllowed = false; + ModifyAllowed = false; + SourceTable = "File Account Scenario"; + InstructionalText = 'Assign file scenarios'; + + layout + { + area(Content) + { + repeater(ScenariosByFile) + { + IndentationColumn = Indentation; + IndentationControls = Name; + ShowAsTree = true; + + field(Name; Rec."Display Name") + { + ApplicationArea = All; + Caption = 'Scenarios by file accounts'; + ToolTip = 'Specifies the scenarios that are using the file account.'; + Editable = false; + StyleExpr = Style; + } + + field(Default; DefaultTxt) + { + ApplicationArea = All; + Caption = 'Default'; + ToolTip = 'Specifies whether this is the default account to use for scenarios when no other account is specified.'; + StyleExpr = Style; + } + } + } + } + + actions + { + area(Processing) + { + group(Account) + { + action(AddScenario) + { + Visible = (TypeOfEntry = TypeOfEntry::Account) and CanUserManageFileSetup; + + ApplicationArea = All; + Caption = 'Assign scenarios'; + ToolTip = 'Assign file scenarios for the selected file account. When assigned, everyone will use the account for the scenario. For example, if you assign the Sales Order scenario, everyone will use the account to send sales orders.'; + Image = NewDocument; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + PromotedIsBig = true; + Scope = Repeater; + + trigger OnAction() + begin + SelectedFileAccountScenario := Rec; + FileScenarioImpl.AddScenarios(Rec); + + FileScenarioImpl.GetScenariosByFileAccount(Rec); + SetSelectedRecord(); + end; + } + } + + group(Scenario) + { + action(ChangeAccount) + { + Visible = (TypeOfEntry = TypeOfEntry::Scenario) and CanUserManageFileSetup; + + ApplicationArea = All; + Caption = 'Reassign'; + ToolTip = 'Reassign the selected scenarios to another file account.'; + Image = Change; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + PromotedIsBig = true; + Scope = Repeater; + + trigger OnAction() + begin + CurrPage.SetSelectionFilter(Rec); + SelectedFileAccountScenario := Rec; + + FileScenarioImpl.ChangeAccount(Rec); + FileScenarioImpl.GetScenariosByFileAccount(Rec); // refresh the data on the page + SetSelectedRecord(); + end; + } + + action(Unassign) + { + Visible = (TypeOfEntry = TypeOfEntry::Scenario) and CanUserManageFileSetup; + + ApplicationArea = All; + Caption = 'Unassign'; + ToolTip = 'Unassign the selected scenarios. Afterward, the default file account will be used to send files for the scenarios.'; + Image = Delete; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + PromotedIsBig = true; + Scope = Repeater; + + trigger OnAction() + begin + CurrPage.SetSelectionFilter(Rec); + SelectedFileAccountScenario := Rec; + + FileScenarioImpl.DeleteScenario(Rec); + FileScenarioImpl.GetScenariosByFileAccount(Rec); // refresh the data on the page + SetSelectedRecord(); + end; + } + } + } + } + + trigger OnOpenPage() + var + FeatureTelemetry: Codeunit "Feature Telemetry"; + begin + FeatureTelemetry.LogUptake('0000CTN', 'File Access', Enum::"Feature Uptake Status"::Discovered); + CanUserManageFileSetup := FileAccountImpl.IsUserFileAdmin(); + FileScenarioImpl.GetScenariosByFileAccount(Rec); + + // Set selection + if not Rec.Get(-1, FileAccountId, FileConnector) then + if Rec.FindFirst() then; + end; + + trigger OnAfterGetRecord() + begin + DefaultTxt := ''; + + TypeOfEntry := Rec.EntryType; + + if TypeOfEntry = TypeOfEntry::Account then begin + Indentation := 0; + Style := 'Strong'; + if Rec.Default then + DefaultTxt := '✓' + end; + + if TypeOfEntry = TypeOfEntry::Scenario then begin + Indentation := 1; + Style := 'Standard'; + end; + end; + + // Used to set the focus on an file account + internal procedure SetFileAccountId(AccountId: Guid; Connector: Enum "File Connector") + begin + FileAccountId := AccountId; + FileConnector := Connector; + end; + + local procedure SetSelectedRecord() + begin + if not Rec.Get(SelectedFileAccountScenario.Scenario, SelectedFileAccountScenario."Account Id", SelectedFileAccountScenario.Connector) then + Rec.FindFirst(); + end; + + var + SelectedFileAccountScenario: Record "File Account Scenario"; + FileScenarioImpl: Codeunit "File Scenario Impl."; + FileAccountImpl: Codeunit "File Account Impl."; + FileAccountId: Guid; + FileConnector: Enum "File Connector"; + Style, DefaultTxt : Text; + TypeOfEntry: Option Account,Scenario; + Indentation: Integer; + CanUserManageFileSetup: Boolean; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al b/Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al new file mode 100644 index 0000000000..2a14f292d1 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al @@ -0,0 +1,38 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Lists of all scenarios assigned to an account. +/// +page 70003 "File Scenarios FactBox" +{ + PageType = ListPart; + Extensible = false; + SourceTable = "File Scenario"; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Editable = false; + ShowFilter = false; + LinksAllowed = false; + Permissions = tabledata "File Scenario" = r; + + layout + { + area(Content) + { + repeater(ScenariosByFile) + { + field(Name; Format(Rec.Scenario)) + { + ApplicationArea = All; + ToolTip = 'The file scenario.'; + Caption = 'File scenario'; + Editable = false; + } + } + } + } +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al b/Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al new file mode 100644 index 0000000000..e4839ff167 --- /dev/null +++ b/Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al @@ -0,0 +1,63 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Displays the scenarios that could be linked to a provided file account. +/// +page 70004 "File Scenarios for Account" +{ + PageType = List; + Extensible = false; + SourceTable = "File Account Scenario"; + InsertAllowed = false; + ModifyAllowed = false; + DeleteAllowed = false; + Editable = false; + ShowFilter = false; + LinksAllowed = false; + + layout + { + area(Content) + { + repeater(ScenariosByFile) + { + field(Name; Rec."Display Name") + { + ApplicationArea = All; + ToolTip = 'The file scenario.'; + Caption = 'File scenario'; + Editable = false; + } + } + } + } + + internal procedure GetSelectedScenarios(var ResultFileAccountScenario: Record "File Account Scenario") + begin + ResultFileAccountScenario.Reset(); + ResultFileAccountScenario.DeleteAll(); + + CurrPage.SetSelectionFilter(Rec); + + if not Rec.FindSet() then + exit; + + repeat + ResultFileAccountScenario.Copy(Rec); + ResultFileAccountScenario.Insert(); + until Rec.Next() = 0; + end; + + trigger OnOpenPage() + begin + FileScenarioImpl.GetAvailableScenariosForAccount(Rec, Rec); + Rec.SetCurrentKey("Display Name"); + if Rec.FindFirst() then; // set the selection to the first record + end; + + var + FileScenarioImpl: Codeunit "File Scenario Impl."; +} \ No newline at end of file From 6ecfacc739d07a3b1455aadbf5fe2025e9f2a977 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 4 May 2023 17:38:42 +0200 Subject: [PATCH 02/33] Add File Browser --- .../app/src/BlobStorageAccount.Table.al | 4 +- .../app/src/BlobStorageAccountWizard.Page.al | 8 + .../src/BlobStorageConnectorImpl.Codeunit.al | 128 ++++++++++++++- .../src/BlobStorageContainerLookup.Page.al | 23 +++ .../src/Account/FileAccountImpl.Codeunit.al | 13 ++ .../src/Account/FileAccounts.Page.al | 22 ++- .../src/Connector/FileConnector.Interface.al | 6 +- .../src/Lookup/FileAccountBrowser.Page.al | 152 ++++++++++++++++++ .../src/Lookup/FileAccountContent.Table.al | 32 ++++ .../File Access/src/Lookup/FileType.Enum.al | 29 ++++ 10 files changed, 405 insertions(+), 12 deletions(-) create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al create mode 100644 Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al create mode 100644 Modules/System/File Access/src/Lookup/FileAccountContent.Table.al create mode 100644 Modules/System/File Access/src/Lookup/FileType.Enum.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al index 4dbae1c33e..0a2e47601c 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al @@ -25,11 +25,11 @@ table 80100 "Blob Storage Account" DataClassification = CustomerContent; Caption = 'Name of account'; } - field(3; "Storage Account Name"; Text[50]) + field(3; "Storage Account Name"; Text[2048]) { Caption = 'Storage Account Name'; } - field(4; "Container Name"; Text[50]) + field(4; "Container Name"; Text[2048]) { Caption = 'Container Name'; } diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al index 4d3644808f..7e6c361819 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al @@ -78,6 +78,14 @@ page 80101 "Blob Storage Account Wizard" ToolTip = 'Specifies the container to use of the Storage Blob.'; ShowMandatory = true; + trigger OnLookup(var Text: Text): Boolean + var + BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; + begin + BlobStorageConnectorImpl.LookUpContainer(Rec, Password, Text); + exit(true); + end; + trigger OnValidate() begin IsNextEnabled := BlobStorageConnectorImpl.IsAccountValid(Rec); diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 001f2179dc..5d3054e740 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -10,7 +10,6 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" var ConnectorDescriptionTxt: Label 'Use Azure Blob Storage to store and retrieve files.'; - NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; ConnectorBase64LogoTxt: Label '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', Locked = true; @@ -20,9 +19,34 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file path to list. /// The file account ID which is used to get the file. /// A list with all files stored in the path. - procedure ListFiles(Path: Text; AccountId: Guid; var Files: List of [Text]) + procedure ListFiles(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary) + var + ABSContainerContent: Record "ABS Container Content"; + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin - + InitBlobClient(AccountId, ABSBlobClient); + if (Path <> '') and not Path.EndsWith('/') then + Path += '/'; + ABSOptionalParameters.Prefix(Path); + ABSOptionalParameters.Delimiter('/'); + ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); + + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + ABSContainerContent.SetFilter("Blob Type", '<>%1', ''); + if not ABSContainerContent.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := ABSContainerContent.Name; + FileAccountContent.Type := FileAccountContent.Type::"File"; + FileAccountContent."Parent Directory" := ABSContainerContent."Parent Directory"; + FileAccountContent.Insert(); + until ABSContainerContent.Next() = 0; end; /// @@ -32,8 +56,17 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file account ID which is used to get the file. /// The Stream were the file is read to. procedure GetFile(Path: Text; AccountId: Guid; Stream: InStream) + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; begin + InitBlobClient(AccountId, ABSBlobClient); + ABSOperationResponse := ABSBlobClient.GetBlobAsStream(Path, Stream); + if ABSOperationResponse.IsSuccessful() then + exit; + + Error(ABSOperationResponse.GetError()); end; /// @@ -42,9 +75,18 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file path inside the file account. /// The file account ID which is used to send out the file. /// The Stream were the file is read from. - procedure SetFile(Path: Text; AccountId: Guid; Stream: OutStream) + procedure SetFile(Path: Text; AccountId: Guid; Stream: InStream) + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; begin + InitBlobClient(AccountId, ABSBlobClient); + ABSOperationResponse := ABSBlobClient.PutBlobBlockBlobStream(Path, Stream); + + if ABSOperationResponse.IsSuccessful() then + exit; + Error(ABSOperationResponse.GetError()); end; /// @@ -54,8 +96,11 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file account ID which is used to send out the file. /// Returns true if the file exists procedure FileExists(Path: Text; AccountId: Guid): Boolean + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; begin - + InitBlobClient(AccountId, ABSBlobClient); end; /// @@ -64,8 +109,17 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file path inside the file account. /// The file account ID which is used to send out the file. procedure DeleteFile(Path: Text; AccountId: Guid) + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; begin + InitBlobClient(AccountId, ABSBlobClient); + ABSOperationResponse := ABSBlobClient.DeleteBlob(Path); + + if ABSOperationResponse.IsSuccessful() then + exit; + Error(ABSOperationResponse.GetError()); end; /// @@ -74,9 +128,36 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// The file path to list. /// The file account ID which is used to get the file. /// A list with all directories stored in the path. - procedure ListDirectories(Path: Text; AccountId: Guid; var Directiories: List of [Text]) + procedure ListDirectories(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary) + var + ABSContainerContent: Record "ABS Container Content"; + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin + InitBlobClient(AccountId, ABSBlobClient); + + if (Path <> '') and not Path.EndsWith('/') then + Path += '/'; + + ABSOptionalParameters.Prefix(Path); + ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + ABSContainerContent.SetRange("Parent Directory", Path); + ABSContainerContent.SetRange("Blob Type", ''); + if not ABSContainerContent.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := ABSContainerContent.Name; + FileAccountContent.Type := FileAccountContent.Type::Directory; + FileAccountContent."Parent Directory" := ABSContainerContent."Parent Directory"; + FileAccountContent.Insert(); + until ABSContainerContent.Next() = 0; end; /// @@ -228,4 +309,39 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" FileAccount.Name := NewBlobStorageAccount.Name; FileAccount.Connector := Enum::"File Connector"::"Blob Storage"; end; + + internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; Password: Text; var NewContainerName: Text[2048]) + var + ABSContainers: Record "ABS Container"; + ABSContainerClient: Codeunit "ABS Container Client"; + StorageServiceAuthorization: Codeunit "Storage Service Authorization"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + Authorization: Interface "Storage Service Authorization"; + begin + Account.TestField("Storage Account Name"); + Authorization := StorageServiceAuthorization.CreateSharedKey(Password); + ABSContainerClient.Initialize(Account."Storage Account Name", Authorization); + ABSOperationResponse := ABSContainerClient.ListContainers(ABSContainers); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + if not ABSContainers.Get(NewContainerName) then + if ABSContainers.FindFirst() then; + + if (Page.RunModal(Page::"Blob Storage Container Lookup", ABSContainers) <> Action::LookupOK) then + exit; + + NewContainerName := ABSContainers.Name; + end; + + local procedure InitBlobClient(var AccountId: Guid; var ABSBlobClient: Codeunit "ABS Blob Client") + var + BlobStorageAccount: Record "Blob Storage Account"; + StorageServiceAuthorization: Codeunit "Storage Service Authorization"; + Authorization: Interface "Storage Service Authorization"; + begin + BlobStorageAccount.Get(AccountId); + Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetPassword(BlobStorageAccount."Password Key")); + ABSBlobClient.Initialize(BlobStorageAccount."Storage Account Name", BlobStorageAccount."Container Name", Authorization); + end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al new file mode 100644 index 0000000000..b068fcb269 --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al @@ -0,0 +1,23 @@ +page 80102 "Blob Storage Container Lookup" +{ + Caption = 'Container Lookup'; + PageType = List; + SourceTable = "ABS Container"; + Editable = false; + Extensible = false; + + layout + { + area(content) + { + repeater(General) + { + field(Name; Rec.Name) + { + ApplicationArea = All; + ToolTip = 'Specifies the Name of the container.'; + } + } + } + } +} diff --git a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al index 1818fac02e..611ac353b5 100644 --- a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al +++ b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al @@ -196,6 +196,19 @@ codeunit 70001 "File Account Impl." FileScenario.SetDefaultFileAccount(FileAccount); end; + procedure BrowseAccount(var FileAccount: Record "File Account") + var + FileAccountBrowser: Page "File Account Browser"; + begin + CheckPermissions(); + + if IsNullGuid(FileAccount."Account Id") then + exit; + + FileAccountBrowser.BrowseFileAccount(FileAccount); + FileAccountBrowser.Run(); + end; + internal procedure CheckPermissions() begin if not IsUserFileAdmin() then diff --git a/Modules/System/File Access/src/Account/FileAccounts.Page.al b/Modules/System/File Access/src/Account/FileAccounts.Page.al index e0e18a9b36..58c82e14ae 100644 --- a/Modules/System/File Access/src/Account/FileAccounts.Page.al +++ b/Modules/System/File Access/src/Account/FileAccounts.Page.al @@ -128,7 +128,6 @@ page 70000 "File Accounts" area(Processing) { - //TODO add file browser here action(MakeDefault) { ApplicationArea = All; @@ -151,6 +150,27 @@ page 70000 "File Accounts" CurrPage.Update(false); end; } + action(BrowseAccount) + { + ApplicationArea = All; + Image = SelectField; + Caption = 'Browse Account'; + ToolTip = 'Opens a File Browser and shows the content of the selected account.'; + Visible = (not IsInLookupMode) and CanUserManageFileSetup; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + PromotedIsBig = true; + Scope = Repeater; + + trigger OnAction() + begin + FileAccountImpl.BrowseAccount(Rec); + + UpdateAccounts := true; + CurrPage.Update(false); + end; + } action(Delete) { diff --git a/Modules/System/File Access/src/Connector/FileConnector.Interface.al b/Modules/System/File Access/src/Connector/FileConnector.Interface.al index 9930434f49..f3671f3d99 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Interface.al +++ b/Modules/System/File Access/src/Connector/FileConnector.Interface.al @@ -14,7 +14,7 @@ interface "File Connector" /// The file path to list. /// The file account ID which is used to get the file. /// A list with all files stored in the path. - procedure ListFiles(Path: Text; AccountId: Guid; var Files: List of [Text]); + procedure ListFiles(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary); /// /// Gets a file from the provided account. @@ -30,7 +30,7 @@ interface "File Connector" /// The file path inside the file account. /// The file account ID which is used to send out the file. /// The Stream were the file is read from. - procedure SetFile(Path: Text; AccountId: Guid; Stream: OutStream); + procedure SetFile(Path: Text; AccountId: Guid; Stream: InStream); /// /// Checks if a file exists on the provided account. @@ -53,7 +53,7 @@ interface "File Connector" /// The file path to list. /// The file account ID which is used to get the file. /// A list with all directories stored in the path. - procedure ListDirectories(Path: Text; AccountId: Guid; var Directiories: List of [Text]); + procedure ListDirectories(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary); /// /// Creates a directory on the provided account. diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al new file mode 100644 index 0000000000..00e8e0e857 --- /dev/null +++ b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al @@ -0,0 +1,152 @@ +page 70005 "File Account Browser" +{ + Caption = 'File Account Browser'; + PageType = List; + SourceTable = "File Account Content"; + Editable = false; + + layout + { + area(content) + { + repeater(General) + { + field(Name; Rec.Name) + { + DrillDown = true; + ApplicationArea = All; + ToolTip = 'Specifies the value of the Name field.'; + + trigger OnDrillDown() + begin + if Rec.Type = Rec.Type::Directory then + BrowseFolder(Rec) + else + DownloadFile(Rec); + end; + } + field("Type"; Rec."Type") + { + ApplicationArea = All; + ToolTip = 'Specifies the value of the Type field.'; + } + } + } + } + + actions + { + area(Processing) + { + action(Up) + { + Caption = 'Up'; + ApplicationArea = All; + Image = MoveUp; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + Enabled = ParentFolderExists; + + trigger OnAction() + var + Path: Text; + begin + if CurrPath = '' then + exit; + + if (CurrPath.TrimEnd(IFileConnector.PathSeparator()).Contains(IFileConnector.PathSeparator())) then + Path := CurrPath.TrimEnd(IFileConnector.PathSeparator()).Substring(1, CurrPath.LastIndexOf(IFileConnector.PathSeparator())); + + BrowseFolder(Path); + end; + } + action(Upload) + { + Caption = 'Upload'; + ApplicationArea = All; + Image = Import; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + + trigger OnAction() + begin + UploadFile(); + BrowseFolder(CurrPath); + end; + } + } + } + + var + IFileConnector: Interface "File Connector"; + AccountId: Guid; + CurrPath: Text; + ParentFolderExists: Boolean; + + procedure BrowseFileAccount(FileAccount: Record "File Account") + begin + BrowseFileAccount(FileAccount, ''); + end; + + procedure BrowseFileAccount(FileAccount: Record "File Account"; Path: Text) + begin + AccountId := FileAccount."Account Id"; + IFileConnector := FileAccount.Connector; + BrowseFolder(''); + end; + + local procedure BrowseFolder(var TempFileAccountContent: Record "File Account Content" temporary) + var + Path: Text; + begin + Path := CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name); + BrowseFolder(Path); + end; + + local procedure BrowseFolder(Path: Text) + var + Directiories: List of [Text]; + Files: List of [Text]; + Entry: Text; + begin + CurrPath := Path; + ParentFolderExists := CurrPath <> ''; + Rec.DeleteAll(); + IFileConnector.ListDirectories(Path, AccountId, Rec); + IFileConnector.ListFiles(Path, AccountId, Rec); + IF Rec.FindFirst() then; + end; + + local procedure CombinePath(ParentDirectory: Text; Name: Text): Text + begin + if ParentDirectory = '' then + exit(Name); + + if not ParentDirectory.EndsWith(IFileConnector.PathSeparator()) then + ParentDirectory += IFileConnector.PathSeparator(); + + exit(ParentDirectory + Name); + end; + + local procedure DownloadFile(var TempFileAccountContent: Record "File Account Content" temporary) + var + Stream: InStream; + begin + IFileConnector.GetFile(CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name), AccountId, Stream); + DownloadFromStream(Stream, '', '', '', TempFileAccountContent.Name); + end; + + local procedure UploadFile() + var + UploadDialogTxt: Label 'Upload File'; + FromFile: Text; + Stream: InStream; + begin + if not UploadIntoStream(UploadDialogTxt, '', '', FromFile, Stream) then + exit; + + IFileConnector.SetFile(CombinePath(CurrPath, FromFile), AccountId, Stream); + end; +} diff --git a/Modules/System/File Access/src/Lookup/FileAccountContent.Table.al b/Modules/System/File Access/src/Lookup/FileAccountContent.Table.al new file mode 100644 index 0000000000..b615a5a694 --- /dev/null +++ b/Modules/System/File Access/src/Lookup/FileAccountContent.Table.al @@ -0,0 +1,32 @@ +table 70005 "File Account Content" +{ + Caption = 'File Account Content'; + DataClassification = SystemMetadata; + TableType = Temporary; + + fields + { + field(1; "Type"; Enum "File Type") + { + Caption = 'Type'; + DataClassification = ToBeClassified; + } + field(2; Name; Text[2048]) + { + Caption = 'Name'; + DataClassification = ToBeClassified; + } + field(10; "Parent Directory"; Text[2048]) + { + Caption = 'Parent Directory'; + DataClassification = ToBeClassified; + } + } + keys + { + key(PK; "Type", Name) + { + Clustered = true; + } + } +} diff --git a/Modules/System/File Access/src/Lookup/FileType.Enum.al b/Modules/System/File Access/src/Lookup/FileType.Enum.al new file mode 100644 index 0000000000..e8ff45c6e5 --- /dev/null +++ b/Modules/System/File Access/src/Lookup/FileType.Enum.al @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +/// +/// Indicator of what type the resource is. +/// +enum 70002 "File Type" +{ + Access = Public; + Extensible = false; + + /// + /// Indicates if entry is a directory. + /// + value(0; Directory) + { + Caption = 'Directory', Locked = true; + } + + /// + /// Indicates if entry is a file type. + /// + value(1; File) + { + Caption = 'File', Locked = true; + } +} \ No newline at end of file From 25bc19cd43d7f3cbe60b65efe1d8efe10a04634b Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 5 May 2023 09:41:08 +0200 Subject: [PATCH 03/33] Add File System Codeunit --- .../src/Account/FileAccountImpl.Codeunit.al | 3 +- .../src/FileSystem/FileSystem.Codeunit.al | 91 ++++++++++ .../src/FileSystem/FileSystemImp.Codeunit.al | 165 ++++++++++++++++++ .../src/Lookup/FileAccountBrowser.Page.al | 123 +++++++++---- 4 files changed, 348 insertions(+), 34 deletions(-) create mode 100644 Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al create mode 100644 Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al diff --git a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al index 611ac353b5..ff0b761f82 100644 --- a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al +++ b/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al @@ -205,7 +205,8 @@ codeunit 70001 "File Account Impl." if IsNullGuid(FileAccount."Account Id") then exit; - FileAccountBrowser.BrowseFileAccount(FileAccount); + FileAccountBrowser.SetFileAcconut(FileAccount); + FileAccountBrowser.BrowseFileAccount(''); FileAccountBrowser.Run(); end; diff --git a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al new file mode 100644 index 0000000000..1ebd7cf218 --- /dev/null +++ b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al @@ -0,0 +1,91 @@ +codeunit 70004 "File System" +{ + var + FileSystemImpl: Codeunit "File System Impl."; + + procedure Initialize(Scenario: Enum "File Scenario") + begin + FileSystemImpl.Initialize(Scenario); + end; + + procedure Initialize(FileAccount: Record "File Account") + begin + FileSystemImpl.Initialize(FileAccount); + end; + + [TryFunction] + procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + begin + FileSystemImpl.ListFiles(Path, FileAccountContent); + end; + + [TryFunction] + procedure GetFile(Path: Text; Stream: InStream) + begin + FileSystemImpl.GetFile(Path, Stream); + end; + + [TryFunction] + procedure SetFile(Path: Text; Stream: InStream) + begin + FileSystemImpl.SetFile(Path, Stream); + end; + + procedure FileExists(Path: Text): Boolean + begin + exit(FileSystemImpl.FileExists(Path)); + end; + + [TryFunction] + procedure DeleteFile(Path: Text) + begin + FileSystemImpl.DeleteFile(Path); + end; + + [TryFunction] + procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + begin + FileSystemImpl.ListDirectories(Path, FileAccountContent); + end; + + [TryFunction] + procedure CreateDirectory(Path: Text) + begin + FileSystemImpl.CreateDirectory(Path); + end; + + procedure DirectoryExists(Path: Text): Boolean + begin + exit(FileSystemImpl.DirectoryExists(Path)); + end; + + procedure PathSeparator(): Text + begin + exit(FileSystemImpl.PathSeparator()); + end; + + procedure CombinePath(Path: Text; ChildPath: Text): Text + begin + Exit(FileSystemImpl.CombinePath(Path, ChildPath)); + end; + + procedure GetParentPath(Path: Text): Text + begin + exit(FileSystemImpl.GetParentPath(Path)); + end; + + procedure SelectFolderUI(Path: Text): Text + begin + exit(FileSystemImpl.SelectFolderUI(Path)); + end; + + procedure SelectFileUI(Path: Text; FileFilter: Text): Text + begin + exit(FileSystemImpl.SelectFileUI(Path, FileFilter)); + end; + + procedure SaveFileUI(Path: Text; FileExtension: Text): Text + begin + exit(FileSystemImpl.SaveFileUI(Path, FileExtension)); + end; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al new file mode 100644 index 0000000000..d35c4376b2 --- /dev/null +++ b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al @@ -0,0 +1,165 @@ +codeunit 70005 "File System Impl." +{ + var + IFileConnector: Interface "File Connector"; + CurrFileAccount: Record "File Account"; + Initialized: Boolean; + + internal procedure Initialize(Scenario: Enum "File Scenario") + var + FileAccount: Record "File Account"; + FileScenario: Codeunit "File Scenario"; + NoFileAccountFoundErr: Label 'No defaut file account defined.'; + begin + if not FileScenario.GetFileAccount(Scenario, FileAccount) then + Error(NoFileAccountFoundErr); + + Initialize(FileAccount); + end; + + internal procedure Initialize(FileAccount: Record "File Account") + begin + CurrFileAccount := FileAccount; + IFileConnector := FileAccount.Connector; + Initialized := true; + end; + + internal procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + begin + CheckInitialization(); + IFileConnector.ListFiles(Path, CurrFileAccount."Account Id", FileAccountContent); + end; + + internal procedure GetFile(Path: Text; Stream: InStream) + begin + CheckInitialization(); + IFileConnector.GetFile(Path, CurrFileAccount."Account Id", Stream); + end; + + internal procedure SetFile(Path: Text; Stream: InStream) + begin + CheckInitialization(); + IFileConnector.SetFile(Path, CurrFileAccount."Account Id", Stream); + end; + + internal procedure FileExists(Path: Text): Boolean + begin + CheckInitialization(); + exit(IFileConnector.FileExists(Path, CurrFileAccount."Account Id")); + end; + + internal procedure DeleteFile(Path: Text) + begin + CheckInitialization(); + IFileConnector.DeleteFile(Path, CurrFileAccount."Account Id"); + end; + + internal procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + begin + CheckInitialization(); + IFileConnector.ListDirectories(Path, CurrFileAccount."Account Id", FileAccountContent); + end; + + internal procedure CreateDirectory(Path: Text) + begin + CheckInitialization(); + IFileConnector.CreateDirectory(Path, CurrFileAccount."Account Id"); + end; + + internal procedure DirectoryExists(Path: Text): Boolean + begin + CheckInitialization(); + exit(IFileConnector.DirectoryExists(Path, CurrFileAccount."Account Id")); + end; + + internal procedure PathSeparator(): Text + begin + CheckInitialization(); + exit(IFileConnector.PathSeparator()); + end; + + internal procedure CombinePath(Path: Text; ChildPath: Text): Text + begin + if Path = '' then + exit(ChildPath); + + if not Path.EndsWith(PathSeparator()) then + Path += PathSeparator(); + + exit(Path + ChildPath); + end; + + internal procedure GetParentPath(Path: Text) ParentPath: Text + begin + if (Path.TrimEnd(PathSeparator()).Contains(PathSeparator())) then + ParentPath := Path.TrimEnd(PathSeparator()).Substring(1, Path.LastIndexOf(PathSeparator())); + end; + + internal procedure SelectFolderUI(Path: Text): Text + var + FileAccountContent: Record "File Account Content"; + FileAccountBrowser: Page "File Account Browser"; + begin + FileAccountBrowser.SetFileAcconut(CurrFileAccount); + FileAccountBrowser.EnableDirectoryLookupMode(Path); + if FileAccountBrowser.RunModal() <> Action::LookupOK then + exit(''); + + FileAccountBrowser.GetRecord(FileAccountContent); + if FileAccountContent.Type <> FileAccountContent.Type::Directory then + exit(''); + + exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); + end; + + internal procedure SelectFileUI(Path: Text; FileFilter: Text): Text + var + FileAccountContent: Record "File Account Content"; + FileAccountBrowser: Page "File Account Browser"; + begin + FileAccountBrowser.SetFileAcconut(CurrFileAccount); + FileAccountBrowser.EnableFileLookupMode(Path, FileFilter); + if FileAccountBrowser.RunModal() <> Action::LookupOK then + exit(''); + + FileAccountBrowser.GetRecord(FileAccountContent); + if FileAccountContent.Type <> FileAccountContent.Type::File then + exit(''); + + exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); + end; + + internal procedure SaveFileUI(Path: Text; FileExtension: Text): Text + var + FileAccountContent: Record "File Account Content"; + FileAccountBrowser: Page "File Account Browser"; + FileName, FileNameWithExtenion : Text; + PleaseProvideFileExtensionErr: Label 'Please provide a valid file extension.'; + FileNameTok: Label '%1.%2', Locked = true; + begin + if FileExtension = '' then + Error(PleaseProvideFileExtensionErr); + + FileAccountBrowser.SetFileAcconut(CurrFileAccount); + FileAccountBrowser.EnableSaveFileLookupMode(Path, FileExtension); + if FileAccountBrowser.RunModal() <> Action::LookupOK then + exit(''); + + FileName := FileAccountBrowser.GetFileName(); + if FileName = '' then + exit(''); + + FileNameWithExtenion := StrSubstNo(FileNameTok, FileName, FileExtension); + exit(CombinePath(FileAccountBrowser.GetCurrentDirectory(), FileNameWithExtenion)); + end; + + local procedure CheckInitialization() + var + NotInitializedErr: Label 'Please call Initalize() first.'; + begin + if Initialized then + exit; + + Error(NotInitializedErr); + end; +} \ No newline at end of file diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al index 00e8e0e857..b022ca2bdb 100644 --- a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al @@ -3,7 +3,9 @@ page 70005 "File Account Browser" Caption = 'File Account Browser'; PageType = List; SourceTable = "File Account Content"; - Editable = false; + ModifyAllowed = false; + InsertAllowed = false; + DeleteAllowed = false; layout { @@ -22,7 +24,8 @@ page 70005 "File Account Browser" if Rec.Type = Rec.Type::Directory then BrowseFolder(Rec) else - DownloadFile(Rec); + if not IsInLookupMode then + DownloadFile(Rec); end; } field("Type"; Rec."Type") @@ -31,6 +34,18 @@ page 70005 "File Account Browser" ToolTip = 'Specifies the value of the Type field.'; } } + + group(SaveFileNameGroup) + { + Caption = '', Locked = true; + ShowCaption = false; + Visible = ShowFileName; + + field(SaveFileNameField; SaveFileName) + { + Caption = 'Filename'; + } + } } } @@ -55,9 +70,7 @@ page 70005 "File Account Browser" if CurrPath = '' then exit; - if (CurrPath.TrimEnd(IFileConnector.PathSeparator()).Contains(IFileConnector.PathSeparator())) then - Path := CurrPath.TrimEnd(IFileConnector.PathSeparator()).Substring(1, CurrPath.LastIndexOf(IFileConnector.PathSeparator())); - + Path := FileSystem.GetParentPath(CurrPath); BrowseFolder(Path); end; } @@ -69,6 +82,8 @@ page 70005 "File Account Browser" Promoted = true; PromotedOnly = true; PromotedCategory = Process; + Visible = not IsInLookupMode; + Enabled = not IsInLookupMode; trigger OnAction() begin @@ -80,61 +95,89 @@ page 70005 "File Account Browser" } var - IFileConnector: Interface "File Connector"; - AccountId: Guid; - CurrPath: Text; - ParentFolderExists: Boolean; + FileSystem: Codeunit "File System"; + CurrPath, CurrFileFilter, SaveFileName : Text; + ParentFolderExists, DoNotLoadFields, IsInLookupMode, ShowFileName : Boolean; - procedure BrowseFileAccount(FileAccount: Record "File Account") + internal procedure SetFileAcconut(FileAccount: Record "File Account") begin - BrowseFileAccount(FileAccount, ''); + FileSystem.Initialize(FileAccount); end; - procedure BrowseFileAccount(FileAccount: Record "File Account"; Path: Text) + internal procedure BrowseFileAccount(Path: Text) begin - AccountId := FileAccount."Account Id"; - IFileConnector := FileAccount.Connector; BrowseFolder(''); end; + internal procedure EnableFileLookupMode(Path: Text; FileFilter: Text) + begin + CurrFileFilter := FileFilter; + ApplyFileFilter(); + EnableLookupMode(); + BrowseFolder(Path); + end; + + internal procedure EnableDirectoryLookupMode(Path: Text) + begin + DoNotLoadFields := true; + EnableLookupMode(); + BrowseFolder(Path); + end; + + internal procedure EnableSaveFileLookupMode(Path: Text; FileExtension: Text) + var + FileFilterTok: Label '*.%1', Locked = true; + begin + ShowFileName := true; + EnableLookupMode(); + BrowseFolder(Path); + CurrFileFilter := StrSubstNo(FileFilterTok, FileExtension); + ApplyFileFilter(); + end; + + internal procedure GetCurrentDirectory(): Text + begin + exit(CurrPath); + end; + + internal procedure GetFileName(): Text + begin + exit(SaveFileName); + end; + + local procedure EnableLookupMode() + begin + IsInLookupMode := true; + CurrPage.LookupMode(true); + end; + local procedure BrowseFolder(var TempFileAccountContent: Record "File Account Content" temporary) var Path: Text; begin - Path := CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name); + Path := FileSystem.CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name); BrowseFolder(Path); end; local procedure BrowseFolder(Path: Text) - var - Directiories: List of [Text]; - Files: List of [Text]; - Entry: Text; begin CurrPath := Path; ParentFolderExists := CurrPath <> ''; Rec.DeleteAll(); - IFileConnector.ListDirectories(Path, AccountId, Rec); - IFileConnector.ListFiles(Path, AccountId, Rec); - IF Rec.FindFirst() then; - end; - - local procedure CombinePath(ParentDirectory: Text; Name: Text): Text - begin - if ParentDirectory = '' then - exit(Name); - if not ParentDirectory.EndsWith(IFileConnector.PathSeparator()) then - ParentDirectory += IFileConnector.PathSeparator(); + FileSystem.ListDirectories(Path, Rec); + if not DoNotLoadFields then + FileSystem.ListFiles(Path, Rec); - exit(ParentDirectory + Name); + ApplyFileFilter(); + if Rec.FindFirst() then; end; local procedure DownloadFile(var TempFileAccountContent: Record "File Account Content" temporary) var Stream: InStream; begin - IFileConnector.GetFile(CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name), AccountId, Stream); + FileSystem.GetFile(FileSystem.CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name), Stream); DownloadFromStream(Stream, '', '', '', TempFileAccountContent.Name); end; @@ -147,6 +190,20 @@ page 70005 "File Account Browser" if not UploadIntoStream(UploadDialogTxt, '', '', FromFile, Stream) then exit; - IFileConnector.SetFile(CombinePath(CurrPath, FromFile), AccountId, Stream); + FileSystem.SetFile(FileSystem.CombinePath(CurrPath, FromFile), Stream); + end; + + local procedure ApplyFileFilter() + var + CurrFilterGroup: Integer; + begin + if CurrFileFilter = '' then + exit; + + CurrFilterGroup := Rec.FilterGroup(); + Rec.FilterGroup(-1); + Rec.SetRange(Type, Type::Directory); + Rec.SetFilter(Name, CurrFileFilter); + Rec.FilterGroup(CurrFilterGroup); end; } From 959f05e33042bdb54925273e93b4d2b56b597638 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 5 May 2023 12:06:56 +0200 Subject: [PATCH 04/33] Add documentation and small changes --- .../src/BlobStorageConnectorImpl.Codeunit.al | 103 ++++++++++-- .../src/Connector/FileConnector.Interface.al | 52 ++++-- .../src/FileSystem/FileSystem.Codeunit.al | 159 +++++++++++++++++- .../src/FileSystem/FileSystemImp.Codeunit.al | 44 +++-- .../src/Lookup/FileAccountBrowser.Page.al | 55 ++++-- 5 files changed, 347 insertions(+), 66 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 5d3054e740..4d001dc3c5 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -16,10 +16,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Gets a List of Files stored on the provided account. /// - /// The file path to list. /// The file account ID which is used to get the file. + /// The file path to list. /// A list with all files stored in the path. - procedure ListFiles(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary) + procedure ListFiles(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary) var ABSContainerContent: Record "ABS Container Content"; ABSBlobClient: Codeunit "ABS Blob Client"; @@ -52,10 +52,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Gets a file from the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to get the file. + /// The file path inside the file account. /// The Stream were the file is read to. - procedure GetFile(Path: Text; AccountId: Guid; Stream: InStream) + procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream) var ABSBlobClient: Codeunit "ABS Blob Client"; ABSOperationResponse: Codeunit "ABS Operation Response"; @@ -72,10 +72,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Gets a file to the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. + /// The file path inside the file account. /// The Stream were the file is read from. - procedure SetFile(Path: Text; AccountId: Guid; Stream: InStream) + procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream) var ABSBlobClient: Codeunit "ABS Blob Client"; ABSOperationResponse: Codeunit "ABS Operation Response"; @@ -89,26 +89,78 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" Error(ABSOperationResponse.GetError()); end; + /// + /// Copies as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + begin + InitBlobClient(AccountId, ABSBlobClient); + ABSOperationResponse := ABSBlobClient.CopyBlob(TargetPath, SourcePath); + + if ABSOperationResponse.IsSuccessful() then + exit; + + Error(ABSOperationResponse.GetError()); + end; + + /// + /// Move as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + begin + InitBlobClient(AccountId, ABSBlobClient); + ABSOperationResponse := ABSBlobClient.CopyBlob(TargetPath, SourcePath); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + ABSOperationResponse := ABSBlobClient.DeleteBlob(SourcePath); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + end; + /// /// Checks if a file exists on the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. + /// The file path inside the file account. /// Returns true if the file exists - procedure FileExists(Path: Text; AccountId: Guid): Boolean + procedure FileExists(AccountId: Guid; Path: Text): Boolean var ABSBlobClient: Codeunit "ABS Blob Client"; + ABSContainerContent: Record "ABS Container Content"; ABSOperationResponse: Codeunit "ABS Operation Response"; + ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin + if Path = '' then + exit(false); + InitBlobClient(AccountId, ABSBlobClient); + ABSOptionalParameters.Prefix(Path); + ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + exit(not ABSContainerContent.IsEmpty()); end; /// /// Deletes a file exists on the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. - procedure DeleteFile(Path: Text; AccountId: Guid) + /// The file path inside the file account. + procedure DeleteFile(AccountId: Guid; Path: Text) var ABSBlobClient: Codeunit "ABS Blob Client"; ABSOperationResponse: Codeunit "ABS Operation Response"; @@ -125,10 +177,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Gets a List of Directories stored on the provided account. /// - /// The file path to list. /// The file account ID which is used to get the file. + /// The file path to list. /// A list with all directories stored in the path. - procedure ListDirectories(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary) + procedure ListDirectories(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary) var ABSContainerContent: Record "ABS Container Content"; ABSBlobClient: Codeunit "ABS Blob Client"; @@ -163,9 +215,9 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Creates a directory on the provided account. /// - /// The directory path inside the file account. /// The file account ID which is used to send out the file. - procedure CreateDirectory(Path: Text; AccountId: Guid) + /// The directory path inside the file account. + procedure CreateDirectory(AccountId: Guid; Path: Text) begin end; @@ -173,20 +225,35 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// Checks if a directory exists on the provided account. /// - /// The directory path inside the file account. /// The file account ID which is used to send out the file. + /// The directory path inside the file account. /// Returns true if the directory exists - procedure DirectoryExists(Path: Text; AccountId: Guid): Boolean + procedure DirectoryExists(AccountId: Guid; Path: Text): Boolean + var + ABSBlobClient: Codeunit "ABS Blob Client"; + ABSContainerContent: Record "ABS Container Content"; + ABSOperationResponse: Codeunit "ABS Operation Response"; + ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin + if Path = '' then + exit(true); + InitBlobClient(AccountId, ABSBlobClient); + ABSOptionalParameters.Prefix(Path); + ABSOptionalParameters.MaxResults(1); + ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + exit(not ABSContainerContent.IsEmpty()); end; /// /// Deletes a directory exists on the provided account. /// - /// The directory path inside the file account. /// The file account ID which is used to send out the file. - procedure DeleteDirectory(Path: Text; AccountId: Guid) + /// The directory path inside the file account. + procedure DeleteDirectory(AccountId: Guid; Path: Text) begin end; diff --git a/Modules/System/File Access/src/Connector/FileConnector.Interface.al b/Modules/System/File Access/src/Connector/FileConnector.Interface.al index f3671f3d99..93f723ad8b 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Interface.al +++ b/Modules/System/File Access/src/Connector/FileConnector.Interface.al @@ -11,71 +11,89 @@ interface "File Connector" /// /// Gets a List of Files stored on the provided account. /// - /// The file path to list. /// The file account ID which is used to get the file. + /// The file path to list. /// A list with all files stored in the path. - procedure ListFiles(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary); + procedure ListFiles(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary); /// /// Gets a file from the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to get the file. + /// The file path inside the file account. /// The Stream were the file is read to. - procedure GetFile(Path: Text; AccountId: Guid; Stream: InStream); + procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream); /// /// Gets a file to the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. + /// The file path inside the file account. /// The Stream were the file is read from. - procedure SetFile(Path: Text; AccountId: Guid; Stream: InStream); + procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream); + + + /// + /// Copies as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text); + + + /// + /// Move as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text); /// /// Checks if a file exists on the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. + /// The file path inside the file account. /// Returns true if the file exists - procedure FileExists(Path: Text; AccountId: Guid): Boolean; + procedure FileExists(AccountId: Guid; Path: Text): Boolean; /// /// Deletes a file exists on the provided account. /// - /// The file path inside the file account. /// The file account ID which is used to send out the file. - procedure DeleteFile(Path: Text; AccountId: Guid); + /// The file path inside the file account. + procedure DeleteFile(AccountId: Guid; Path: Text); /// /// Gets a List of Directories stored on the provided account. /// - /// The file path to list. /// The file account ID which is used to get the file. + /// The file path to list. /// A list with all directories stored in the path. - procedure ListDirectories(Path: Text; AccountId: Guid; var FileAccountContent: Record "File Account Content" temporary); + procedure ListDirectories(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary); /// /// Creates a directory on the provided account. /// /// The directory path inside the file account. /// The file account ID which is used to send out the file. - procedure CreateDirectory(Path: Text; AccountId: Guid); + procedure CreateDirectory(AccountId: Guid; Path: Text); /// /// Checks if a directory exists on the provided account. /// - /// The directory path inside the file account. /// The file account ID which is used to send out the file. + /// The directory path inside the file account. /// Returns true if the directory exists - procedure DirectoryExists(Path: Text; AccountId: Guid): Boolean; + procedure DirectoryExists(AccountId: Guid; Path: Text): Boolean; /// /// Deletes a directory exists on the provided account. /// - /// The directory path inside the file account. /// The file account ID which is used to send out the file. - procedure DeleteDirectory(Path: Text; AccountId: Guid); + /// The directory path inside the file account. + procedure DeleteDirectory(AccountId: Guid; Path: Text); /// /// Returns the path separator of the file account. diff --git a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al index 1ebd7cf218..e8500026b8 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al @@ -3,89 +3,240 @@ codeunit 70004 "File System" var FileSystemImpl: Codeunit "File System Impl."; + /// + /// Initialized the File System for the give scenario. + /// + /// File Scenario to use. procedure Initialize(Scenario: Enum "File Scenario") begin FileSystemImpl.Initialize(Scenario); end; + /// + /// Initialized the File System for the give file account. + /// + /// File Account to use. procedure Initialize(FileAccount: Record "File Account") begin FileSystemImpl.Initialize(FileAccount); end; + /// + /// List all files from the given path. + /// + /// Folder to list + /// File account content. [TryFunction] procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) begin FileSystemImpl.ListFiles(Path, FileAccountContent); end; + /// + /// Retireves a file from the file account. + /// + /// File Path to open. + /// Stream which contains the file content. [TryFunction] procedure GetFile(Path: Text; Stream: InStream) begin FileSystemImpl.GetFile(Path, Stream); end; + /// + /// Stores a file in to the file account. + /// + /// File Path inside the file account. + /// Stream to store. [TryFunction] procedure SetFile(Path: Text; Stream: InStream) begin FileSystemImpl.SetFile(Path, Stream); end; + /// + /// Copies a file in the file account. + /// + /// Source path of the file. + /// Target Path of the file copy. + [TryFunction] + procedure CopyFile(SourcePath: Text; TargetPath: Text) + begin + FileSystemImpl.CopyFile(SourcePath, TargetPath); + end; + + /// + /// Moves a file in the file account. + /// + /// Source path of the file. + /// Target Path of the file. + [TryFunction] + procedure MoveFile(SourcePath: Text; TargetPath: Text) + begin + FileSystemImpl.MoveFile(SourcePath, TargetPath); + end; + + /// + /// Checks if a specific file exists in the file account. + /// + /// File path to check. + /// Returns true if the file exists. procedure FileExists(Path: Text): Boolean begin exit(FileSystemImpl.FileExists(Path)); end; + /// + /// Deletes a file from the file account. + /// + /// File path of the file to delete. [TryFunction] procedure DeleteFile(Path: Text) begin FileSystemImpl.DeleteFile(Path); end; + /// + /// List all directories from the given path. + /// + /// Folder to list + /// File account content. [TryFunction] procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) begin FileSystemImpl.ListDirectories(Path, FileAccountContent); end; + /// + /// Creates a directory in the file account. + /// + /// Path of the new Directory to create. [TryFunction] procedure CreateDirectory(Path: Text) begin FileSystemImpl.CreateDirectory(Path); end; + /// + /// Checks if a specific directory exists in the file account. + /// + /// Path of the directry to check. + /// Returns true if directory exists. procedure DirectoryExists(Path: Text): Boolean begin exit(FileSystemImpl.DirectoryExists(Path)); end; + /// + /// Deletes a directory from the file account. + /// + /// Directory to remove. + [TryFunction] + procedure DeleteDirectory(Path: Text) + begin + FileSystemImpl.DeleteDirectory(Path); + end; + + /// + /// Returns the path separator used by the file account system. + /// + /// procedure PathSeparator(): Text begin exit(FileSystemImpl.PathSeparator()); end; + /// + /// Combines to paths together. + /// + /// First part to combine. + /// Second part to combine. + /// Correctly combined path. procedure CombinePath(Path: Text; ChildPath: Text): Text begin Exit(FileSystemImpl.CombinePath(Path, ChildPath)); end; + /// + /// Gets the Parent Path of the given path. + /// + /// File or directoy path. + /// The parent of the speicfied path. procedure GetParentPath(Path: Text): Text begin exit(FileSystemImpl.GetParentPath(Path)); end; + /// + /// Opens a folder selection dialog. + /// + /// Start path of the dialog. + /// Returns the selected Folder. procedure SelectFolderUI(Path: Text): Text + var + DefaulSelectFolderUILbl: Label 'Select a folder'; + begin + exit(SelectFolderUI(Path, DefaulSelectFolderUILbl)); + end; + + /// + /// Opens a folder selection dialog. + /// + /// Start path of the dialog. + /// Title of the selection dialog. + /// Returns the selected Folder. + procedure SelectFolderUI(Path: Text; DialogTitle: Text): Text begin - exit(FileSystemImpl.SelectFolderUI(Path)); + exit(FileSystemImpl.SelectFolderUI(Path, DialogTitle)); end; + /// + /// Opens a select file dialog. + /// + /// Start path. + /// A filter string that applies only on files not on folders. + /// Returns the path of the selected file. procedure SelectFileUI(Path: Text; FileFilter: Text): Text + var + DefaulSelectFileUILbl: Label 'Select a file'; + begin + exit(SelectFileUI(Path, FileFilter, DefaulSelectFileUILbl)); + end; + + /// + /// Opens a select file dialog. + /// + /// Start path of the dialog. + /// A filter string that applies only on files not on folders. + /// Title of the selection dialog. + /// Returns the path of the selected file. + procedure SelectFileUI(Path: Text; FileFilter: Text; DialogTitle: Text): Text begin - exit(FileSystemImpl.SelectFileUI(Path, FileFilter)); + exit(FileSystemImpl.SelectFileUI(Path, FileFilter, DialogTitle)); end; + /// + /// Opens a save to dialog. + /// + /// Start path of the dialog. + /// The fileextion without dot (like pdf or txt). + /// Returns the selecte file path. procedure SaveFileUI(Path: Text; FileExtension: Text): Text + var + DefaultSaveFileUITitleLbl: Label 'Save as'; + begin + exit(SaveFileUI(Path, FileExtension, DefaultSaveFileUITitleLbl)); + end; + + /// + /// Opens a save to dialog. + /// + /// Start path of the dialog. + /// The fileextion without dot (like pdf or txt). + /// Title of the selection dialog. + /// Returns the selecte file path. + procedure SaveFileUI(Path: Text; FileExtension: Text; DialogTitle: Text): Text begin - exit(FileSystemImpl.SaveFileUI(Path, FileExtension)); + exit(FileSystemImpl.SaveFileUI(Path, FileExtension, DialogTitle)); end; -} \ No newline at end of file +} diff --git a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al index d35c4376b2..e0d4a39132 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al @@ -27,49 +27,68 @@ codeunit 70005 "File System Impl." internal procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) begin CheckInitialization(); - IFileConnector.ListFiles(Path, CurrFileAccount."Account Id", FileAccountContent); + IFileConnector.ListFiles(CurrFileAccount."Account Id", Path, FileAccountContent); end; internal procedure GetFile(Path: Text; Stream: InStream) begin CheckInitialization(); - IFileConnector.GetFile(Path, CurrFileAccount."Account Id", Stream); + IFileConnector.GetFile(CurrFileAccount."Account Id", Path, Stream); end; internal procedure SetFile(Path: Text; Stream: InStream) begin CheckInitialization(); - IFileConnector.SetFile(Path, CurrFileAccount."Account Id", Stream); + IFileConnector.SetFile(CurrFileAccount."Account Id", Path, Stream); + end; + + + internal procedure CopyFile(SourcePath: Text; TargetPath: Text) + begin + CheckInitialization(); + IFileConnector.CopyFile(CurrFileAccount."Account Id", SourcePath, TargetPath); + end; + + internal procedure MoveFile(SourcePath: Text; TargetPath: Text) + begin + CheckInitialization(); + IFileConnector.MoveFile(CurrFileAccount."Account Id", SourcePath, TargetPath); end; internal procedure FileExists(Path: Text): Boolean begin CheckInitialization(); - exit(IFileConnector.FileExists(Path, CurrFileAccount."Account Id")); + exit(IFileConnector.FileExists(CurrFileAccount."Account Id", Path)); end; internal procedure DeleteFile(Path: Text) begin CheckInitialization(); - IFileConnector.DeleteFile(Path, CurrFileAccount."Account Id"); + IFileConnector.DeleteFile(CurrFileAccount."Account Id", Path); end; internal procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) begin CheckInitialization(); - IFileConnector.ListDirectories(Path, CurrFileAccount."Account Id", FileAccountContent); + IFileConnector.ListDirectories(CurrFileAccount."Account Id", Path, FileAccountContent); end; internal procedure CreateDirectory(Path: Text) begin CheckInitialization(); - IFileConnector.CreateDirectory(Path, CurrFileAccount."Account Id"); + IFileConnector.CreateDirectory(CurrFileAccount."Account Id", Path); end; internal procedure DirectoryExists(Path: Text): Boolean begin CheckInitialization(); - exit(IFileConnector.DirectoryExists(Path, CurrFileAccount."Account Id")); + exit(IFileConnector.DirectoryExists(CurrFileAccount."Account Id", Path)); + end; + + internal procedure DeleteDirectory(Path: Text) + begin + CheckInitialization(); + IFileConnector.DeleteDirectory(CurrFileAccount."Account Id", Path); end; internal procedure PathSeparator(): Text @@ -95,11 +114,12 @@ codeunit 70005 "File System Impl." ParentPath := Path.TrimEnd(PathSeparator()).Substring(1, Path.LastIndexOf(PathSeparator())); end; - internal procedure SelectFolderUI(Path: Text): Text + internal procedure SelectFolderUI(Path: Text; DialogTitle: Text): Text var FileAccountContent: Record "File Account Content"; FileAccountBrowser: Page "File Account Browser"; begin + FileAccountBrowser.SetPageCaption(DialogTitle); FileAccountBrowser.SetFileAcconut(CurrFileAccount); FileAccountBrowser.EnableDirectoryLookupMode(Path); if FileAccountBrowser.RunModal() <> Action::LookupOK then @@ -112,11 +132,12 @@ codeunit 70005 "File System Impl." exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); end; - internal procedure SelectFileUI(Path: Text; FileFilter: Text): Text + internal procedure SelectFileUI(Path: Text; FileFilter: Text; DialogTitle: Text): Text var FileAccountContent: Record "File Account Content"; FileAccountBrowser: Page "File Account Browser"; begin + FileAccountBrowser.SetPageCaption(DialogTitle); FileAccountBrowser.SetFileAcconut(CurrFileAccount); FileAccountBrowser.EnableFileLookupMode(Path, FileFilter); if FileAccountBrowser.RunModal() <> Action::LookupOK then @@ -129,7 +150,7 @@ codeunit 70005 "File System Impl." exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); end; - internal procedure SaveFileUI(Path: Text; FileExtension: Text): Text + internal procedure SaveFileUI(Path: Text; FileExtension: Text; DialogTitle: Text): Text var FileAccountContent: Record "File Account Content"; FileAccountBrowser: Page "File Account Browser"; @@ -140,6 +161,7 @@ codeunit 70005 "File System Impl." if FileExtension = '' then Error(PleaseProvideFileExtensionErr); + FileAccountBrowser.SetPageCaption(DialogTitle); FileAccountBrowser.SetFileAcconut(CurrFileAccount); FileAccountBrowser.EnableSaveFileLookupMode(Path, FileExtension); if FileAccountBrowser.RunModal() <> Action::LookupOK then diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al index b022ca2bdb..50626d9813 100644 --- a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al @@ -96,9 +96,15 @@ page 70005 "File Account Browser" var FileSystem: Codeunit "File System"; - CurrPath, CurrFileFilter, SaveFileName : Text; + CurrPath, CurrFileFilter, SaveFileName, CurrPageCaption : Text; ParentFolderExists, DoNotLoadFields, IsInLookupMode, ShowFileName : Boolean; + trigger OnOpenPage() + begin + if CurrPageCaption <> '' then + CurrPage.Caption(CurrPageCaption); + end; + internal procedure SetFileAcconut(FileAccount: Record "File Account") begin FileSystem.Initialize(FileAccount); @@ -112,7 +118,6 @@ page 70005 "File Account Browser" internal procedure EnableFileLookupMode(Path: Text; FileFilter: Text) begin CurrFileFilter := FileFilter; - ApplyFileFilter(); EnableLookupMode(); BrowseFolder(Path); end; @@ -129,10 +134,9 @@ page 70005 "File Account Browser" FileFilterTok: Label '*.%1', Locked = true; begin ShowFileName := true; + CurrFileFilter := StrSubstNo(FileFilterTok, FileExtension); EnableLookupMode(); BrowseFolder(Path); - CurrFileFilter := StrSubstNo(FileFilterTok, FileExtension); - ApplyFileFilter(); end; internal procedure GetCurrentDirectory(): Text @@ -145,6 +149,20 @@ page 70005 "File Account Browser" exit(SaveFileName); end; + + internal procedure SetPageCaption(NewCaption: Text) + begin + CurrPageCaption := NewCaption; + end; + + local procedure StripNotsupportChrInFileName(InText: Text): Text + var + InvalidChrStringTxt: Label '"#%&*:<>?\/{|}~', Locked = true; + begin + InText := DelChr(InText, '=', InvalidChrStringTxt); + exit(InText); + end; + local procedure EnableLookupMode() begin IsInLookupMode := true; @@ -162,14 +180,12 @@ page 70005 "File Account Browser" local procedure BrowseFolder(Path: Text) begin CurrPath := Path; - ParentFolderExists := CurrPath <> ''; + ParentFolderExists := FileSystem.GetParentPath(Path) <> ''; Rec.DeleteAll(); FileSystem.ListDirectories(Path, Rec); - if not DoNotLoadFields then - FileSystem.ListFiles(Path, Rec); + ListFiles(Path); - ApplyFileFilter(); if Rec.FindFirst() then; end; @@ -193,17 +209,24 @@ page 70005 "File Account Browser" FileSystem.SetFile(FileSystem.CombinePath(CurrPath, FromFile), Stream); end; - local procedure ApplyFileFilter() + local procedure ListFiles(var Path: Text) var - CurrFilterGroup: Integer; + FileAccountContent: Record "File Account Content" temporary; begin - if CurrFileFilter = '' then + if DoNotLoadFields then + exit; + + FileSystem.ListFiles(Path, FileAccountContent); + if CurrFileFilter <> '' then + FileAccountContent.SetFilter(Name, CurrFileFilter); + + if not FileAccountContent.FindSet() then exit; - CurrFilterGroup := Rec.FilterGroup(); - Rec.FilterGroup(-1); - Rec.SetRange(Type, Type::Directory); - Rec.SetFilter(Name, CurrFileFilter); - Rec.FilterGroup(CurrFilterGroup); + repeat + Rec.Init(); + Rec.TransferFields(FileAccountContent); + Rec.Insert(); + until FileAccountContent.Next() = 0; end; } From 4bb40801c24e8d1d95d48f230861853f5a625b6a Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 5 May 2023 12:34:51 +0200 Subject: [PATCH 05/33] Fix up action --- .../System/File Access/src/Lookup/FileAccountBrowser.Page.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al index 50626d9813..662a00a34f 100644 --- a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al @@ -180,7 +180,7 @@ page 70005 "File Account Browser" local procedure BrowseFolder(Path: Text) begin CurrPath := Path; - ParentFolderExists := FileSystem.GetParentPath(Path) <> ''; + ParentFolderExists := Path <> ''; Rec.DeleteAll(); FileSystem.ListDirectories(Path, Rec); From 4acb5f1865afe08240decfb1a2c441478e59adbb Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 09:17:59 +0200 Subject: [PATCH 06/33] Remove all indataset --- .../System/File Access/src/Account/FileAccountWizard.Page.al | 2 -- Modules/System/File Access/src/Account/FileAccounts.Page.al | 2 -- 2 files changed, 4 deletions(-) diff --git a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al index 849fe6105f..18592f8836 100644 --- a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al +++ b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al @@ -501,9 +501,7 @@ page 70001 "File Account Wizard" AccountCreationSuccessfullyCompletedDurationLbl: Label 'Successful creation of account completed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; AccountCreationFailureDurationLbl: Label 'Creation of account failed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; FileConnectorHasBeenUninstalledMsg: Label 'The selected file extension has been uninstalled. You must reinstall the extension to add an account with it.'; - [InDataSet] AppSourceAvailable: Boolean; - [InDataSet] TopBannerVisible: Boolean; BackActionVisible: Boolean; BackActionEnabled: Boolean; diff --git a/Modules/System/File Access/src/Account/FileAccounts.Page.al b/Modules/System/File Access/src/Account/FileAccounts.Page.al index 58c82e14ae..f50e0cf8e3 100644 --- a/Modules/System/File Access/src/Account/FileAccounts.Page.al +++ b/Modules/System/File Access/src/Account/FileAccounts.Page.al @@ -317,9 +317,7 @@ page 70000 "File Accounts" var DefaultFileAccount: Record "File Account"; FileAccountImpl: Codeunit "File Account Impl."; - [InDataSet] IsDefault: Boolean; - [InDataSet] CanUserManageFileSetup: Boolean; DefaultTxt: Text; UpdateAccounts: Boolean; From 74c5ecee40736cbaa7b30ad7ba2632241e1aad6d Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 09:21:31 +0200 Subject: [PATCH 07/33] Store Assisted Setup Image in a locked label --- .../File Access/src/Account/FileAccountWizard.Page.al | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al index 18592f8836..b1fc23430b 100644 --- a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al +++ b/Modules/System/File Access/src/Account/FileAccountWizard.Page.al @@ -477,9 +477,11 @@ page 70001 "File Account Wizard" end; local procedure LoadTopBanners() + var + AssistedSetupLogoTok: Label 'ASSISTEDSETUP-NOTEXT-400PX.PNG', Locked = true; begin - if MediaResourcesStandard.Get('ASSISTEDSETUP-NOTEXT-400PX.PNG') and - MediaResourcesDone.Get('ASSISTEDSETUPDONE-NOTEXT-400PX.PNG') and (CurrentClientType() = ClientType::Web) + if MediaResourcesStandard.Get(AssistedSetupLogoTok) and + MediaResourcesDone.Get(AssistedSetupLogoTok) and (CurrentClientType() = ClientType::Web) then TopBannerVisible := MediaResourcesDone."Media Reference".HasValue(); end; From f7af2e70401e07524e5d67b17d4a75c4d74d29b9 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 11:03:27 +0200 Subject: [PATCH 08/33] Add review changes --- .../app/src/BlobStorageAccount.Page.al | 10 ---------- .../app/src/BlobStorageAccountWizard.Page.al | 5 +++-- .../src/FileSystem/FileSystem.Codeunit.al | 8 ++++++++ .../src/FileSystem/FileSystemImp.Codeunit.al | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al index 0f76a52ace..c47f0339db 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al @@ -59,19 +59,9 @@ page 80100 "Blob Storage Account" } } - actions - { - area(processing) - { - //TODO Add File Browser - } - } - var - [InDataSet] PasswordEditable: Boolean; [NonDebuggable] - [InDataSet] Password: Text; trigger OnOpenPage() diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al index 7e6c361819..520a41780b 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al @@ -135,17 +135,18 @@ page 80101 "Blob Storage Account Wizard" MediaResources: Record "Media Resources"; BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; [NonDebuggable] - [InDataSet] Password: Text; IsNextEnabled: Boolean; TopBannerVisible: Boolean; trigger OnOpenPage() + var + AssistedSetupLogoTok: Label 'ASSISTEDSETUP-NOTEXT-400PX.PNG', Locked = true; begin Rec.Init(); Rec.Insert(); - if MediaResources.Get('ASSISTEDSETUP-NOTEXT-400PX.PNG') and (CurrentClientType() = ClientType::Web) then + if MediaResources.Get(AssistedSetupLogoTok) and (CurrentClientType() = ClientType::Web) then TopBannerVisible := MediaResources."Media Reference".HasValue(); end; diff --git a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al index e8500026b8..fe1ea9221e 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al @@ -239,4 +239,12 @@ codeunit 70004 "File System" begin exit(FileSystemImpl.SaveFileUI(Path, FileExtension, DialogTitle)); end; + + /// + /// Opens a File Browser + /// + procedure BrowseAccount() + begin + FileSystemImpl.BrowseAccount(); + end; } diff --git a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al index e0d4a39132..136fc0c59e 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al @@ -119,6 +119,8 @@ codeunit 70005 "File System Impl." FileAccountContent: Record "File Account Content"; FileAccountBrowser: Page "File Account Browser"; begin + CheckInitialization(); + FileAccountBrowser.SetPageCaption(DialogTitle); FileAccountBrowser.SetFileAcconut(CurrFileAccount); FileAccountBrowser.EnableDirectoryLookupMode(Path); @@ -137,6 +139,8 @@ codeunit 70005 "File System Impl." FileAccountContent: Record "File Account Content"; FileAccountBrowser: Page "File Account Browser"; begin + CheckInitialization(); + FileAccountBrowser.SetPageCaption(DialogTitle); FileAccountBrowser.SetFileAcconut(CurrFileAccount); FileAccountBrowser.EnableFileLookupMode(Path, FileFilter); @@ -158,6 +162,8 @@ codeunit 70005 "File System Impl." PleaseProvideFileExtensionErr: Label 'Please provide a valid file extension.'; FileNameTok: Label '%1.%2', Locked = true; begin + CheckInitialization(); + if FileExtension = '' then Error(PleaseProvideFileExtensionErr); @@ -175,6 +181,14 @@ codeunit 70005 "File System Impl." exit(CombinePath(FileAccountBrowser.GetCurrentDirectory(), FileNameWithExtenion)); end; + internal procedure BrowseAccount() + var + FileAccountImpl: Codeunit "File Account Impl."; + begin + CheckInitialization(); + FileAccountImpl.BrowseAccount(CurrFileAccount); + end; + local procedure CheckInitialization() var NotInitializedErr: Label 'Please call Initalize() first.'; From c4cd06bde808887641d76ce1efd8c682d871e2dc Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 16:57:46 +0200 Subject: [PATCH 09/33] Add pagination support --- .../src/BlobStorageConnectorImpl.Codeunit.al | 64 ++++++++++++------- .../src/Connector/FileConnector.Interface.al | 6 +- .../src/FileSystem/FileSystem.Codeunit.al | 11 ++-- .../src/FileSystem/FileSystemImp.Codeunit.al | 8 +-- .../src/Lookup/FileAccountBrowser.Page.al | 19 +++++- .../src/Lookup/FilePaginationData.Codeunit.al | 26 ++++++++ 6 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 4d001dc3c5..c56c7ed3c0 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -18,8 +18,9 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// The file account ID which is used to get the file. /// The file path to list. + /// Defines the pagination data. /// A list with all files stored in the path. - procedure ListFiles(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary) + procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) var ABSContainerContent: Record "ABS Container Content"; ABSBlobClient: Codeunit "ABS Blob Client"; @@ -27,14 +28,11 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin InitBlobClient(AccountId, ABSBlobClient); - if (Path <> '') and not Path.EndsWith('/') then - Path += '/'; - ABSOptionalParameters.Prefix(Path); + CheckPath(Path); + InitOptionalParameters(Path, FilePaginationData, ABSOptionalParameters); ABSOptionalParameters.Delimiter('/'); ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); - - if not ABSOperationResponse.IsSuccessful() then - Error(ABSOperationResponse.GetError()); + ValidateListingResponse(FilePaginationData, ABSOperationResponse); ABSContainerContent.SetFilter("Blob Type", '<>%1', ''); if not ABSContainerContent.FindSet() then @@ -179,8 +177,9 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" /// /// The file account ID which is used to get the file. /// The file path to list. + /// Defines the pagination data. /// A list with all directories stored in the path. - procedure ListDirectories(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary) + procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) var ABSContainerContent: Record "ABS Container Content"; ABSBlobClient: Codeunit "ABS Blob Client"; @@ -188,15 +187,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" ABSOptionalParameters: Codeunit "ABS Optional Parameters"; begin InitBlobClient(AccountId, ABSBlobClient); - - if (Path <> '') and not Path.EndsWith('/') then - Path += '/'; - - ABSOptionalParameters.Prefix(Path); + CheckPath(Path); + InitOptionalParameters(Path, FilePaginationData, ABSOptionalParameters); ABSOperationResponse := ABSBlobClient.ListBlobs(ABSContainerContent, ABSOptionalParameters); - - if not ABSOperationResponse.IsSuccessful() then - Error(ABSOperationResponse.GetError()); + ValidateListingResponse(FilePaginationData, ABSOperationResponse); ABSContainerContent.SetRange("Parent Directory", Path); ABSContainerContent.SetRange("Blob Type", ''); @@ -275,13 +269,15 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" var Account: Record "Blob Storage Account"; begin - if Account.FindSet() then - repeat - Accounts."Account Id" := Account.Id; - Accounts.Name := Account.Name; - Accounts.Connector := Enum::"File Connector"::"Blob Storage"; - Accounts.Insert(); - until Account.Next() = 0; + if not Account.FindSet() then + exit; + + repeat + Accounts."Account Id" := Account.Id; + Accounts.Name := Account.Name; + Accounts.Connector := Enum::"File Connector"::"Blob Storage"; + Accounts.Insert(); + until Account.Next() = 0; end; /// @@ -411,4 +407,26 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetPassword(BlobStorageAccount."Password Key")); ABSBlobClient.Initialize(BlobStorageAccount."Storage Account Name", BlobStorageAccount."Container Name", Authorization); end; + + local procedure CheckPath(var Path: Text) + begin + if (Path <> '') and not Path.EndsWith('/') then + Path += '/'; + end; + + local procedure InitOptionalParameters(var Path: Text; var FilePaginationData: Codeunit "File Pagination Data"; var ABSOptionalParameters: Codeunit "ABS Optional Parameters") + begin + ABSOptionalParameters.Prefix(Path); + ABSOptionalParameters.MaxResults(500); + ABSOptionalParameters.NextMarker(FilePaginationData.GetMarker()); + end; + + local procedure ValidateListingResponse(var FilePaginationData: Codeunit "File Pagination Data"; var ABSOperationResponse: Codeunit "ABS Operation Response") + begin + if not ABSOperationResponse.IsSuccessful() then + Error(ABSOperationResponse.GetError()); + + FilePaginationData.SetMarker(ABSOperationResponse.GetNextMarker()); + FilePaginationData.SetEndOfListing(ABSOperationResponse.GetNextMarker() = ''); + end; } \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnector.Interface.al b/Modules/System/File Access/src/Connector/FileConnector.Interface.al index 93f723ad8b..ea91ed83cc 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Interface.al +++ b/Modules/System/File Access/src/Connector/FileConnector.Interface.al @@ -13,8 +13,9 @@ interface "File Connector" /// /// The file account ID which is used to get the file. /// The file path to list. + /// Defines the pagination data. /// A list with all files stored in the path. - procedure ListFiles(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary); + procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary); /// /// Gets a file from the provided account. @@ -70,8 +71,9 @@ interface "File Connector" /// /// The file account ID which is used to get the file. /// The file path to list. + /// Defines the pagination data. /// A list with all directories stored in the path. - procedure ListDirectories(AccountId: Guid; Path: Text; var FileAccountContent: Record "File Account Content" temporary); + procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary); /// /// Creates a directory on the provided account. diff --git a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al index fe1ea9221e..4602a75064 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al @@ -25,11 +25,11 @@ codeunit 70004 "File System" /// List all files from the given path. /// /// Folder to list + /// Defines the pagination data. /// File account content. - [TryFunction] - procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + procedure ListFiles(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) begin - FileSystemImpl.ListFiles(Path, FileAccountContent); + FileSystemImpl.ListFiles(Path, FilePaginationData, FileAccountContent); end; /// @@ -100,11 +100,12 @@ codeunit 70004 "File System" /// List all directories from the given path. /// /// Folder to list + /// Defines the pagination data. /// File account content. [TryFunction] - procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + procedure ListDirectories(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) begin - FileSystemImpl.ListDirectories(Path, FileAccountContent); + FileSystemImpl.ListDirectories(Path, FilePaginationData, FileAccountContent); end; /// diff --git a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al index 136fc0c59e..28d307c2da 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al +++ b/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al @@ -24,10 +24,10 @@ codeunit 70005 "File System Impl." Initialized := true; end; - internal procedure ListFiles(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + internal procedure ListFiles(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) begin CheckInitialization(); - IFileConnector.ListFiles(CurrFileAccount."Account Id", Path, FileAccountContent); + IFileConnector.ListFiles(CurrFileAccount."Account Id", Path, FilePaginationData, FileAccountContent); end; internal procedure GetFile(Path: Text; Stream: InStream) @@ -67,10 +67,10 @@ codeunit 70005 "File System Impl." IFileConnector.DeleteFile(CurrFileAccount."Account Id", Path); end; - internal procedure ListDirectories(Path: Text; var FileAccountContent: Record "File Account Content" temporary) + internal procedure ListDirectories(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) begin CheckInitialization(); - IFileConnector.ListDirectories(CurrFileAccount."Account Id", Path, FileAccountContent); + IFileConnector.ListDirectories(CurrFileAccount."Account Id", Path, FilePaginationData, FileAccountContent); end; internal procedure CreateDirectory(Path: Text) diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al index 662a00a34f..c913f1ec7d 100644 --- a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al @@ -178,14 +178,18 @@ page 70005 "File Account Browser" end; local procedure BrowseFolder(Path: Text) + var + FilePaginationData: Codeunit "File Pagination Data"; begin CurrPath := Path; ParentFolderExists := Path <> ''; Rec.DeleteAll(); - FileSystem.ListDirectories(Path, Rec); - ListFiles(Path); + repeat + FileSystem.ListDirectories(Path, FilePaginationData, Rec); + until FilePaginationData.IsEndOfListing(); + ListFiles(Path); if Rec.FindFirst() then; end; @@ -212,11 +216,20 @@ page 70005 "File Account Browser" local procedure ListFiles(var Path: Text) var FileAccountContent: Record "File Account Content" temporary; + FilePaginationData: Codeunit "File Pagination Data"; begin if DoNotLoadFields then exit; - FileSystem.ListFiles(Path, FileAccountContent); + repeat + FileSystem.ListFiles(Path, FilePaginationData, FileAccountContent); + until FilePaginationData.IsEndOfListing(); + + AddFiles(FileAccountContent); + end; + + local procedure AddFiles(var FileAccountContent: Record "File Account Content" temporary) + begin if CurrFileFilter <> '' then FileAccountContent.SetFilter(Name, CurrFileFilter); diff --git a/Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al b/Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al new file mode 100644 index 0000000000..a3d9b10b7b --- /dev/null +++ b/Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al @@ -0,0 +1,26 @@ +codeunit 70006 "File Pagination Data" +{ + var + Marker: Text; + EndOfListing: Boolean; + + procedure SetMarker(NewMarker: Text) + begin + Marker := NewMarker; + end; + + procedure GetMarker(): Text + begin + exit(Marker); + end; + + procedure SetEndOfListing(NewEndOfListing: Boolean) + begin + EndOfListing := NewEndOfListing; + end; + + procedure IsEndOfListing(): Boolean + begin + exit(EndOfListing); + end; +} From 24725be2e33e65b3d80004761d2aafdc5f49a89f Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 17:08:36 +0200 Subject: [PATCH 10/33] Rename Module --- .../app/app.json | 3 +- .../app/src/BlobStorageConnector.EnumExt.al | 4 +- .../src/BlobStorageConnectorImpl.Codeunit.al | 6 +- Modules/System/File Access/README.md | 1751 ----------------- Modules/System/File System/README.md | 6 + .../{File Access => File System}/app.json | 3 +- .../src/Account/FileAccount.Codeunit.al | 0 .../src/Account/FileAccount.Table.al | 2 +- .../src/Account/FileAccountImpl.Codeunit.al | 28 +- .../src/Account/FileAccountWizard.Page.al | 4 +- .../src/Account/FileAccounts.Page.al | 2 +- .../Connector/FileSystemConnector.Enum.al} | 2 +- .../FileSystemConnector.Interface.al} | 4 +- .../Connector/FileSystemConnector.Table.al} | 4 +- .../FileSystemConnectorLogo.Table.al} | 4 +- .../src/FileSystem/FileSystem.Codeunit.al | 0 .../src/FileSystem/FileSystemImp.Codeunit.al | 2 +- .../src/Lookup/FileAccountBrowser.Page.al | 0 .../src/Lookup/FileAccountContent.Table.al | 0 .../src/Lookup/FilePaginationData.Codeunit.al | 0 .../src/Lookup/FileType.Enum.al | 0 .../src/Scenario/FileAccountScenario.Table.al | 2 +- .../src/Scenario/FileScenario.Codeunit.al | 0 .../src/Scenario/FileScenario.Enum.al | 0 .../src/Scenario/FileScenario.Table.al | 2 +- .../src/Scenario/FileScenarioImpl.Codeunit.al | 2 +- .../src/Scenario/FileScenarioSetup.Page.al | 4 +- .../src/Scenario/FileScenariosFactBox.Page.al | 0 .../Scenario/FileScenariosForAccount.Page.al | 0 29 files changed, 46 insertions(+), 1789 deletions(-) delete mode 100644 Modules/System/File Access/README.md create mode 100644 Modules/System/File System/README.md rename Modules/System/{File Access => File System}/app.json (96%) rename Modules/System/{File Access => File System}/src/Account/FileAccount.Codeunit.al (100%) rename Modules/System/{File Access => File System}/src/Account/FileAccount.Table.al (96%) rename Modules/System/{File Access => File System}/src/Account/FileAccountImpl.Codeunit.al (90%) rename Modules/System/{File Access => File System}/src/Account/FileAccountWizard.Page.al (99%) rename Modules/System/{File Access => File System}/src/Account/FileAccounts.Page.al (99%) rename Modules/System/{File Access/src/Connector/FileConnector.Enum.al => File System/src/Connector/FileSystemConnector.Enum.al} (87%) rename Modules/System/{File Access/src/Connector/FileConnector.Interface.al => File System/src/Connector/FileSystemConnector.Interface.al} (98%) rename Modules/System/{File Access/src/Connector/FileConnector.Table.al => File System/src/Connector/FileSystemConnector.Table.al} (90%) rename Modules/System/{File Access/src/Connector/FileConnectorLogo.Table.al => File System/src/Connector/FileSystemConnectorLogo.Table.al} (88%) rename Modules/System/{File Access => File System}/src/FileSystem/FileSystem.Codeunit.al (100%) rename Modules/System/{File Access => File System}/src/FileSystem/FileSystemImp.Codeunit.al (99%) rename Modules/System/{File Access => File System}/src/Lookup/FileAccountBrowser.Page.al (100%) rename Modules/System/{File Access => File System}/src/Lookup/FileAccountContent.Table.al (100%) rename Modules/System/{File Access => File System}/src/Lookup/FilePaginationData.Codeunit.al (100%) rename Modules/System/{File Access => File System}/src/Lookup/FileType.Enum.al (100%) rename Modules/System/{File Access => File System}/src/Scenario/FileAccountScenario.Table.al (96%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenario.Codeunit.al (100%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenario.Enum.al (100%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenario.Table.al (94%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenarioImpl.Codeunit.al (99%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenarioSetup.Page.al (98%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenariosFactBox.Page.al (100%) rename Modules/System/{File Access => File System}/src/Scenario/FileScenariosForAccount.Page.al (100%) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json index 0899c085e0..84a342044b 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -17,7 +17,7 @@ "dependencies": [ { "id": "c9c54414-80c3-4cc9-98c6-589158882774", - "name": "File Access", + "name": "File System", "publisher": "Microsoft", "version": "22.1.0.0" } @@ -42,5 +42,6 @@ "allowDownloadingSource": true, "includeSourceInSymbolFile": true }, + "runtime": "11.0", "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" } \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al index af0e30b992..5661d97108 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al @@ -6,7 +6,7 @@ /// /// Enum extension to register the Blob Storage connector. /// -enumextension 80100 "Blob Storage Connector" extends "File Connector" +enumextension 80100 "Blob Storage Connector" extends "File System Connector" { /// /// The Blob Storage connector. @@ -14,6 +14,6 @@ enumextension 80100 "Blob Storage Connector" extends "File Connector" value(2147483647; "Blob Storage") // Max int value so it appears last //FIXME Id from Module { Caption = 'Blob Storage'; - Implementation = "File Connector" = "Blob Storage Connector Impl."; + Implementation = "File System Connector" = "Blob Storage Connector Impl."; } } \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index c56c7ed3c0..02345bae54 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -3,7 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" +codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" { Access = Internal; Permissions = tabledata "Blob Storage Account" = rimd; @@ -275,7 +275,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" repeat Accounts."Account Id" := Account.Id; Accounts.Name := Account.Name; - Accounts.Connector := Enum::"File Connector"::"Blob Storage"; + Accounts.Connector := Enum::"File System Connector"::"Blob Storage"; Accounts.Insert(); until Account.Next() = 0; end; @@ -370,7 +370,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File Connector" FileAccount."Account Id" := NewBlobStorageAccount.Id; FileAccount.Name := NewBlobStorageAccount.Name; - FileAccount.Connector := Enum::"File Connector"::"Blob Storage"; + FileAccount.Connector := Enum::"File System Connector"::"Blob Storage"; end; internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; Password: Text; var NewContainerName: Text[2048]) diff --git a/Modules/System/File Access/README.md b/Modules/System/File Access/README.md deleted file mode 100644 index 5e7e0b886f..0000000000 --- a/Modules/System/File Access/README.md +++ /dev/null @@ -1,1751 +0,0 @@ -Provides an API that lets you connect email accounts to Business Central so that people can send messages without having to open their email application. The email module consists of the following main entities: - -### Email Account -An email account holds the information needed to send emails from Business Central. - -### Email Address Lookup -Email address lookup suggests email addresses to the user for the To, Cc and Bcc fields, and the suggestions are based on the related records of the email. - -### Email Connector -An email connector is an interface for creating and managing email accounts, and sending emails. Every email account belongs to an email connector. - -### Email Scenario -Email scenarios are specific business processes that involve documents or notifications. Use scenarios to seamlessly integrate email accounts with business processes. - -### Email Message -Payload for every email that is being composed or already has been sent. - -### Email Outbox -Holds draft emails, and emails that were not successfully sent. - -### Sent Email -Holds emails that have been sent. - -# Public Objects -## Email Account (Table 8902) - - A common representation of an email account. - - - -## Email Outbox (Table 8888) -Holds information about draft emails and email that are about to be sent. - - -## Sent Email (Table 8889) -Holds information about the sent emails. - -### GetMessageId (Method) - - Get the message id of the sent email. - - -#### Syntax -``` -procedure GetMessageId(): Guid -``` -#### Return Value -*[Guid](https://go.microsoft.com/fwlink/?linkid=2210122)* - -Message id. - -## Email Related Attachment (Table 8910) - - Temporary table that holds information about attachments related to an email. - - - -## Email Connector (Interface) - - An e-mail connector interface used to creating e-mail accounts and sending an e-mail. - - -### Send (Method) - - Sends an e-mail using the provided account. - - -#### Syntax -``` -procedure Send(EmailMessage: Codeunit "Email Message"; AccountId: Guid) -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message that is to be sent out. - -*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The email account ID which is used to send out the email. - -### GetAccounts (Method) - - Gets the e-mail accounts registered for the connector. - - -#### Syntax -``` -procedure GetAccounts(var Accounts: Record "Email Account") -``` -#### Parameters -*Accounts ([Record "Email Account"]())* - -Out variable that holds the registered e-mail accounts for the connector. - -### ShowAccountInformation (Method) - - Shows the information for an e-mail account. - - -#### Syntax -``` -procedure ShowAccountInformation(AccountId: Guid) -``` -#### Parameters -*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the e-mail account - -### RegisterAccount (Method) - - Registers an e-mail account for the connector. - - -The out parameter must hold the account ID of the added account. - -#### Syntax -``` -procedure RegisterAccount(var Account: Record "Email Account"): Boolean -``` -#### Parameters -*Account ([Record "Email Account"]())* - -Out parameter with the details of the registered Account. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if an account was registered. -### DeleteAccount (Method) - - Deletes an e-mail account for the connector. - - -#### Syntax -``` -procedure DeleteAccount(AccountId: Guid): Boolean -``` -#### Parameters -*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the e-mail account - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if an account was deleted. -### GetLogoAsBase64 (Method) - - Provides a custom logo for the connector that shows in the Setup Email Account Guide. - - -The recomended image size is 128x128. - -#### Syntax -``` -procedure GetLogoAsBase64(): Text -``` -#### Return Value -*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* - -Base64 encoded image. -### GetDescription (Method) - - Provides a more detailed description of the connector. - - -#### Syntax -``` -procedure GetDescription(): Text[250] -``` -#### Return Value -*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* - -A more detailed desctiption of the connector. - -## Email Account (Codeunit 8894) - - Provides functionality to work with email accounts. - - -### GetAllAccounts (Method) - - Gets all of the email accounts registered in Business Central. - - -#### Syntax -``` -procedure GetAllAccounts(LoadLogos: Boolean; var Accounts: Record "Email Account" temporary) -``` -#### Parameters -*LoadLogos ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Flag, used to determine whether to load the logos for the accounts. - -*Accounts ([Record "Email Account" temporary]())* - -Out parameter holding the email accounts. - -### GetAllAccounts (Method) - - Gets all of the email accounts registered in Business Central. - - -#### Syntax -``` -procedure GetAllAccounts(var Accounts: Record "Email Account" temporary) -``` -#### Parameters -*Accounts ([Record "Email Account" temporary]())* - -Out parameter holding the email accounts. - -### IsAnyAccountRegistered (Method) - - Checks if there is at least one email account registered in Business Central. - - -#### Syntax -``` -procedure IsAnyAccountRegistered(): Boolean -``` -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if there is any account registered in the system, otherwise - false. -### ValidateEmailAddress (Method) -The email address "%1" is not valid. - - - Validates an email address and throws an error if it is invalid. - - -If the provided email address is an empty string, the function will do nothing. - -#### Syntax -``` -[TryFunction] -procedure ValidateEmailAddress(EmailAddress: Text) -``` -#### Parameters -*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The email address to validate. - -### ValidateEmailAddress (Method) -The email address "%1" is not valid. - - - Validates an email address and throws an error if it is invalid. - - -#### Syntax -``` -[TryFunction] -procedure ValidateEmailAddress(EmailAddress: Text; AllowEmptyValue: Boolean) -``` -#### Parameters -*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The email address to validate. - -*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Indicates whether to skip the validation if the provided email address is empty. - -### ValidateEmailAddresses (Method) -The email address "%1" is not valid. - - - Validates email addresses and displays an error if any are invalid. - - -If the provided email address is an empty string, the function will do nothing. - -#### Syntax -``` -[TryFunction] -procedure ValidateEmailAddresses(EmailAddresses: Text) -``` -#### Parameters -*EmailAddresses ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The email addresses to validate, separated by semicolons. - -### ValidateEmailAddresses (Method) -The email address "%1" is not valid. - - - Validates email addresses and displays an error if any are invalid. - - -#### Syntax -``` -[TryFunction] -procedure ValidateEmailAddresses(EmailAddresses: Text; AllowEmptyValue: Boolean) -``` -#### Parameters -*EmailAddresses ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The email addresses to validate, separated by semicolons. - -*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Indicates whether to skip the validation if no email address is provided. - -### OnAfterValidateEmailAddress (Event) -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnAfterValidateEmailAddress(EmailAddress: Text; AllowEmptyValue: Boolean) -``` -#### Parameters -*EmailAddress ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - - - -*AllowEmptyValue ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - - - - -## Email (Codeunit 8901) - - Provides functionality to create and send emails. - - -### SaveAsDraft (Method) - - Saves a draft email in the Outbox. - - -#### Syntax -``` -procedure SaveAsDraft(EmailMessage: Codeunit "Email Message") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to save. - -### SaveAsDraft (Method) - - Saves a draft email in the Outbox. - - -#### Syntax -``` -procedure SaveAsDraft(EmailMessage: Codeunit "Email Message"; var EmailOutbox: Record "Email Outbox") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to save. - -*EmailOutbox ([Record "Email Outbox"]())* - -The created outbox entry. - -### Enqueue (Method) - - Enqueues an email to be sent in the background. - - -The default account will be used for sending the email. - -#### Syntax -``` -procedure Enqueue(EmailMessage: Codeunit "Email Message") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -### Enqueue (Method) - - Enqueues an email to be sent in the background. - - -#### Syntax -``` -procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailScenario ([Enum "Email Scenario"]())* - -The scenario to use in order to determine the email account to use for sending the email. - -### Enqueue (Method) - - Enqueues an email to be sent in the background. - - -Both "Account Id" and Connector fields need to be set on the parameter. - -#### Syntax -``` -procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary) -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccount ([Record "Email Account" temporary]())* - -The email account to use for sending the email. - -### Enqueue (Method) - - Enqueues an email to be sent in the background. - - -#### Syntax -``` -procedure Enqueue(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email account to use for sending the email. - -*EmailConnector ([Enum "Email Connector"]())* - -The email connector to use for sending the email. - -### Send (Method) -The email message has already been queued. - - - Sends the email in the current session. - - -The default account will be used for sending the email. - -#### Syntax -``` -procedure Send(EmailMessage: Codeunit "Email Message"): Boolean -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email was successfully sent; otherwise - false. -### Send (Method) -The email message has already been queued. - - - Sends the email in the current session. - - -#### Syntax -``` -procedure Send(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario"): Boolean -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailScenario ([Enum "Email Scenario"]())* - -The scenario to use in order to determine the email account to use for sending the email. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email was successfully sent; otherwise - false. -### Send (Method) -The email message has already been queued. - - - Sends the email in the current session. - - -Both "Account Id" and Connector fields need to be set on the parameter. - -#### Syntax -``` -procedure Send(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary): Boolean -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccount ([Record "Email Account" temporary]())* - -The email account to use for sending the email. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email was successfully sent; otherwise - false -### Send (Method) -The email message has already been queued. - - - Sends the email in the current session. - - -#### Syntax -``` -procedure Send(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Boolean -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email account to use for sending the email. - -*EmailConnector ([Enum "Email Connector"]())* - -The email connector to use for sending the email. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email was successfully sent; otherwise - false -### OpenInEditor (Method) - - Opens an email message in "Email Editor" page. - - -#### Syntax -``` -procedure OpenInEditor(EmailMessage: Codeunit "Email Message") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -### OpenInEditor (Method) - - Opens an email message in "Email Editor" page. - - -#### Syntax -``` -procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailScenario ([Enum "Email Scenario"]())* - -The scenario to use in order to determine the email account to use on the page. - -### OpenInEditor (Method) - - Opens an email message in "Email Editor" page. - - -Both "Account Id" and Connector fields need to be set on the parameter. - -#### Syntax -``` -procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary) -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccount ([Record "Email Account" temporary]())* - -The email account to fill in. - -### OpenInEditor (Method) - - Opens an email message in "Email Editor" page. - - -#### Syntax -``` -procedure OpenInEditor(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email account to use on the page. - -*EmailConnector ([Enum "Email Connector"]())* - -The email connector to use on the page. - -### OpenInEditorModally (Method) - - Opens an email message in "Email Editor" page modally. - - -#### Syntax -``` -procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"): Enum "Email Action" -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -#### Return Value -*[Enum "Email Action"]()* - -The action that the user performed with the email message. -### OpenInEditorModally (Method) - - Opens an email message in "Email Editor" page modally. - - -#### Syntax -``` -procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailScenario: Enum "Email Scenario"): Enum "Email Action" -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailScenario ([Enum "Email Scenario"]())* - -The scenario to use in order to determine the email account to use on the page. - -#### Return Value -*[Enum "Email Action"]()* - -The action that the user performed with the email message. -### OpenInEditorModally (Method) - - Opens an email message in "Email Editor" page modally. - - -Both "Account Id" and Connector fields need to be set on the parameter. - -#### Syntax -``` -procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailAccount: Record "Email Account" temporary): Enum "Email Action" -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccount ([Record "Email Account" temporary]())* - -The email account to fill in. - -#### Return Value -*[Enum "Email Action"]()* - -The action that the user performed with the email message. -### OpenInEditorModally (Method) - - Opens an email message in "Email Editor" page modally. - - -#### Syntax -``` -procedure OpenInEditorModally(EmailMessage: Codeunit "Email Message"; EmailAccountId: Guid; EmailConnector: Enum "Email Connector"): Enum "Email Action" -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message to use as payload. - -*EmailAccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email account to use on the page. - -*EmailConnector ([Enum "Email Connector"]())* - -The email connector to use on the page. - -#### Return Value -*[Enum "Email Action"]()* - -The action that the user performed with the email message. -### GetSentEmailsForRecord (Method) - - Gets the sent emails related to a record. - - -#### Syntax -``` -[Obsolete('Use GetSentEmailsForRecord(TableId: Integer; SystemId: Guid; var ResultEmailOutbox: Record "Email Outbox" temporary) instead.','19.0')] -procedure GetSentEmailsForRecord(TableId: Integer; SystemId: Guid)ResultSentEmails: Record "Sent Email" temporary -``` -#### Parameters -*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table ID of the record. - -*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the record. - -#### Return Value -*[Record "Sent Email" temporary]()* - -The sent email related to a record. -### GetSentEmailsForRecord (Method) - - Gets the sent emails related to a record. - - -#### Syntax -``` -procedure GetSentEmailsForRecord(TableId: Integer; SystemId: Guid; var ResultSentEmails: Record "Sent Email" temporary) -``` -#### Parameters -*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table ID of the record. - -*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the record. - -*ResultSentEmails ([Record "Sent Email" temporary]())* - - - -### GetSentEmailsForRecord (Method) - - Gets the sent emails related to a record. - - -#### Syntax -``` -procedure GetSentEmailsForRecord(RecordVariant: Variant; var ResultSentEmails: Record "Sent Email" temporary) -``` -#### Parameters -*RecordVariant ([Variant](https://go.microsoft.com/fwlink/?linkid=2210243))* - -Source Record. - -*ResultSentEmails ([Record "Sent Email" temporary]())* - -The sent email related to a record. - -### GetEmailOutboxForRecord (Method) - - Gets the outbox emails related to a record. - - -#### Syntax -``` -procedure GetEmailOutboxForRecord(RecordVariant: Variant; var ResultEmailOutbox: Record "Email Outbox" temporary) -``` -#### Parameters -*RecordVariant ([Variant](https://go.microsoft.com/fwlink/?linkid=2210243))* - -Source Record. - -*ResultEmailOutbox ([Record "Email Outbox" temporary]())* - -The outbox emails related to a record. - -### OpenSentEmails (Method) - - Open the sent emails page for a source record given by its table ID and system ID. - - -#### Syntax -``` -procedure OpenSentEmails(TableId: Integer; SystemId: Guid) -``` -#### Parameters -*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table ID of the record. - -*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the record. - -### GetOutboxEmailRecordStatus (Method) - - Gets the outbox email status. - - -#### Syntax -``` -procedure GetOutboxEmailRecordStatus(MessageId: Guid)ResultStatus: Enum "Email Status" -``` -#### Parameters -*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The MessageId of the record. - -#### Return Value -*[Enum "Email Status"]()* - -Email Status of the record. -### AddRelation (Method) - - Adds a relation between an email message and a record. - - -#### Syntax -``` -procedure AddRelation(EmailMessage: Codeunit "Email Message"; TableId: Integer; SystemId: Guid; RelationType: Enum "Email Relation Type") -``` -#### Parameters -*EmailMessage ([Codeunit "Email Message"]())* - -The email message for which to create the relation. - -*TableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table ID of the record. - -*SystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the record. - -*RelationType ([Enum "Email Relation Type"]())* - -The relation type to set. - -### OnGetTestEmailBody (Event) - - Integration event to override the default email body for test messages. - - -#### Syntax -``` -[Obsolete('The event will be removed. Subscribe to OnGetBodyForTestEmail instead', '17.3')] -[IntegrationEvent(false, false)] -procedure OnGetTestEmailBody(Connector: Enum "Email Connector"; var Body: Text) -``` -#### Parameters -*Connector ([Enum "Email Connector"]())* - -The connector used to send the email message. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -Out param to set the email body to a new value. - -### OnShowSource (Event) - - Integration event to show an email source record. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnShowSource(SourceTableId: Integer; SourceSystemId: Guid; var IsHandled: Boolean) -``` -#### Parameters -*SourceTableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - - - -*SourceSystemId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the source record. - -*IsHandled ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Out parameter to set if the event was handled. - -### OnGetBodyForTestEmail (Event) - - Integration event to override the default email body for test messages. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnGetBodyForTestEmail(Connector: Enum "Email Connector"; AccountId: Guid; var Body: Text) -``` -#### Parameters -*Connector ([Enum "Email Connector"]())* - -The connector used to send the email message. - -*AccountId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The account ID of the email account used to send the email message. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031 - -Out param to set the email body to a new value. - -### OnAfterSendEmail (Event) - - Integration event that notifies senders about whether their email was successfully sent in the background. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnAfterSendEmail(MessageId: Guid; Status: Boolean) -``` -#### Parameters -*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email in the queue. - -*Status ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -True if the message was successfully sent. - -### OnFindRelatedAttachments (Event) - - Integration event to get the names and IDs of attachments related to a source record. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnFindRelatedAttachments(SourceTableId: Integer; SourceSystemID: Guid; var EmailRelatedAttachments: Record "Email Related Attachment") -``` -#### Parameters -*SourceTableId ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table number of the source record. - -*SourceSystemID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the source record. - -*EmailRelatedAttachments ([Record "Email Related Attachment"]())* - -Out parameter to return attachments related to the source record. - -### OnGetAttachment (Event) - - Integration event that requests an attachment to be added to an email. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnGetAttachment(AttachmentTableID: Integer; AttachmentSystemID: Guid; MessageID: Guid) -``` -#### Parameters -*AttachmentTableID ([Integer](https://go.microsoft.com/fwlink/?linkid=2209956))* - -The table number of the attachment. - -*AttachmentSystemID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The system ID of the attachment. - -*MessageID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email to add an attachment to. - -### OnEnqueuedInOutbox (Event) - - Integration event to implement additional validation after the email message has been enqueued in the email outbox. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnEnqueuedInOutbox(MessageId: Guid) -``` -#### Parameters -*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email that has been queued - - -## Email Message (Codeunit 8904) - - Codeunit to create and manage email messages. - - -### Create (Method) - - Creates the email with recipients, subject, and body. - - -#### Syntax -``` -procedure Create(ToRecipients: Text; Subject: Text; Body: Text) -``` -#### Parameters -*ToRecipients ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The recipient(s) of the email. A string containing the email addresses of the recipients separated by semicolon. - -*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The subject of the email. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -Raw text that will be used as body of the email. - -### Create (Method) - - Creates the email with recipients, subject, and body. - - -#### Syntax -``` -procedure Create(ToRecipients: Text; Subject: Text; Body: Text; HtmlFormatted: Boolean) -``` -#### Parameters -*ToRecipients ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The recipient(s) of the email. A string containing the email addresses of the recipients separated by semicolon. - -*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The subject of the email. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The body of the email. - -*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Whether the body is HTML formatted. - -### Create (Method) - - Creates the email with recipients, subject, and body. - - -#### Syntax -``` -procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean) -``` -#### Parameters -*ToRecipients ([List of [Text]]())* - -The recipient(s) of the email. A list of email addresses the email will be send directly to. - -*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The subject of the email. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The body of the email - -*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Whether the body is HTML formatted - -### Create (Method) - - Creates the email with recipients, subject, and body. - - -#### Syntax -``` -procedure Create(ToRecipients: List of [Text]; Subject: Text; Body: Text; HtmlFormatted: Boolean; CCRecipients: List of [Text]; BCCRecipients: List of [Text]) -``` -#### Parameters -*ToRecipients ([List of [Text]]())* - -The recipient(s) of the email. A list of email addresses the email will be send directly to. - -*Subject ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The subject of the email. - -*Body ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The body of the email. - -*HtmlFormatted ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Whether the body is HTML formatted. - -*CCRecipients ([List of [Text]]())* - -The CC recipient(s) of the email. A list of email addresses that will be listed as CC. - -*BCCRecipients ([List of [Text]]())* - -TThe BCC recipient(s) of the email. A list of email addresses that will be listed as BCC. - -### Get (Method) - - Gets the email message with the given ID. - - -#### Syntax -``` -procedure Get(MessageId: Guid): Boolean -``` -#### Parameters -*MessageId ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -The ID of the email message to get. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email was found; otherwise - false. -### GetBody (Method) - - Gets the body of the email message. - - -#### Syntax -``` -procedure GetBody(): Text -``` -#### Return Value -*[Text](https://go.microsoft.com/fwlink/?linkid=2210031 - -The body of the email. -### GetSubject (Method) - - Gets the subject of the email message. - - -#### Syntax -``` -procedure GetSubject(): Text[2048] -``` -#### Return Value -*[Text[2048]](https://go.microsoft.com/fwlink/?linkid=2210031)* - -The subject of the email. -### IsBodyHTMLFormatted (Method) - - Checks if the email body is formatted in HTML. - - -#### Syntax -``` -procedure IsBodyHTMLFormatted(): Boolean -``` -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the email body is formatted in HTML; otherwise - false. -### GetId (Method) - - Gets the ID of the email message. - - -#### Syntax -``` -procedure GetId(): Guid -``` -#### Return Value -*[Guid](https://go.microsoft.com/fwlink/?linkid=2210122)* - -The ID of the email. -### GetRecipients (Method) - - Gets the recipents of a certain type of the email message. - - -#### Syntax -``` -procedure GetRecipients(RecipientType: Enum "Email Recipient Type"; var Recipients: list of [Text]) -``` -#### Parameters -*RecipientType ([Enum "Email Recipient Type"]())* - -Specifies the type of the recipients. - -*Recipients ([list of [Text]]())* - -Out parameter filled with the recipients' email addresses. - -### AddAttachment (Method) - - Adds a file attachment to the email message. - - -#### Syntax -``` -procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentBase64: Text) -``` -#### Parameters -*AttachmentName ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The name of the file attachment. - -*ContentType ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The Content Type of the file attachment. - -*AttachmentBase64 ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The Base64 text representation of the attachment. - -### AddAttachment (Method) - - Adds a file attachment to the email message. - - -#### Syntax -``` -procedure AddAttachment(AttachmentName: Text[250]; ContentType: Text[250]; AttachmentStream: InStream) -``` -#### Parameters -*AttachmentName ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The name of the file attachment. - -*ContentType ([Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The Content Type of the file attachment. - -*AttachmentStream ([InStream](https://go.microsoft.com/fwlink/?linkid=2210033))* - -The instream of the attachment. - -### Attachments_DeleteContent (Method) - - Deletes the contents of the currently selected attachment. - - -#### Syntax -``` -procedure Attachments_DeleteContent(): Boolean -``` -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -Returns true if contents was successfully deleted, otherwise false. -### Attachments_First (Method) - - Finds the first attachment of the email message. - - -#### Syntax -``` -procedure Attachments_First(): Boolean -``` -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if there is any attachment; otherwise - false. -### Attachments_Next (Method) - - Finds the next attachment of the email message. - - -#### Syntax -``` -procedure Attachments_Next(): Integer -``` -#### Return Value -*[Integer](https://go.microsoft.com/fwlink/?linkid=2209956)* - -The ID of the next attachment if it was found; otherwise - 0. -### Attachments_GetName (Method) - - Gets the name of the current attachment. - - -#### Syntax -``` -procedure Attachments_GetName(): Text[250] -``` -#### Return Value -*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* - -The name of the current attachment. -### Attachments_GetContent (Method) - - Gets the content of the current attachment. - - -#### Syntax -``` -procedure Attachments_GetContent(var AttachmentStream: InStream) -``` -#### Parameters -*AttachmentStream ([InStream](https://go.microsoft.com/fwlink/?linkid=2210033))* - -Out parameter with the content of the current attachment. - -### Attachments_GetContentBase64 (Method) - - Gets the content of the current attachment in Base64 encoding. - - -#### Syntax -``` -procedure Attachments_GetContentBase64(): Text -``` -#### Return Value -*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* - -The content of the current attachment in Base64 encoding. -### Attachments_GetContentType (Method) - - Gets the content type of the current attachment. - - -#### Syntax -``` -procedure Attachments_GetContentType(): Text[250] -``` -#### Return Value -*[Text[250]](https://go.microsoft.com/fwlink/?linkid=2210031)* - -The content type of the current attachment. -### Attachments_GetContentId (Method) - - Gets the content ID of the current attachment. - - -This value is filled only if the attachment is inline the email body. - -#### Syntax -``` -procedure Attachments_GetContentId(): Text[40] -``` -#### Return Value -*[Text[40]](https://go.microsoft.com/fwlink/?linkid=2210031)* - -The content ID of the current attachment. -### Attachments_GetLength (Method) - - Gets the content length of the current attachment. - - -#### Syntax -``` -procedure Attachments_GetLength(): Integer -``` -#### Return Value -*[Integer](https://go.microsoft.com/fwlink/?linkid=2209956)* - -The content length of the current attachment. -### Attachments_IsInline (Method) - - Checks if the attachment is inline the message body. - - -#### Syntax -``` -procedure Attachments_IsInline(): Boolean -``` -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if the attachment is inline the message body; otherwise - false. -### OnGetAttachmentContent (Event) - - Integration event to provide the stream of data for a given MediaID. If attachment content has been deleted, this event makes it possible to provide - the data from elsewhere. - - -#### Syntax -``` -[IntegrationEvent(false, false)] -internal procedure OnGetAttachmentContent(MediaID: Guid; var InStream: Instream; var Handled: Boolean) -``` -#### Parameters -*MediaID ([Guid](https://go.microsoft.com/fwlink/?linkid=2210122))* - -Id of the underlying media field that contains the attachment data. - -*InStream ([Instream]())* - -Stream to that should pointed to the attachment data. - -*Handled ([Boolean](https://go.microsoft.com/fwlink/?linkid=2209954))* - -Was the attachment content added to the stream. - - -## Email Scenario (Codeunit 8893) - - Provides functionality to work with email scenarios. - - -### GetDefaultEmailAccount (Method) - - Gets the default email account. - - -#### Syntax -``` -procedure GetDefaultEmailAccount(var EmailAccount: Record "Email Account"): Boolean -``` -#### Parameters -*EmailAccount ([Record "Email Account"]())* - -Out parameter holding information about the default email account. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if an account for the the default scenario was found; otherwise - false. -### GetEmailAccount (Method) - - Gets the email account used by the given email scenario. - If the no account is defined for the provided scenario, the default account (if defined) will be returned. - - -#### Syntax -``` -procedure GetEmailAccount(Scenario: Enum "Email Scenario"; var EmailAccount: Record "Email Account"): Boolean -``` -#### Parameters -*Scenario ([Enum "Email Scenario"]())* - -The scenario to look for. - -*EmailAccount ([Record "Email Account"]())* - -Out parameter holding information about the email account. - -#### Return Value -*[Boolean](https://go.microsoft.com/fwlink/?linkid=2209954)* - -True if an account for the specified scenario was found; otherwise - false. -### SetDefaultEmailAccount (Method) - - Sets a default email account. - - -#### Syntax -``` -procedure SetDefaultEmailAccount(EmailAccount: Record "Email Account") -``` -#### Parameters -*EmailAccount ([Record "Email Account"]())* - -The email account to use. - -### SetEmailAccount (Method) - - Sets an email account to be used by the given email scenario. - - -#### Syntax -``` -procedure SetEmailAccount(Scenario: Enum "Email Scenario"; EmailAccount: Record "Email Account") -``` -#### Parameters -*Scenario ([Enum "Email Scenario"]())* - -The scenario for which to set an email account. - -*EmailAccount ([Record "Email Account"]())* - -The email account to use. - -### UnassignScenario (Method) - - Unassign an email scenario. The scenario will then use the default email account. - - -#### Syntax -``` -procedure UnassignScenario(Scenario: Enum "Email Scenario") -``` -#### Parameters -*Scenario ([Enum "Email Scenario"]())* - -The scenario to unassign. - - -## Email Test Mail (Codeunit 8887) - - Sends a test email to a specified account. - - - -## Email Accounts (Page 8887) - - Lists all of the registered email accounts - - -### GetAccount (Method) - - Gets the selected email account. - - -#### Syntax -``` -procedure GetAccount(var Account: Record "Email Account") -``` -#### Parameters -*Account ([Record "Email Account"]())* - -The selected email account - -### SetAccount (Method) - - Sets an email account to be selected. - - -#### Syntax -``` -procedure SetAccount(var Account: Record "Email Account") -``` -#### Parameters -*Account ([Record "Email Account"]())* - -The email account to be initially selected on the page - -### EnableLookupMode (Method) - - Enables the lookup mode on the page. - - -#### Syntax -``` -procedure EnableLookupMode() -``` - -## Email Account Wizard (Page 8886) - - Step by step guide for adding a new email account in Business Central - - - -## Email Activities (Page 8885) - - Provides information about the status of the emails. - - - -## Email Relation Picker (Page 8910) - -## Email Editor (Page 13) - - A page to create, edit and send e-mails. - - - -## Email Outbox (Page 8882) - - Displays information about email that are queued for sending. - - - -## Email Viewer (Page 12) - - A page to view sent emails. - - - -## Sent Emails (Page 8883) - - Provides an overview of all e-mail that were sent out. - - - -## Email Attachments (Page 8889) - -## Email Related Attachments (Page 8890) - - Displays a list of related attachments to an email. - - - -## Email Scenario Setup (Page 8893) - - Page is used to display email scenarios usage by email accounts. - - - -## Email Scenarios FactBox (Page 8895) - - Lists of all scenarios assigned to an account. - - - -## Email Scenarios for Account (Page 8894) - - Displays the scenarios that could be linked to a provided e-mail account. - - - -## Email User-Specified Address (Page 8884) - - A page to enter an email address. - - -### GetEmailAddress (Method) - - Gets the email address(es) that has been entered. - - -#### Syntax -``` -procedure GetEmailAddress(): Text -``` -#### Return Value -*[Text](https://go.microsoft.com/fwlink/?linkid=2210031)* - -Email address(es) -### SetEmailAddress (Method) - - Sets the inital value to be displayed. - - -#### Syntax -``` -procedure SetEmailAddress(Address: Text) -``` -#### Parameters -*Address ([Text](https://go.microsoft.com/fwlink/?linkid=2210031))* - -The value to be prefilled - - -## Email Connector (Enum 8889) - - Enum that holds all of the available email connectors. - - - -## Email Relation Type (Enum 8908) - - Represent the type of relation between an email and a source record. - - -### Primary Source (value: 0) - - - Primary source of an email. There should only be one primary source for an email. - - -### Related Entity (value: 1) - - - Related entity of an email. An email can have many record relations of this type. - - - -## Email Action (Enum 8891) - - Defines the action that the user can take when open an email message in the editor modally. - - -### Saved As Draft (value: 0) - - - The email was saved as draft. - - -### Discarded (value: 1) - - - The email was discarded. - - -### Sent (value: 2) - - - The email was sent. - - - -## Email Status (Enum 8888) - - Specifies the status of an email when it is in the outbox. - - -### (value: 0) - - - An uninitialized email. - - -### Draft (value: 1) - - - An email waiting to be queued up for sending. - - -### Queued (value: 2) - - - An email queued up and waiting to be processed. - - -### Processing (value: 3) - - - An email that is currently being processed and sent. - - -### Failed (value: 4) - - - An email that had an error occur during processing. - - - -## Email Recipient Type (Enum 8901) - - Specifies the type of an email recipient. - - -### To (value: 0) - - - Recipient type 'To'. - - -### Cc (value: 1) - - - Recipient type 'Cc'. - - -### Bcc (value: 3) - - - Recipient type 'Bcc'. - - - -## Email Scenario (Enum 8890) - - Email scenarios. - Used to decouple email accounts from sending emails. - - -### Default (value: 0) - - - The default email scenario. - Used in the cases where no other scenario is defined. - - diff --git a/Modules/System/File System/README.md b/Modules/System/File System/README.md new file mode 100644 index 0000000000..aa74a80495 --- /dev/null +++ b/Modules/System/File System/README.md @@ -0,0 +1,6 @@ +Provides an API that lets you connect file system accounts to Business Central so that people can access files outside BC. The file system module consists of the following main entities: + +### File Account +An file account holds the information needed to access a file system from Business Central. + +... \ No newline at end of file diff --git a/Modules/System/File Access/app.json b/Modules/System/File System/app.json similarity index 96% rename from Modules/System/File Access/app.json rename to Modules/System/File System/app.json index 9fe1743127..27715f0eda 100644 --- a/Modules/System/File Access/app.json +++ b/Modules/System/File System/app.json @@ -1,6 +1,6 @@ { "id": "c9c54414-80c3-4cc9-98c6-589158882774", - "name": "File Access", + "name": "File System", "publisher": "Microsoft", "brief": "Enables user to send emails from Business Central.", "description": "Enables user to send emails from Business Central.", @@ -36,5 +36,6 @@ } ], "target": "OnPrem", + "runtime": "11.0", "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" } \ No newline at end of file diff --git a/Modules/System/File Access/src/Account/FileAccount.Codeunit.al b/Modules/System/File System/src/Account/FileAccount.Codeunit.al similarity index 100% rename from Modules/System/File Access/src/Account/FileAccount.Codeunit.al rename to Modules/System/File System/src/Account/FileAccount.Codeunit.al diff --git a/Modules/System/File Access/src/Account/FileAccount.Table.al b/Modules/System/File System/src/Account/FileAccount.Table.al similarity index 96% rename from Modules/System/File Access/src/Account/FileAccount.Table.al rename to Modules/System/File System/src/Account/FileAccount.Table.al index 9ab68f08af..f1fe415396 100644 --- a/Modules/System/File Access/src/Account/FileAccount.Table.al +++ b/Modules/System/File System/src/Account/FileAccount.Table.al @@ -23,7 +23,7 @@ table 70000 "File Account" DataClassification = SystemMetadata; // Field only in Memory } - field(4; Connector; Enum "File Connector") + field(4; Connector; Enum "File System Connector") { DataClassification = SystemMetadata; } diff --git a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al similarity index 90% rename from Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al rename to Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al index ff0b761f82..6e7e19b375 100644 --- a/Modules/System/File Access/src/Account/FileAccountImpl.Codeunit.al +++ b/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al @@ -8,14 +8,14 @@ codeunit 70001 "File Account Impl." Access = Internal; InherentPermissions = X; InherentEntitlements = X; - Permissions = tabledata "File Connector Logo" = rimd, + Permissions = tabledata "File System Connector Logo" = rimd, tabledata "File Scenario" = imd; procedure GetAllAccounts(LoadLogos: Boolean; var TempFileAccount: Record "File Account" temporary) var FileAccounts: Record "File Account"; - IFileConnector: Interface "File Connector"; - Connector: Enum "File Connector"; + IFileConnector: Interface "File System Connector"; + Connector: Enum "File System Connector"; begin TempFileAccount.Reset(); TempFileAccount.DeleteAll(); @@ -49,7 +49,7 @@ codeunit 70001 "File Account Impl." CurrentDefaultFileAccount: Record "File Account"; ConfirmManagement: Codeunit "Confirm Management"; FileScenario: Codeunit "File Scenario"; - FileConnector: Interface "File Connector"; + FileConnector: Interface "File System Connector"; begin CheckPermissions(); @@ -75,7 +75,7 @@ codeunit 70001 "File Account Impl." HandleDefaultAccountDeletion(CurrentDefaultFileAccount."Account Id", CurrentDefaultFileAccount.Connector); end; - local procedure HandleDefaultAccountDeletion(CurrentDefaultAccountId: Guid; Connector: Enum "File Connector") + local procedure HandleDefaultAccountDeletion(CurrentDefaultAccountId: Guid; Connector: Enum "File System Connector") var AllFileAccounts: Record "File Account"; NewDefaultFileAccount: Record "File Account"; @@ -117,9 +117,9 @@ codeunit 70001 "File Account Impl." exit(false); end; - local procedure ImportLogo(var FileAccount: Record "File Account"; Connector: Interface "File Connector") + local procedure ImportLogo(var FileAccount: Record "File Account"; Connector: Interface "File System Connector") var - FileConnectorLogo: Record "File Connector Logo"; + FileConnectorLogo: Record "File System Connector Logo"; TempBlob: Codeunit "Temp Blob"; Base64Convert: Codeunit "Base64 Convert"; ConnectorLogoBase64: Text; @@ -158,15 +158,15 @@ codeunit 70001 "File Account Impl." exit(FileScenario.WritePermission()); end; - procedure FindAllConnectors(var FileConnector: Record "File Connector") + procedure FindAllConnectors(var FileConnector: Record "File System Connector") var Base64Convert: Codeunit "Base64 Convert"; - ConnectorInterface: Interface "File Connector"; - Connector: Enum "File Connector"; + ConnectorInterface: Interface "File System Connector"; + Connector: Enum "File System Connector"; ConnectorLogoBase64: Text; OutStream: Outstream; begin - foreach Connector in Enum::"File Connector".Ordinals() do begin + foreach Connector in Enum::"File System Connector".Ordinals() do begin ConnectorInterface := Connector; ConnectorLogoBase64 := ConnectorInterface.GetLogoAsBase64(); FileConnector.Connector := Connector; @@ -179,9 +179,9 @@ codeunit 70001 "File Account Impl." end; end; - procedure IsValidConnector(Connector: Enum "File Connector"): Boolean + procedure IsValidConnector(Connector: Enum "File System Connector"): Boolean begin - exit("File Connector".Ordinals().Contains(Connector.AsInteger())); + exit("File System Connector".Ordinals().Contains(Connector.AsInteger())); end; procedure MakeDefault(var FileAccount: Record "File Account") @@ -216,7 +216,7 @@ codeunit 70001 "File Account Impl." Error(CannotManageSetupErr); end; - local procedure ImportLogoBlob(var FileAccount: Record "File Account"; Connector: Interface "File Connector") + local procedure ImportLogoBlob(var FileAccount: Record "File Account"; Connector: Interface "File System Connector") var Base64Convert: Codeunit "Base64 Convert"; ConnectorLogoBase64: Text; diff --git a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al b/Modules/System/File System/src/Account/FileAccountWizard.Page.al similarity index 99% rename from Modules/System/File Access/src/Account/FileAccountWizard.Page.al rename to Modules/System/File System/src/Account/FileAccountWizard.Page.al index b1fc23430b..62a422b33a 100644 --- a/Modules/System/File Access/src/Account/FileAccountWizard.Page.al +++ b/Modules/System/File System/src/Account/FileAccountWizard.Page.al @@ -12,7 +12,7 @@ page 70001 "File Account Wizard" ApplicationArea = All; UsageCategory = Administration; Caption = 'Set Up File'; - SourceTable = "File Connector"; + SourceTable = "File System Connector"; SourceTableTemporary = true; InsertAllowed = false; ModifyAllowed = false; @@ -435,7 +435,7 @@ page 70001 "File Account Wizard" local procedure TryRegisterAccount(var AccountWasRegistered: Boolean) var FileAccountImpl: Codeunit "File Account Impl."; - FileConnector: Interface "File Connector"; + FileConnector: Interface "File System Connector"; begin // Check to validate that the connector is still installed // The connector could have been uninstalled by another user/session diff --git a/Modules/System/File Access/src/Account/FileAccounts.Page.al b/Modules/System/File System/src/Account/FileAccounts.Page.al similarity index 99% rename from Modules/System/File Access/src/Account/FileAccounts.Page.al rename to Modules/System/File System/src/Account/FileAccounts.Page.al index f50e0cf8e3..7a4d8be9e4 100644 --- a/Modules/System/File Access/src/Account/FileAccounts.Page.al +++ b/Modules/System/File System/src/Account/FileAccounts.Page.al @@ -274,7 +274,7 @@ page 70000 "File Accounts" local procedure ShowAccountInformation() var FileAccountImpl: Codeunit "File Account Impl."; - Connector: Interface "File Connector"; + Connector: Interface "File System Connector"; begin UpdateAccounts := true; diff --git a/Modules/System/File Access/src/Connector/FileConnector.Enum.al b/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al similarity index 87% rename from Modules/System/File Access/src/Connector/FileConnector.Enum.al rename to Modules/System/File System/src/Connector/FileSystemConnector.Enum.al index 2497a1c764..1e5d197de2 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Enum.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al @@ -6,7 +6,7 @@ /// /// Enum that holds all of the available file connectors. /// -enum 70000 "File Connector" implements "File Connector" +enum 70000 "File System Connector" implements "File System Connector" { Extensible = true; } \ No newline at end of file diff --git a/Modules/System/File Access/src/Connector/FileConnector.Interface.al b/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al similarity index 98% rename from Modules/System/File Access/src/Connector/FileConnector.Interface.al rename to Modules/System/File System/src/Connector/FileSystemConnector.Interface.al index ea91ed83cc..927328cc95 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Interface.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al @@ -4,9 +4,9 @@ // ------------------------------------------------------------------------------------------------ /// -/// An file connector interface used to creating file accounts and handle external files. +/// An File System Connector interface used to creating file accounts and handle external files. /// -interface "File Connector" +interface "File System Connector" { /// /// Gets a List of Files stored on the provided account. diff --git a/Modules/System/File Access/src/Connector/FileConnector.Table.al b/Modules/System/File System/src/Connector/FileSystemConnector.Table.al similarity index 90% rename from Modules/System/File Access/src/Connector/FileConnector.Table.al rename to Modules/System/File System/src/Connector/FileSystemConnector.Table.al index 0d29b46629..c878df2cab 100644 --- a/Modules/System/File Access/src/Connector/FileConnector.Table.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Table.al @@ -3,7 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -table 70001 "File Connector" +table 70001 "File System Connector" { TableType = Temporary; Access = Internal; @@ -12,7 +12,7 @@ table 70001 "File Connector" fields { - field(1; Connector; Enum "File Connector") + field(1; Connector; Enum "File System Connector") { DataClassification = SystemMetadata; } diff --git a/Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al b/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al similarity index 88% rename from Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al rename to Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al index 5a0ee947b9..6dc7d85e87 100644 --- a/Modules/System/File Access/src/Connector/FileConnectorLogo.Table.al +++ b/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al @@ -3,7 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ -table 70002 "File Connector Logo" +table 70002 "File System Connector Logo" { DataClassification = SystemMetadata; Access = Internal; @@ -12,7 +12,7 @@ table 70002 "File Connector Logo" fields { - field(1; Connector; Enum "File Connector") + field(1; Connector; Enum "File System Connector") { DataClassification = SystemMetadata; } diff --git a/Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al similarity index 100% rename from Modules/System/File Access/src/FileSystem/FileSystem.Codeunit.al rename to Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al diff --git a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al similarity index 99% rename from Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al rename to Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al index 28d307c2da..afecd977a4 100644 --- a/Modules/System/File Access/src/FileSystem/FileSystemImp.Codeunit.al +++ b/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al @@ -1,7 +1,7 @@ codeunit 70005 "File System Impl." { var - IFileConnector: Interface "File Connector"; + IFileConnector: Interface "File System Connector"; CurrFileAccount: Record "File Account"; Initialized: Boolean; diff --git a/Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al similarity index 100% rename from Modules/System/File Access/src/Lookup/FileAccountBrowser.Page.al rename to Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al diff --git a/Modules/System/File Access/src/Lookup/FileAccountContent.Table.al b/Modules/System/File System/src/Lookup/FileAccountContent.Table.al similarity index 100% rename from Modules/System/File Access/src/Lookup/FileAccountContent.Table.al rename to Modules/System/File System/src/Lookup/FileAccountContent.Table.al diff --git a/Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al b/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al similarity index 100% rename from Modules/System/File Access/src/Lookup/FilePaginationData.Codeunit.al rename to Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al diff --git a/Modules/System/File Access/src/Lookup/FileType.Enum.al b/Modules/System/File System/src/Lookup/FileType.Enum.al similarity index 100% rename from Modules/System/File Access/src/Lookup/FileType.Enum.al rename to Modules/System/File System/src/Lookup/FileType.Enum.al diff --git a/Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al b/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al similarity index 96% rename from Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al rename to Modules/System/File System/src/Scenario/FileAccountScenario.Table.al index 1354dcbd80..68a5ecbce7 100644 --- a/Modules/System/File Access/src/Scenario/FileAccountScenario.Table.al +++ b/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al @@ -20,7 +20,7 @@ table 70003 "File Account Scenario" DataClassification = SystemMetadata; } - field(2; Connector; Enum "File Connector") + field(2; Connector; Enum "File System Connector") { DataClassification = SystemMetadata; } diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al similarity index 100% rename from Modules/System/File Access/src/Scenario/FileScenario.Codeunit.al rename to Modules/System/File System/src/Scenario/FileScenario.Codeunit.al diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Enum.al b/Modules/System/File System/src/Scenario/FileScenario.Enum.al similarity index 100% rename from Modules/System/File Access/src/Scenario/FileScenario.Enum.al rename to Modules/System/File System/src/Scenario/FileScenario.Enum.al diff --git a/Modules/System/File Access/src/Scenario/FileScenario.Table.al b/Modules/System/File System/src/Scenario/FileScenario.Table.al similarity index 94% rename from Modules/System/File Access/src/Scenario/FileScenario.Table.al rename to Modules/System/File System/src/Scenario/FileScenario.Table.al index 60723a965e..ba45f05f49 100644 --- a/Modules/System/File Access/src/Scenario/FileScenario.Table.al +++ b/Modules/System/File System/src/Scenario/FileScenario.Table.al @@ -19,7 +19,7 @@ table 70004 "File Scenario" DataClassification = SystemMetadata; } - field(2; Connector; Enum "File Connector") + field(2; Connector; Enum "File System Connector") { DataClassification = SystemMetadata; } diff --git a/Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al similarity index 99% rename from Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al rename to Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al index 2fa9d3e697..c3ac2e59be 100644 --- a/Modules/System/File Access/src/Scenario/FileScenarioImpl.Codeunit.al +++ b/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al @@ -151,7 +151,7 @@ codeunit 70003 "File Scenario Impl." FileAccountScenarios.SetCurrentKey("Display Name"); // sort scenarios by "Display Name" end; - local procedure AddEntry(var Result: Record "File Account Scenario"; EntryType: Option; Scenario: Integer; AccountId: Guid; Connector: Enum "File Connector"; DisplayName: Text[2048]; Default: Boolean; var Position: Integer) + local procedure AddEntry(var Result: Record "File Account Scenario"; EntryType: Option; Scenario: Integer; AccountId: Guid; Connector: Enum "File System Connector"; DisplayName: Text[2048]; Default: Boolean; var Position: Integer) begin // Add entry to the result while maintaining the position so that the tree represents the data correctly Result.EntryType := EntryType; diff --git a/Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al b/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al similarity index 98% rename from Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al rename to Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al index cf568cea5d..13d9c573fe 100644 --- a/Modules/System/File Access/src/Scenario/FileScenarioSetup.Page.al +++ b/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al @@ -169,7 +169,7 @@ page 70002 "File Scenario Setup" end; // Used to set the focus on an file account - internal procedure SetFileAccountId(AccountId: Guid; Connector: Enum "File Connector") + internal procedure SetFileAccountId(AccountId: Guid; Connector: Enum "File System Connector") begin FileAccountId := AccountId; FileConnector := Connector; @@ -186,7 +186,7 @@ page 70002 "File Scenario Setup" FileScenarioImpl: Codeunit "File Scenario Impl."; FileAccountImpl: Codeunit "File Account Impl."; FileAccountId: Guid; - FileConnector: Enum "File Connector"; + FileConnector: Enum "File System Connector"; Style, DefaultTxt : Text; TypeOfEntry: Option Account,Scenario; Indentation: Integer; diff --git a/Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al b/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al similarity index 100% rename from Modules/System/File Access/src/Scenario/FileScenariosFactBox.Page.al rename to Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al diff --git a/Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al b/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al similarity index 100% rename from Modules/System/File Access/src/Scenario/FileScenariosForAccount.Page.al rename to Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al From 763a7a00c5c63699c7c68c57b265a81aa35c368f Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 17:49:27 +0200 Subject: [PATCH 11/33] Add directory support to Storage Blob Implementation --- .../src/BlobStorageConnectorImpl.Codeunit.al | 42 +++++++++++- .../src/Lookup/FileAccountBrowser.Page.al | 67 +++++++++++++++++++ .../src/Lookup/FolderNameInput.Page.al | 26 +++++++ 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 Modules/System/File System/src/Lookup/FolderNameInput.Page.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 02345bae54..d13a4ca887 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -12,6 +12,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" ConnectorDescriptionTxt: Label 'Use Azure Blob Storage to store and retrieve files.'; NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; ConnectorBase64LogoTxt: Label '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', Locked = true; + MarkerFileNameTok: Label 'BusinessCentral.FileSystem.txt', Locked = true; /// /// Gets a List of Files stored on the provided account. @@ -35,6 +36,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" ValidateListingResponse(FilePaginationData, ABSOperationResponse); ABSContainerContent.SetFilter("Blob Type", '<>%1', ''); + ABSContainerContent.SetFilter(Name, '<>%1', MarkerFileNameTok); if not ABSContainerContent.FindSet() then exit; @@ -212,8 +214,23 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" /// The file account ID which is used to send out the file. /// The directory path inside the file account. procedure CreateDirectory(AccountId: Guid; Path: Text) + var + TempBlob: Codeunit "Temp Blob"; + FileSystem: Codeunit "File System"; + IStream: InStream; + OStream: OutStream; + DirectoryAlreadyExistsErr: Label 'Directory already exists.'; + MarkerFileContentTok: Label 'This is a directory marker file created by Business Central. It is safe to delete it.', Locked = true; begin + if DirectoryExists(AccountId, Path) then + Error(DirectoryAlreadyExistsErr); + + Path := CombinePath(Path, MarkerFileNameTok); + TempBlob.CreateOutStream(OStream); + OStream.WriteText(MarkerFileContentTok); + TempBlob.CreateInStream(IStream); + SetFile(AccountId, Path, IStream); end; /// @@ -248,8 +265,18 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" /// The file account ID which is used to send out the file. /// The directory path inside the file account. procedure DeleteDirectory(AccountId: Guid; Path: Text) + var + FileAccountContent: Record "File Account Content" temporary; + FilePaginationData: Codeunit "File Pagination Data"; + DirectoryMustBeEmptyErr: Label 'Directory is not empty.'; begin + ListFiles(AccountId, Path, FilePaginationData, FileAccountContent); + ListDirectories(AccountId, Path, FilePaginationData, FileAccountContent); + FileAccountContent.SetFilter(Name, '<>%1', MarkerFileNameTok); + if not FileAccountContent.IsEmpty() then + Error(DirectoryMustBeEmptyErr); + DeleteFile(AccountId, CombinePath(Path, MarkerFileNameTok)); end; /// @@ -410,8 +437,19 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" local procedure CheckPath(var Path: Text) begin - if (Path <> '') and not Path.EndsWith('/') then - Path += '/'; + if (Path <> '') and not Path.EndsWith(PathSeparator()) then + Path += PathSeparator(); + end; + + local procedure CombinePath(Path: Text; ChildPath: Text): Text + begin + if Path = '' then + exit(ChildPath); + + if not Path.EndsWith(PathSeparator()) then + Path += PathSeparator(); + + exit(Path + ChildPath); end; local procedure InitOptionalParameters(var Path: Text; var FilePaginationData: Codeunit "File Pagination Data"; var ABSOptionalParameters: Codeunit "ABS Optional Parameters") diff --git a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al index c913f1ec7d..f3069203fe 100644 --- a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al @@ -6,6 +6,7 @@ page 70005 "File Account Browser" ModifyAllowed = false; InsertAllowed = false; DeleteAllowed = false; + Extensible = false; layout { @@ -82,6 +83,7 @@ page 70005 "File Account Browser" Promoted = true; PromotedOnly = true; PromotedCategory = Process; + Ellipsis = true; Visible = not IsInLookupMode; Enabled = not IsInLookupMode; @@ -91,6 +93,42 @@ page 70005 "File Account Browser" BrowseFolder(CurrPath); end; } + action("Create Directory") + { + Caption = 'Create Directory'; + ApplicationArea = All; + Image = Bin; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + Ellipsis = true; + Visible = not IsInLookupMode; + Enabled = not IsInLookupMode; + + trigger OnAction() + begin + CreateDirectory(); + BrowseFolder(CurrPath); + end; + } + action(Delete) + { + Caption = 'Delete'; + ApplicationArea = All; + Image = Delete; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + Ellipsis = true; + Visible = not IsInLookupMode; + Enabled = not IsInLookupMode; + + trigger OnAction() + begin + DeleteFileOrDirectory(); + BrowseFolder(CurrPath); + end; + } } } @@ -213,6 +251,18 @@ page 70005 "File Account Browser" FileSystem.SetFile(FileSystem.CombinePath(CurrPath, FromFile), Stream); end; + local procedure CreateDirectory() + var + FolderNameInput: Page "Folder Name Input"; + FolderName: Text; + begin + if FolderNameInput.RunModal() <> Action::OK then + exit; + + FolderName := StripNotsupportChrInFileName(FolderNameInput.GetFolderName()); + FileSystem.CreateDirectory(FileSystem.CombinePath(CurrPath, FolderName)); + end; + local procedure ListFiles(var Path: Text) var FileAccountContent: Record "File Account Content" temporary; @@ -242,4 +292,21 @@ page 70005 "File Account Browser" Rec.Insert(); until FileAccountContent.Next() = 0; end; + + local procedure DeleteFileOrDirectory() + var + PathToDelete: Text; + DeleteQst: Label 'Delete %1?', Comment = '%1 - Path to Delete'; + begin + PathToDelete := FileSystem.CombinePath(Rec."Parent Directory", Rec.Name); + if not Confirm(DeleteQst, false, PathToDelete) then + exit; + + case Rec.Type of + Rec.Type::Directory: + FileSystem.DeleteDirectory(PathToDelete); + Rec.Type::File: + FileSystem.DeleteFile(PathToDelete); + end; + end; } diff --git a/Modules/System/File System/src/Lookup/FolderNameInput.Page.al b/Modules/System/File System/src/Lookup/FolderNameInput.Page.al new file mode 100644 index 0000000000..0a26defa87 --- /dev/null +++ b/Modules/System/File System/src/Lookup/FolderNameInput.Page.al @@ -0,0 +1,26 @@ +page 70006 "Folder Name Input" +{ + ApplicationArea = All; + Caption = 'Create Folder...'; + PageType = StandardDialog; + Extensible = false; + + layout + { + area(content) + { + field(FolderNameField; FolderName) + { + Caption = 'Folder Name'; + } + } + } + + var + FolderName: Text; + + internal procedure GetFolderName(): Text + begin + exit(FolderName); + end; +} From e67351e3d215e581900bf18ac60197ec03405027 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 17:58:30 +0200 Subject: [PATCH 12/33] Use actionref in Account Browser --- .../src/Lookup/FileAccountBrowser.Page.al | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al index f3069203fe..adcf1850b9 100644 --- a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al @@ -52,6 +52,13 @@ page 70005 "File Account Browser" actions { + area(Promoted) + { + actionref(UpRef; Up) { } + actionref(UploadRef; Upload) { } + actionref(CreateDirectoryRef; "Create Directory") { } + actionref(DeleteRef; Delete) { } + } area(Processing) { action(Up) @@ -59,9 +66,6 @@ page 70005 "File Account Browser" Caption = 'Up'; ApplicationArea = All; Image = MoveUp; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; Enabled = ParentFolderExists; trigger OnAction() @@ -80,9 +84,6 @@ page 70005 "File Account Browser" Caption = 'Upload'; ApplicationArea = All; Image = Import; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; Ellipsis = true; Visible = not IsInLookupMode; Enabled = not IsInLookupMode; @@ -98,9 +99,6 @@ page 70005 "File Account Browser" Caption = 'Create Directory'; ApplicationArea = All; Image = Bin; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; Ellipsis = true; Visible = not IsInLookupMode; Enabled = not IsInLookupMode; @@ -116,9 +114,6 @@ page 70005 "File Account Browser" Caption = 'Delete'; ApplicationArea = All; Image = Delete; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; Ellipsis = true; Visible = not IsInLookupMode; Enabled = not IsInLookupMode; From ac6c08ead1dbdbb9bce2cb23807059942d4eef66 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 18:11:13 +0200 Subject: [PATCH 13/33] Add permissions --- .../FileSystemAdmin.PermissionSet.al | 16 +++++++++++ .../FileSystemEdit.PermissionSet.al | 15 +++++++++++ .../FileSystemObjects.PermissionSetExt.al | 27 +++++++++++++++++++ .../FileSystemRead.PermissionSet.al | 11 ++++++++ 4 files changed, 69 insertions(+) create mode 100644 Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al create mode 100644 Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al create mode 100644 Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al create mode 100644 Modules/System/File System/permissions/FileSystemRead.PermissionSet.al diff --git a/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al b/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al new file mode 100644 index 0000000000..ee8d01fb91 --- /dev/null +++ b/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al @@ -0,0 +1,16 @@ +permissionset 70000 "File System - Admin" +{ + Access = Public; + Assignable = true; + Caption = 'File System - Admin'; + + IncludedPermissionSets = "File System - Edit"; + + Permissions = + tabledata "File Account" = RMID, + tabledata "File System Connector" = RMID, + tabledata "File System Connector Logo" = RMID, + tabledata "File Account Scenario" = RMID, + tabledata "File Scenario" = RMID, + tabledata "File Account Content" = RMID; +} diff --git a/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al b/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al new file mode 100644 index 0000000000..5cd380190c --- /dev/null +++ b/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al @@ -0,0 +1,15 @@ +permissionset 70002 "File System - Read" +{ + Access = Internal; + Assignable = false; + + IncludedPermissionSets = "File System - Objects"; + + Permissions = tabledata "File Account" = r, + tabledata "File System Connector" = r, + tabledata "File System Connector Logo" = r, + tabledata "File Account Scenario" = r, + tabledata "File Scenario" = r, + tabledata "File Account Content" = r, + tabledata Media = r; // File System Account Wizard requires this +} \ No newline at end of file diff --git a/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al b/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al new file mode 100644 index 0000000000..cc43b9632c --- /dev/null +++ b/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al @@ -0,0 +1,27 @@ +permissionset 70001 "File System - Objects" +{ + Access = Internal; + Assignable = false; + + Permissions = + table "File Account" = X, + table "File System Connector" = X, + table "File System Connector Logo" = X, + table "File Account Scenario" = X, + table "File Scenario" = X, + table "File Account Content" = X, + codeunit "File Account" = X, + codeunit "File Account Impl." = X, + codeunit "File Scenario" = X, + codeunit "File Pagination Data" = X, + codeunit "File System Impl." = X, + codeunit "File System" = X, + codeunit "File Scenario Impl." = X, + page "File Accounts" = X, + page "File Account Wizard" = X, + page "Folder Name Input" = X, + page "File Scenarios FactBox" = X, + page "File Scenarios for Account" = X, + page "File Scenario Setup" = X, + page "File Account Browser" = X; +} diff --git a/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al b/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al new file mode 100644 index 0000000000..719c1f6977 --- /dev/null +++ b/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al @@ -0,0 +1,11 @@ +permissionset 70003 "File System - Edit" +{ + Access = Public; + Assignable = false; + Caption = 'File System - Edit'; + + IncludedPermissionSets = "File System - Read"; + + Permissions = tabledata "File System Connector Logo" = imd, + tabledata "Tenant Media" = imd; +} \ No newline at end of file From 4d88e2cbd69169e7b7bf5bf4c9038b1697ed96d4 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 18:18:19 +0200 Subject: [PATCH 14/33] Add permission sets --- .../app/permissions/BlobStorEdit.PermissionSet.al | 11 +++++++++++ .../permissions/BlobStorObjects.PermissionSet.al | 13 +++++++++++++ .../app/permissions/BlobStorRead.PermissionSet.al | 11 +++++++++++ .../FileSystemAdminBlobStor.PermissionSetExt.al | 4 ++++ 4 files changed, 39 insertions(+) create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al create mode 100644 Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al new file mode 100644 index 0000000000..a5212d76ca --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al @@ -0,0 +1,11 @@ +permissionset 80102 "Blob Stor. - Edit" +{ + Assignable = false; + Access = Public; + Caption = 'Blob Storage - Edit'; + + IncludedPermissionSets = "Blob Stor. - Read"; + + Permissions = + tabledata "Blob Storage Account" = imd; +} diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al new file mode 100644 index 0000000000..f2257a5435 --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al @@ -0,0 +1,13 @@ +permissionset 80100 "Blob Stor. - Objects" +{ + Assignable = false; + Access = Public; + Caption = 'Blob Storage - Objects'; + + Permissions = + table "Blob Storage Account" = X, + codeunit "Blob Storage Connector Impl." = X, + page "Blob Storage Account Wizard" = X, + page "Blob Storage Container Lookup" = X, + page "Blob Storage Account" = X; +} diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al new file mode 100644 index 0000000000..aed13c4b7c --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al @@ -0,0 +1,11 @@ +permissionset 80101 "Blob Stor. - Read" +{ + Assignable = false; + Access = Public; + Caption = 'Blob Storage - Read'; + + IncludedPermissionSets = "Blob Stor. - Objects"; + + Permissions = + tabledata "Blob Storage Account" = r; +} diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al new file mode 100644 index 0000000000..270008c8aa --- /dev/null +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al @@ -0,0 +1,4 @@ +permissionsetextension 80100 "File System - Admin - Blob Stor." extends "File System - Admin" +{ + IncludedPermissionSets = "Blob Stor. - Edit"; +} From c5fc3866dcae815811b238f71c5d58bbbc02a1df Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 6 Oct 2023 18:28:07 +0200 Subject: [PATCH 15/33] Fix application area --- Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al index adcf1850b9..54592ab1d6 100644 --- a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al @@ -44,6 +44,7 @@ page 70005 "File Account Browser" field(SaveFileNameField; SaveFileName) { + ApplicationArea = All; Caption = 'Filename'; } } From 248542ba97d0d106adcee46df9a0e086f0fde83f Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Mon, 9 Oct 2023 16:43:03 +0200 Subject: [PATCH 16/33] Add namespace and disclaimer to all files --- Apps/W1/File - Azure BLOB Storage Connector/app/app.json | 5 ++--- .../app/permissions/BlobStorEdit.PermissionSet.al | 7 +++++++ .../app/permissions/BlobStorObjects.PermissionSet.al | 7 +++++++ .../app/permissions/BlobStorRead.PermissionSet.al | 7 +++++++ .../FileSystemAdminBlobStor.PermissionSetExt.al | 7 +++++++ .../app/src/BlobStorageAccount.Page.al | 2 ++ .../app/src/BlobStorageAccount.Table.al | 2 ++ .../app/src/BlobStorageAccountWizard.Page.al | 4 ++++ .../app/src/BlobStorageConnector.EnumExt.al | 2 ++ .../app/src/BlobStorageConnectorImpl.Codeunit.al | 5 +++++ .../app/src/BlobStorageContainerLookup.Page.al | 9 +++++++++ Modules/System/File System/app.json | 5 ++--- .../permissions/FileSystemAdmin.PermissionSet.al | 7 +++++++ .../permissions/FileSystemEdit.PermissionSet.al | 9 +++++++++ .../permissions/FileSystemObjects.PermissionSetExt.al | 7 +++++++ .../permissions/FileSystemRead.PermissionSet.al | 9 +++++++++ .../File System/src/Account/FileAccount.Codeunit.al | 3 +++ .../System/File System/src/Account/FileAccount.Table.al | 2 ++ .../File System/src/Account/FileAccountImpl.Codeunit.al | 5 +++++ .../File System/src/Account/FileAccountWizard.Page.al | 6 ++++++ .../System/File System/src/Account/FileAccounts.Page.al | 4 ++++ .../src/Connector/FileSystemConnector.Enum.al | 2 ++ .../src/Connector/FileSystemConnector.Interface.al | 2 ++ .../src/Connector/FileSystemConnector.Table.al | 2 ++ .../src/Connector/FileSystemConnectorLogo.Table.al | 2 ++ .../File System/src/FileSystem/FileSystem.Codeunit.al | 7 +++++++ .../File System/src/FileSystem/FileSystemImp.Codeunit.al | 7 +++++++ .../File System/src/Lookup/FileAccountBrowser.Page.al | 7 +++++++ .../File System/src/Lookup/FileAccountContent.Table.al | 7 +++++++ .../src/Lookup/FilePaginationData.Codeunit.al | 7 +++++++ Modules/System/File System/src/Lookup/FileType.Enum.al | 2 ++ .../File System/src/Lookup/FolderNameInput.Page.al | 7 +++++++ .../src/Scenario/FileAccountScenario.Table.al | 2 ++ .../File System/src/Scenario/FileScenario.Codeunit.al | 2 ++ .../System/File System/src/Scenario/FileScenario.Enum.al | 2 ++ .../File System/src/Scenario/FileScenario.Table.al | 2 ++ .../src/Scenario/FileScenarioImpl.Codeunit.al | 4 ++++ .../File System/src/Scenario/FileScenarioSetup.Page.al | 4 ++++ .../src/Scenario/FileScenariosFactBox.Page.al | 2 ++ .../src/Scenario/FileScenariosForAccount.Page.al | 2 ++ 40 files changed, 180 insertions(+), 6 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json index 84a342044b..e65367c8c3 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -4,7 +4,7 @@ "publisher": "Microsoft", "brief": "Enable all users to use a Azure Blob Storage to save files from Business Central.", "description": "This app enables all users to store files in the smae azure blob storage in Business Central.", - "version": "22.1.0.0", + "version": "23.0.0.0", "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", "help": "https://go.microsoft.com/fwlink/?linkid=2134520", @@ -25,7 +25,7 @@ "screenshots": [ ], - "platform": "22.0.0.0", + "platform": "23.0.0.0", "idRanges": [ { "from": 80100, @@ -42,6 +42,5 @@ "allowDownloadingSource": true, "includeSourceInSymbolFile": true }, - "runtime": "11.0", "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" } \ No newline at end of file diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al index a5212d76ca..3638f5ddbb 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionset 80102 "Blob Stor. - Edit" { Assignable = false; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al index f2257a5435..3ef46b9c40 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionset 80100 "Blob Stor. - Objects" { Assignable = false; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al index aed13c4b7c..901716b150 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionset 80101 "Blob Stor. - Read" { Assignable = false; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al index 270008c8aa..848e254aac 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionsetextension 80100 "File System - Admin - Blob Stor." extends "File System - Admin" { IncludedPermissionSets = "Blob Stor. - Edit"; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al index c47f0339db..3f287ab726 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Displays an account that was registered via the Blob Storage connector. /// diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al index 0a2e47601c..438acc8007 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Holds the information for all file accounts that are registered via the Blob Storage connector /// diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al index 520a41780b..c24d3108cd 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al @@ -3,6 +3,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +Using System.Environment; + /// /// Displays an account that is being registered via the Blob Storage connector. /// diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al index 5661d97108..8fcab6400e 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Enum extension to register the Blob Storage connector. /// diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index d13a4ca887..4fad377aa7 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -3,6 +3,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System.Utilities; +using System.Azure.Storage; + codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" { Access = Internal; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al index b068fcb269..13a9da60d7 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al @@ -1,3 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +using System.Azure.Storage; + page 80102 "Blob Storage Container Lookup" { Caption = 'Container Lookup'; diff --git a/Modules/System/File System/app.json b/Modules/System/File System/app.json index 27715f0eda..28816d7ca8 100644 --- a/Modules/System/File System/app.json +++ b/Modules/System/File System/app.json @@ -27,8 +27,8 @@ "screenshots": [ ], - "platform": "22.0.0.0", - "application": "22.0.0.0", + "platform": "23.0.0.0", + "application": "23.0.0.0", "idRanges": [ { "from": 70000, @@ -36,6 +36,5 @@ } ], "target": "OnPrem", - "runtime": "11.0", "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" } \ No newline at end of file diff --git a/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al b/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al index ee8d01fb91..45aaffabee 100644 --- a/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al +++ b/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionset 70000 "File System - Admin" { Access = Public; diff --git a/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al b/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al index 5cd380190c..e4314d462b 100644 --- a/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al +++ b/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al @@ -1,3 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +using System.Environment; + permissionset 70002 "File System - Read" { Access = Internal; diff --git a/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al b/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al index cc43b9632c..de04bf3b01 100644 --- a/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al +++ b/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + permissionset 70001 "File System - Objects" { Access = Internal; diff --git a/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al b/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al index 719c1f6977..43fb9e2e0d 100644 --- a/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al +++ b/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al @@ -1,3 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +using System.Environment; + permissionset 70003 "File System - Edit" { Access = Public; diff --git a/Modules/System/File System/src/Account/FileAccount.Codeunit.al b/Modules/System/File System/src/Account/FileAccount.Codeunit.al index 5c7e7c53cb..d16f624814 100644 --- a/Modules/System/File System/src/Account/FileAccount.Codeunit.al +++ b/Modules/System/File System/src/Account/FileAccount.Codeunit.al @@ -3,9 +3,12 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Provides functionality to work with file accounts. /// + codeunit 70000 "File Account" { Access = Public; diff --git a/Modules/System/File System/src/Account/FileAccount.Table.al b/Modules/System/File System/src/Account/FileAccount.Table.al index f1fe415396..e4317b2d2b 100644 --- a/Modules/System/File System/src/Account/FileAccount.Table.al +++ b/Modules/System/File System/src/Account/FileAccount.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// A common representation of an file account. /// diff --git a/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al index 6e7e19b375..2535a7b38a 100644 --- a/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al +++ b/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al @@ -3,6 +3,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System.Utilities; +using System.Text; + codeunit 70001 "File Account Impl." { Access = Internal; diff --git a/Modules/System/File System/src/Account/FileAccountWizard.Page.al b/Modules/System/File System/src/Account/FileAccountWizard.Page.al index 62a422b33a..f18e1bad57 100644 --- a/Modules/System/File System/src/Account/FileAccountWizard.Page.al +++ b/Modules/System/File System/src/Account/FileAccountWizard.Page.al @@ -3,6 +3,12 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System.Apps; +using System.Environment; +using System.Telemetry; + /// /// Step by step guide for adding a new file account in Business Central /// diff --git a/Modules/System/File System/src/Account/FileAccounts.Page.al b/Modules/System/File System/src/Account/FileAccounts.Page.al index 7a4d8be9e4..d069d9836c 100644 --- a/Modules/System/File System/src/Account/FileAccounts.Page.al +++ b/Modules/System/File System/src/Account/FileAccounts.Page.al @@ -3,6 +3,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System.Telemetry; + /// /// Lists all of the registered file accounts /// diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al b/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al index 1e5d197de2..7cd7562bf2 100644 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Enum that holds all of the available file connectors. /// diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al b/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al index 927328cc95..d0a1316638 100644 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// An File System Connector interface used to creating file accounts and handle external files. /// diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Table.al b/Modules/System/File System/src/Connector/FileSystemConnector.Table.al index c878df2cab..6795e622f2 100644 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Table.al +++ b/Modules/System/File System/src/Connector/FileSystemConnector.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + table 70001 "File System Connector" { TableType = Temporary; diff --git a/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al b/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al index 6dc7d85e87..6d3459eea1 100644 --- a/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al +++ b/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + table 70002 "File System Connector Logo" { DataClassification = SystemMetadata; diff --git a/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al index 4602a75064..41a19f67d8 100644 --- a/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al +++ b/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + codeunit 70004 "File System" { var diff --git a/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al index afecd977a4..c569119e64 100644 --- a/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al +++ b/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + codeunit 70005 "File System Impl." { var diff --git a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al index 54592ab1d6..fec745954b 100644 --- a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al +++ b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + page 70005 "File Account Browser" { Caption = 'File Account Browser'; diff --git a/Modules/System/File System/src/Lookup/FileAccountContent.Table.al b/Modules/System/File System/src/Lookup/FileAccountContent.Table.al index b615a5a694..f4ca1d3ead 100644 --- a/Modules/System/File System/src/Lookup/FileAccountContent.Table.al +++ b/Modules/System/File System/src/Lookup/FileAccountContent.Table.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + table 70005 "File Account Content" { Caption = 'File Account Content'; diff --git a/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al b/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al index a3d9b10b7b..6a5dc90cfc 100644 --- a/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al +++ b/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + codeunit 70006 "File Pagination Data" { var diff --git a/Modules/System/File System/src/Lookup/FileType.Enum.al b/Modules/System/File System/src/Lookup/FileType.Enum.al index e8ff45c6e5..54f6ea811f 100644 --- a/Modules/System/File System/src/Lookup/FileType.Enum.al +++ b/Modules/System/File System/src/Lookup/FileType.Enum.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Indicator of what type the resource is. /// diff --git a/Modules/System/File System/src/Lookup/FolderNameInput.Page.al b/Modules/System/File System/src/Lookup/FolderNameInput.Page.al index 0a26defa87..23239dcce3 100644 --- a/Modules/System/File System/src/Lookup/FolderNameInput.Page.al +++ b/Modules/System/File System/src/Lookup/FolderNameInput.Page.al @@ -1,3 +1,10 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + page 70006 "Folder Name Input" { ApplicationArea = All; diff --git a/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al b/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al index 68a5ecbce7..649a7b951e 100644 --- a/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al +++ b/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Temporary table used to display the tree sctructure in "File Scenario Setup". /// diff --git a/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al index 5544efd605..46a191fb45 100644 --- a/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al +++ b/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Provides functionality to work with file scenarios. /// diff --git a/Modules/System/File System/src/Scenario/FileScenario.Enum.al b/Modules/System/File System/src/Scenario/FileScenario.Enum.al index e4d2e950e9..7a31b1238c 100644 --- a/Modules/System/File System/src/Scenario/FileScenario.Enum.al +++ b/Modules/System/File System/src/Scenario/FileScenario.Enum.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// File scenarios. /// Used to decouple file accounts from sending files. diff --git a/Modules/System/File System/src/Scenario/FileScenario.Table.al b/Modules/System/File System/src/Scenario/FileScenario.Table.al index ba45f05f49..2258a9c28f 100644 --- a/Modules/System/File System/src/Scenario/FileScenario.Table.al +++ b/Modules/System/File System/src/Scenario/FileScenario.Table.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Holds the mapping between file account and scenarios. /// One scenarios is mapped to one file account. diff --git a/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al index c3ac2e59be..6c0922fdfc 100644 --- a/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al +++ b/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al @@ -3,6 +3,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System; + codeunit 70003 "File Scenario Impl." { Access = Internal; diff --git a/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al b/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al index 13d9c573fe..2d6c1c1396 100644 --- a/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al +++ b/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al @@ -3,6 +3,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + +using System.Telemetry; + /// /// Page is used to display file scenarios usage by file accounts. /// diff --git a/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al b/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al index 2a14f292d1..df5bd47cc4 100644 --- a/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al +++ b/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Lists of all scenarios assigned to an account. /// diff --git a/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al b/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al index e4839ff167..ee44822462 100644 --- a/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al +++ b/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al @@ -3,6 +3,8 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // ------------------------------------------------------------------------------------------------ +namespace System.FileSystem; + /// /// Displays the scenarios that could be linked to a provided file account. /// From 06d382a72443f6aba9e6325f74e32c9b61cb7e0b Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Mon, 9 Oct 2023 17:56:59 +0200 Subject: [PATCH 17/33] Fix version --- Modules/System/File System/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/System/File System/app.json b/Modules/System/File System/app.json index 28816d7ca8..c326959d5d 100644 --- a/Modules/System/File System/app.json +++ b/Modules/System/File System/app.json @@ -4,7 +4,7 @@ "publisher": "Microsoft", "brief": "Enables user to send emails from Business Central.", "description": "Enables user to send emails from Business Central.", - "version": "22.1.0.0", + "version": "23.0.0.0", "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", "help": "https://go.microsoft.com/fwlink/?linkid=2103698", From 32793d8f5b0a5b132a80995515f1005091e60f0b Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 09:31:16 +0100 Subject: [PATCH 18/33] Remove system apps --- Modules/System/File System/README.md | 6 - .../FileSystemAdmin.PermissionSet.al | 23 - .../FileSystemEdit.PermissionSet.al | 24 - .../FileSystemObjects.PermissionSetExt.al | 34 -- .../FileSystemRead.PermissionSet.al | 20 - .../src/Account/FileAccount.Codeunit.al | 46 -- .../src/Account/FileAccount.Table.al | 68 --- .../src/Account/FileAccountImpl.Codeunit.al | 247 -------- .../src/Account/FileAccountWizard.Page.al | 528 ------------------ .../src/Account/FileAccounts.Page.al | 332 ----------- .../src/Connector/FileSystemConnector.Enum.al | 14 - .../FileSystemConnector.Interface.al | 148 ----- .../Connector/FileSystemConnector.Table.al | 39 -- .../FileSystemConnectorLogo.Table.al | 35 -- .../src/FileSystem/FileSystem.Codeunit.al | 258 --------- .../src/FileSystem/FileSystemImp.Codeunit.al | 208 ------- .../src/Lookup/FileAccountBrowser.Page.al | 315 ----------- .../src/Lookup/FileAccountContent.Table.al | 39 -- .../src/Lookup/FilePaginationData.Codeunit.al | 33 -- .../File System/src/Lookup/FileType.Enum.al | 31 - .../src/Lookup/FolderNameInput.Page.al | 33 -- .../src/Scenario/FileAccountScenario.Table.al | 74 --- .../src/Scenario/FileScenario.Codeunit.al | 76 --- .../src/Scenario/FileScenario.Enum.al | 24 - .../src/Scenario/FileScenario.Table.al | 42 -- .../src/Scenario/FileScenarioImpl.Codeunit.al | 329 ----------- .../src/Scenario/FileScenarioSetup.Page.al | 198 ------- .../src/Scenario/FileScenariosFactBox.Page.al | 40 -- .../Scenario/FileScenariosForAccount.Page.al | 65 --- 29 files changed, 3329 deletions(-) delete mode 100644 Modules/System/File System/README.md delete mode 100644 Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al delete mode 100644 Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al delete mode 100644 Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al delete mode 100644 Modules/System/File System/permissions/FileSystemRead.PermissionSet.al delete mode 100644 Modules/System/File System/src/Account/FileAccount.Codeunit.al delete mode 100644 Modules/System/File System/src/Account/FileAccount.Table.al delete mode 100644 Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al delete mode 100644 Modules/System/File System/src/Account/FileAccountWizard.Page.al delete mode 100644 Modules/System/File System/src/Account/FileAccounts.Page.al delete mode 100644 Modules/System/File System/src/Connector/FileSystemConnector.Enum.al delete mode 100644 Modules/System/File System/src/Connector/FileSystemConnector.Interface.al delete mode 100644 Modules/System/File System/src/Connector/FileSystemConnector.Table.al delete mode 100644 Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al delete mode 100644 Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al delete mode 100644 Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al delete mode 100644 Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al delete mode 100644 Modules/System/File System/src/Lookup/FileAccountContent.Table.al delete mode 100644 Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al delete mode 100644 Modules/System/File System/src/Lookup/FileType.Enum.al delete mode 100644 Modules/System/File System/src/Lookup/FolderNameInput.Page.al delete mode 100644 Modules/System/File System/src/Scenario/FileAccountScenario.Table.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenario.Codeunit.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenario.Enum.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenario.Table.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al delete mode 100644 Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al diff --git a/Modules/System/File System/README.md b/Modules/System/File System/README.md deleted file mode 100644 index aa74a80495..0000000000 --- a/Modules/System/File System/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Provides an API that lets you connect file system accounts to Business Central so that people can access files outside BC. The file system module consists of the following main entities: - -### File Account -An file account holds the information needed to access a file system from Business Central. - -... \ No newline at end of file diff --git a/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al b/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al deleted file mode 100644 index 45aaffabee..0000000000 --- a/Modules/System/File System/permissions/FileSystemAdmin.PermissionSet.al +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -permissionset 70000 "File System - Admin" -{ - Access = Public; - Assignable = true; - Caption = 'File System - Admin'; - - IncludedPermissionSets = "File System - Edit"; - - Permissions = - tabledata "File Account" = RMID, - tabledata "File System Connector" = RMID, - tabledata "File System Connector Logo" = RMID, - tabledata "File Account Scenario" = RMID, - tabledata "File Scenario" = RMID, - tabledata "File Account Content" = RMID; -} diff --git a/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al b/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al deleted file mode 100644 index e4314d462b..0000000000 --- a/Modules/System/File System/permissions/FileSystemEdit.PermissionSet.al +++ /dev/null @@ -1,24 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Environment; - -permissionset 70002 "File System - Read" -{ - Access = Internal; - Assignable = false; - - IncludedPermissionSets = "File System - Objects"; - - Permissions = tabledata "File Account" = r, - tabledata "File System Connector" = r, - tabledata "File System Connector Logo" = r, - tabledata "File Account Scenario" = r, - tabledata "File Scenario" = r, - tabledata "File Account Content" = r, - tabledata Media = r; // File System Account Wizard requires this -} \ No newline at end of file diff --git a/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al b/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al deleted file mode 100644 index de04bf3b01..0000000000 --- a/Modules/System/File System/permissions/FileSystemObjects.PermissionSetExt.al +++ /dev/null @@ -1,34 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -permissionset 70001 "File System - Objects" -{ - Access = Internal; - Assignable = false; - - Permissions = - table "File Account" = X, - table "File System Connector" = X, - table "File System Connector Logo" = X, - table "File Account Scenario" = X, - table "File Scenario" = X, - table "File Account Content" = X, - codeunit "File Account" = X, - codeunit "File Account Impl." = X, - codeunit "File Scenario" = X, - codeunit "File Pagination Data" = X, - codeunit "File System Impl." = X, - codeunit "File System" = X, - codeunit "File Scenario Impl." = X, - page "File Accounts" = X, - page "File Account Wizard" = X, - page "Folder Name Input" = X, - page "File Scenarios FactBox" = X, - page "File Scenarios for Account" = X, - page "File Scenario Setup" = X, - page "File Account Browser" = X; -} diff --git a/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al b/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al deleted file mode 100644 index 43fb9e2e0d..0000000000 --- a/Modules/System/File System/permissions/FileSystemRead.PermissionSet.al +++ /dev/null @@ -1,20 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Environment; - -permissionset 70003 "File System - Edit" -{ - Access = Public; - Assignable = false; - Caption = 'File System - Edit'; - - IncludedPermissionSets = "File System - Read"; - - Permissions = tabledata "File System Connector Logo" = imd, - tabledata "Tenant Media" = imd; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Account/FileAccount.Codeunit.al b/Modules/System/File System/src/Account/FileAccount.Codeunit.al deleted file mode 100644 index d16f624814..0000000000 --- a/Modules/System/File System/src/Account/FileAccount.Codeunit.al +++ /dev/null @@ -1,46 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Provides functionality to work with file accounts. -/// - -codeunit 70000 "File Account" -{ - Access = Public; - - /// - /// Gets all of the file accounts registered in Business Central. - /// - /// Flag, used to determine whether to load the logos for the accounts. - /// Out parameter holding the file accounts. - procedure GetAllAccounts(LoadLogos: Boolean; var TempFileAccount: Record "File Account" temporary) - begin - FileAccountImpl.GetAllAccounts(LoadLogos, TempFileAccount); - end; - - /// - /// Gets all of the file accounts registered in Business Central. - /// - /// Out parameter holding the file accounts. - procedure GetAllAccounts(var TempFileAccount: Record "File Account" temporary) - begin - FileAccountImpl.GetAllAccounts(false, TempFileAccount); - end; - - /// - /// Checks if there is at least one file account registered in Business Central. - /// - /// True if there is any account registered in the system, otherwise - false. - procedure IsAnyAccountRegistered(): Boolean - begin - exit(FileAccountImpl.IsAnyAccountRegistered()); - end; - - var - FileAccountImpl: Codeunit "File Account Impl."; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Account/FileAccount.Table.al b/Modules/System/File System/src/Account/FileAccount.Table.al deleted file mode 100644 index e4317b2d2b..0000000000 --- a/Modules/System/File System/src/Account/FileAccount.Table.al +++ /dev/null @@ -1,68 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// A common representation of an file account. -/// -table 70000 "File Account" -{ - Extensible = false; - TableType = Temporary; - - fields - { - field(1; "Account Id"; Guid) - { - DataClassification = SystemMetadata; - } - - field(2; Name; Text[250]) - { - DataClassification = SystemMetadata; // Field only in Memory - } - - field(4; Connector; Enum "File System Connector") - { - DataClassification = SystemMetadata; - } - - field(5; Logo; Media) - { - Access = Internal; - DataClassification = SystemMetadata; - } - - field(6; LogoBlob; Blob) - { - Access = Internal; - DataClassification = SystemMetadata; - Subtype = Bitmap; - } - } - - keys - { - key(PK; "Account Id", Connector) - { - Clustered = true; - } - - key(Name; Name) - { - Description = 'Used for sorting'; - } - } - - fieldgroups - { - fieldgroup(Brick; Logo, Name) - { - - } - } - -} \ No newline at end of file diff --git a/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al b/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al deleted file mode 100644 index 2535a7b38a..0000000000 --- a/Modules/System/File System/src/Account/FileAccountImpl.Codeunit.al +++ /dev/null @@ -1,247 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Utilities; -using System.Text; - -codeunit 70001 "File Account Impl." -{ - Access = Internal; - InherentPermissions = X; - InherentEntitlements = X; - Permissions = tabledata "File System Connector Logo" = rimd, - tabledata "File Scenario" = imd; - - procedure GetAllAccounts(LoadLogos: Boolean; var TempFileAccount: Record "File Account" temporary) - var - FileAccounts: Record "File Account"; - IFileConnector: Interface "File System Connector"; - Connector: Enum "File System Connector"; - begin - TempFileAccount.Reset(); - TempFileAccount.DeleteAll(); - - foreach Connector in Connector.Ordinals do begin - IFileConnector := Connector; - - FileAccounts.DeleteAll(); - IFileConnector.GetAccounts(FileAccounts); - - if FileAccounts.FindSet() then - repeat - TempFileAccount := FileAccounts; - TempFileAccount.Connector := Connector; - - if LoadLogos then begin - ImportLogo(TempFileAccount, Connector); - ImportLogoBlob(TempFileAccount, Connector); - end; - - if not TempFileAccount.Insert() then; - until FileAccounts.Next() = 0; - end; - - // Sort by account name - TempFileAccount.SetCurrentKey(Name); - end; - - procedure DeleteAccounts(var FileAccountsToDelete: Record "File Account") - var - CurrentDefaultFileAccount: Record "File Account"; - ConfirmManagement: Codeunit "Confirm Management"; - FileScenario: Codeunit "File Scenario"; - FileConnector: Interface "File System Connector"; - begin - CheckPermissions(); - - if not ConfirmManagement.GetResponseOrDefault(ConfirmDeleteQst, true) then - exit; - - if not FileAccountsToDelete.FindSet() then - exit; - - // Get the current default account to track if it was deleted - FileScenario.GetDefaultFileAccount(CurrentDefaultFileAccount); - - // Delete all selected acounts - repeat - // Check to validate that the connector is still installed - // The connector could have been uninstalled by another user/session - if IsValidConnector(FileAccountsToDelete.Connector) then begin - FileConnector := FileAccountsToDelete.Connector; - FileConnector.DeleteAccount(FileAccountsToDelete."Account Id"); - end; - until FileAccountsToDelete.Next() = 0; - - HandleDefaultAccountDeletion(CurrentDefaultFileAccount."Account Id", CurrentDefaultFileAccount.Connector); - end; - - local procedure HandleDefaultAccountDeletion(CurrentDefaultAccountId: Guid; Connector: Enum "File System Connector") - var - AllFileAccounts: Record "File Account"; - NewDefaultFileAccount: Record "File Account"; - FileScenario: Codeunit "File Scenario"; - begin - GetAllAccounts(false, AllFileAccounts); - - if AllFileAccounts.IsEmpty() then - exit; //All of the accounts were deleted, nothing to do - - if AllFileAccounts.Get(CurrentDefaultAccountId, Connector) then - exit; // The default account was not deleted or it never existed - - // In case there's only one account, set it as default - if AllFileAccounts.Count() = 1 then begin - MakeDefault(AllFileAccounts); - exit; - end; - - Commit(); // Commit the accounts deletion in order to prompt for new default account - if PromptNewDefaultAccountChoice(NewDefaultFileAccount) then - MakeDefault(NewDefaultFileAccount) - else - FileScenario.UnassignScenario(Enum::"File Scenario"::Default); // remove the default scenario as it is pointing to a non-existent account - end; - - local procedure PromptNewDefaultAccountChoice(var NewDefaultFileAccount: Record "File Account"): Boolean - var - FileAccountsPage: Page "File Accounts"; - begin - FileAccountsPage.LookupMode(true); - FileAccountsPage.EnableLookupMode(); - FileAccountsPage.Caption(ChooseNewDefaultTxt); - if FileAccountsPage.RunModal() = Action::LookupOK then begin - FileAccountsPage.GetAccount(NewDefaultFileAccount); - exit(true); - end; - - exit(false); - end; - - local procedure ImportLogo(var FileAccount: Record "File Account"; Connector: Interface "File System Connector") - var - FileConnectorLogo: Record "File System Connector Logo"; - TempBlob: Codeunit "Temp Blob"; - Base64Convert: Codeunit "Base64 Convert"; - ConnectorLogoBase64: Text; - OutStream: Outstream; - InStream: InStream; - ConnectorLogoDescriptionTxt: Label '%1 Logo', Locked = true; - begin - ConnectorLogoBase64 := Connector.GetLogoAsBase64(); - - if ConnectorLogoBase64 = '' then - exit; - if not FileConnectorLogo.Get(FileAccount.Connector) then begin - TempBlob.CreateOutStream(OutStream); - Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); - TempBlob.CreateInStream(InStream); - FileConnectorLogo.Connector := FileAccount.Connector; - FileConnectorLogo.Logo.ImportStream(InStream, StrSubstNo(ConnectorLogoDescriptionTxt, FileAccount.Connector)); - if FileConnectorLogo.Insert() then; - end; - FileAccount.Logo := FileConnectorLogo.Logo - end; - - procedure IsAnyAccountRegistered(): Boolean - var - FileAccount: Record "File Account"; - begin - GetAllAccounts(false, FileAccount); - - exit(not FileAccount.IsEmpty()); - end; - - internal procedure IsUserFileAdmin(): Boolean - var - FileScenario: Record "File Scenario"; - begin - exit(FileScenario.WritePermission()); - end; - - procedure FindAllConnectors(var FileConnector: Record "File System Connector") - var - Base64Convert: Codeunit "Base64 Convert"; - ConnectorInterface: Interface "File System Connector"; - Connector: Enum "File System Connector"; - ConnectorLogoBase64: Text; - OutStream: Outstream; - begin - foreach Connector in Enum::"File System Connector".Ordinals() do begin - ConnectorInterface := Connector; - ConnectorLogoBase64 := ConnectorInterface.GetLogoAsBase64(); - FileConnector.Connector := Connector; - FileConnector.Description := ConnectorInterface.GetDescription(); - if ConnectorLogoBase64 <> '' then begin - FileConnector.Logo.CreateOutStream(OutStream); - Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); - end; - FileConnector.Insert(); - end; - end; - - procedure IsValidConnector(Connector: Enum "File System Connector"): Boolean - begin - exit("File System Connector".Ordinals().Contains(Connector.AsInteger())); - end; - - procedure MakeDefault(var FileAccount: Record "File Account") - var - FileScenario: Codeunit "File Scenario"; - begin - CheckPermissions(); - - if IsNullGuid(FileAccount."Account Id") then - exit; - - FileScenario.SetDefaultFileAccount(FileAccount); - end; - - procedure BrowseAccount(var FileAccount: Record "File Account") - var - FileAccountBrowser: Page "File Account Browser"; - begin - CheckPermissions(); - - if IsNullGuid(FileAccount."Account Id") then - exit; - - FileAccountBrowser.SetFileAcconut(FileAccount); - FileAccountBrowser.BrowseFileAccount(''); - FileAccountBrowser.Run(); - end; - - internal procedure CheckPermissions() - begin - if not IsUserFileAdmin() then - Error(CannotManageSetupErr); - end; - - local procedure ImportLogoBlob(var FileAccount: Record "File Account"; Connector: Interface "File System Connector") - var - Base64Convert: Codeunit "Base64 Convert"; - ConnectorLogoBase64: Text; - OutStream: Outstream; - begin - ConnectorLogoBase64 := Connector.GetLogoAsBase64(); - - if ConnectorLogoBase64 <> '' then begin - FileAccount.LogoBlob.CreateOutStream(OutStream); - Base64Convert.FromBase64(ConnectorLogoBase64, OutStream); - end; - end; - - [InternalEvent(false)] - internal procedure OnAfterSetSelectionFilter(var FileAccount: Record "File Account") - begin - end; - - var - ConfirmDeleteQst: Label 'Go ahead and delete?'; - ChooseNewDefaultTxt: Label 'Choose a Default Account'; - CannotManageSetupErr: Label 'Your user account does not give you permission to set up file. Please contact your administrator.'; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Account/FileAccountWizard.Page.al b/Modules/System/File System/src/Account/FileAccountWizard.Page.al deleted file mode 100644 index f18e1bad57..0000000000 --- a/Modules/System/File System/src/Account/FileAccountWizard.Page.al +++ /dev/null @@ -1,528 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Apps; -using System.Environment; -using System.Telemetry; - -/// -/// Step by step guide for adding a new file account in Business Central -/// -page 70001 "File Account Wizard" -{ - PageType = NavigatePage; - ApplicationArea = All; - UsageCategory = Administration; - Caption = 'Set Up File'; - SourceTable = "File System Connector"; - SourceTableTemporary = true; - InsertAllowed = false; - ModifyAllowed = false; - DeleteAllowed = false; - Editable = true; - ShowFilter = false; - LinksAllowed = false; - Permissions = tabledata Media = r, - tabledata "Media Resources" = r; - - layout - { - area(Content) - { - - group(Done) - { - Editable = false; - ShowCaption = false; - Visible = not DoneVisible and TopBannerVisible; - field(NotDoneIcon; MediaResourcesStandard."Media Reference") - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - ToolTip = ' '; - Caption = ' '; - } - } - group(NotDone) - { - Editable = false; - ShowCaption = false; - Visible = DoneVisible and TopBannerVisible; - field(DoneIcon; MediaResourcesDone."Media Reference") - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - ToolTip = ' '; - Caption = ' '; - } - } - - group(Header) - { - ShowCaption = false; - Visible = WelcomeVisible; - - group(HeaderText) - { - Caption = 'Welcome to file in Business Central'; - InstructionalText = 'Make file communications easier by connecting file accounts to Business Central. For example, store sales quotes and orders pdfs without opening an file app.'; - } - - field(LearnMoreHeader; LearnMoreTok) - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - Caption = ' '; - ToolTip = 'View information about how to set up the file capabilities.'; - - trigger OnDrillDown() - begin - Hyperlink(LearnMoreURLTxt); - end; - } - - group(Privacy) - { - Caption = 'Privacy notice'; - InstructionalText = 'By adding an file account you acknowledge that the file provider might be able to access the data you send in files from Business Central.'; - } - - group(GetStartedText) - { - Caption = 'Let''s go!'; - InstructionalText = 'Choose Next to get started.'; - } - } - - group(ConnectorHeader) - { - ShowCaption = false; - Visible = ChooseConnectorVisible and ConnectorsAvailable; - - label(UsageWarning) - { - Caption = 'Use caution when adding file accounts. Depending on your setup, accounts can be available to all users.'; - } - } - - group(ConnectorsGroup) - { - Visible = ChooseConnectorVisible and ConnectorsAvailable; - label("Specify the type of file account to add") - { - Caption = 'Specify the type of file account to add'; - ApplicationArea = All; - } - - repeater(Connectors) - { - ShowCaption = false; - Visible = ChooseConnectorVisible and ConnectorsAvailable; - FreezeColumn = Name; - Editable = false; - - field(Logo; Rec.Logo) - { - ApplicationArea = All; - Caption = ' '; - Editable = false; - Visible = ChooseConnectorVisible; - ToolTip = 'Select the type of account you want to create.'; - ShowCaption = false; - Width = 1; - } - - field(Name; Rec.Connector) - { - ApplicationArea = All; - Caption = 'Account Type'; - ToolTip = 'Specifies the type of the account you want to create.'; - Editable = false; - } - - field(Details; Rec.Description) - { - ApplicationArea = All; - Caption = 'Details'; - ToolTip = 'Specifies more details about the account type.'; - Editable = false; - Width = 50; - } - } - } - - group(NoConnectrosAvailableGroup) - { - Visible = ChooseConnectorVisible and not ConnectorsAvailable; - label(NoConnectorsAvailable) - { - ApplicationArea = All; - Caption = 'There are no file apps available. To use this feature you must install an file app.'; - } - - label(NoConnectorsAvailable2) - { - ApplicationArea = All; - Caption = 'File apps are available in Extension Management and AppSource.'; - } - - field(ExtensionManagement; ExtensionManagementTok) - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - Caption = ' '; - ToolTip = 'Navigate to Extension Management page.'; - - trigger OnDrillDown() - begin - Page.Run(Page::"Extension Management"); - end; - } - - field(AppSource; AppSourceTok) - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - Visible = AppSourceAvailable; - Caption = ' '; - ToolTip = 'Navigate to AppSource.'; - - trigger OnDrillDown() - begin - //FIXME - /* - AppSource := AppSource.Create(); - AppSource.ShowAppSource(); - */ - end; - } - - label(NoConnectorsAvailable3) - { - ApplicationArea = All; - Caption = 'View a list of the available file apps'; - } - - field(LearnMore; LearnMoreTok) - { - ApplicationArea = All; - Editable = false; - ShowCaption = false; - Caption = ' '; - ToolTip = 'View information about how to set up the file capabilities.'; - - trigger OnDrillDown() - begin - Hyperlink(LearnMoreURLTxt); - end; - } - } - - group(LastPage) - { - Visible = DoneVisible; - - group(AllSet) - { - Caption = 'Congratulations!'; - InstructionalText = 'You have successfully added the file account. To check that it is working, send a test file.'; - } - - group(Account) - { - Caption = 'Account'; - field(Namefield; RegisteredAccount.Name) - { - ApplicationArea = All; - Editable = false; - Caption = 'Name'; - ToolTip = 'Specifies the name of the account registered.'; - } - } - - group(Default) - { - Caption = ''; - - field(DefaultField; SetAsDefault) - { - ApplicationArea = All; - Editable = true; - Enabled = true; - Caption = 'Set as default'; - ToolTip = 'Use this account for all scenarios for which an account is not specified. Scenarios are processes that involve sending documents or notifications by file.'; - } - } - } - } - } - - actions - { - area(Processing) - { - - action(Cancel) - { - ApplicationArea = All; - Visible = CancelActionVisible; - Caption = 'Cancel'; - ToolTip = 'Cancel'; - InFooterBar = true; - Image = Cancel; - - trigger OnAction() - begin - CurrPage.Close(); - end; - } - - action(Back) - { - ApplicationArea = All; - Visible = BackActionVisible; - Enabled = BackActionEnabled; - Caption = 'Back'; - ToolTip = 'Back'; - InFooterBar = true; - Image = PreviousRecord; - - trigger OnAction() - begin - NextStep(true); - end; - } - - action(Next) - { - ApplicationArea = All; - Visible = NextActionVisible; - Enabled = NextActionEnabled; - Caption = 'Next'; - ToolTip = 'Next'; - InFooterBar = true; - Image = NextRecord; - - trigger OnAction() - begin - NextStep(false); - end; - } - - action(Finish) - { - ApplicationArea = All; - Visible = FinishActionVisible; - Caption = 'Finish'; - ToolTip = 'Finish'; - InFooterBar = true; - Image = NextRecord; - - trigger OnAction() - var - FileAccountImpl: Codeunit "File Account Impl."; - begin - if SetAsDefault then - FileAccountImpl.MakeDefault(RegisteredAccount); - - CurrPage.Close(); - end; - } - } - } - - trigger OnOpenPage() - begin - StartTime := CurrentDateTime(); - end; - - trigger OnQueryClosePage(CloseAction: Action): Boolean - var - DurationAsInt: Integer; - begin - DurationAsInt := CurrentDateTime() - StartTime; - if Step = Step::Done then - Session.LogMessage('0000CTK', StrSubstNo(AccountCreationSuccessfullyCompletedDurationLbl, DurationAsInt), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl) - else - Session.LogMessage('0000CTL', StrSubstNo(AccountCreationFailureDurationLbl, DurationAsInt), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); - end; - - trigger OnInit() - var - DefaultAccount: Record "File Account"; - FileAccountImpl: Codeunit "File Account Impl."; - FileScenario: Codeunit "File Scenario"; - begin - FileAccountImpl.CheckPermissions(); - - Step := Step::Welcome; - SetDefaultControls(); - ShowWelcomeStep(); - - FileAccountImpl.FindAllConnectors(Rec); - - FileRateLimitDisplay := NoLimitTxt; - - if not FileScenario.GetDefaultFileAccount(DefaultAccount) then - SetAsDefault := true; - - ConnectorsAvailable := Rec.FindFirst(); // Set the focus on the first record - AppSourceAvailable := false; //FIXME AppSource.IsAvailable(); - LoadTopBanners(); - end; - - local procedure NextStep(Backwards: Boolean) - begin - if Backwards then - Step -= 1 - else - Step += 1; - - SetDefaultControls(); - - case Step of - Step::Welcome: - ShowWelcomeStep(); - Step::"Choose Connector": - ShowChooseConnectorStep(); - Step::"Register Account": - ShowRegisterAccountStep(); - Step::"Done": - ShowDoneStep(); - end; - end; - - local procedure ShowWelcomeStep() - begin - WelcomeVisible := true; - BackActionEnabled := false; - end; - - local procedure ShowChooseConnectorStep() - begin - if not ConnectorsAvailable then - NextActionEnabled := false; - - ChooseConnectorVisible := true; - end; - - local procedure ShowRegisterAccountStep() - var - FeatureTelemetry: Codeunit "Feature Telemetry"; - DefaultFileRateLimit: Integer; - AccountWasRegistered: Boolean; - ConnectorSucceeded: Boolean; - begin - ConnectorSucceeded := TryRegisterAccount(AccountWasRegistered); - - if AccountWasRegistered then begin - FeatureTelemetry.LogUptake('0000CTF', 'File Access', Enum::"Feature Uptake Status"::"Set up"); - Session.LogMessage('0000CTH', Format(Rec.Connector) + ' account has been setup.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); - NextStep(false); - end else begin - Session.LogMessage('0000CTI', StrSubstNo(Format(Rec.Connector) + ' account has failed to setup. Error: %1', GetLastErrorCallStack()), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FileCategoryLbl); - NextStep(true); - end; - - if not ConnectorSucceeded then - Error(GetLastErrorText()); - end; - - [TryFunction] - local procedure TryRegisterAccount(var AccountWasRegistered: Boolean) - var - FileAccountImpl: Codeunit "File Account Impl."; - FileConnector: Interface "File System Connector"; - begin - // Check to validate that the connector is still installed - // The connector could have been uninstalled by another user/session - if not FileAccountImpl.IsValidConnector(Rec.Connector) then - Error(FileConnectorHasBeenUninstalledMsg); - - FileConnector := Rec.Connector; - - ClearLastError(); - AccountWasRegistered := FileConnector.RegisterAccount(RegisteredAccount); - RegisteredAccount.Connector := Rec.Connector; - end; - - local procedure ShowDoneStep() - begin - DoneVisible := true; - BackActionVisible := false; - NextActionVisible := false; - CancelActionVisible := false; - FinishActionVisible := true; - TestFileActionVisible := true; - end; - - local procedure SetDefaultControls() - begin - // Actions - BackActionVisible := true; - BackActionEnabled := true; - NextActionVisible := true; - NextActionEnabled := true; - CancelActionVisible := true; - FinishActionVisible := false; - TestFileActionVisible := false; - - // Groups - WelcomeVisible := false; - ChooseConnectorVisible := false; - DoneVisible := false; - end; - - local procedure LoadTopBanners() - var - AssistedSetupLogoTok: Label 'ASSISTEDSETUP-NOTEXT-400PX.PNG', Locked = true; - begin - if MediaResourcesStandard.Get(AssistedSetupLogoTok) and - MediaResourcesDone.Get(AssistedSetupLogoTok) and (CurrentClientType() = ClientType::Web) - then - TopBannerVisible := MediaResourcesDone."Media Reference".HasValue(); - end; - - var - RegisteredAccount: Record "File Account"; - MediaResourcesStandard: Record "Media Resources"; - MediaResourcesDone: Record "Media Resources"; - //FIXME [RunOnClient] - //FIXME AppSource: DotNet AppSource; - Step: Option Welcome,"Choose Connector","Register Account",Done; - RateLimit: Integer; - AppSourceTok: Label 'AppSource'; - ExtensionManagementTok: Label 'Extension Management'; - FileCategoryLbl: Label 'File', Locked = true; - LearnMoreURLTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2134520', Locked = true; //FIXME - LearnMoreTok: Label 'Learn more'; - NoLimitTxt: Label 'No limit'; - AccountCreationSuccessfullyCompletedDurationLbl: Label 'Successful creation of account completed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; - AccountCreationFailureDurationLbl: Label 'Creation of account failed. Duration: %1 milliseconds.', Comment = '%1 - Duration', Locked = true; - FileConnectorHasBeenUninstalledMsg: Label 'The selected file extension has been uninstalled. You must reinstall the extension to add an account with it.'; - AppSourceAvailable: Boolean; - TopBannerVisible: Boolean; - BackActionVisible: Boolean; - BackActionEnabled: Boolean; - NextActionVisible: Boolean; - NextActionEnabled: Boolean; - CancelActionVisible: Boolean; - FinishActionVisible: Boolean; - TestFileActionVisible: Boolean; - WelcomeVisible: Boolean; - ChooseConnectorVisible: Boolean; - DoneVisible: Boolean; - ConnectorsAvailable: Boolean; - SetAsDefault: Boolean; - StartTime: DateTime; - FileRateLimitDisplay: Text[250]; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Account/FileAccounts.Page.al b/Modules/System/File System/src/Account/FileAccounts.Page.al deleted file mode 100644 index d069d9836c..0000000000 --- a/Modules/System/File System/src/Account/FileAccounts.Page.al +++ /dev/null @@ -1,332 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Telemetry; - -/// -/// Lists all of the registered file accounts -/// -page 70000 "File Accounts" -{ - PageType = List; - Caption = 'File Accounts'; - ApplicationArea = All; - UsageCategory = Administration; - SourceTable = "File Account"; - SourceTableTemporary = true; - PromotedActionCategories = 'New,Process,Report,Navigate'; - InsertAllowed = false; - ModifyAllowed = false; - DeleteAllowed = false; - Editable = false; - ShowFilter = false; - LinksAllowed = false; - RefreshOnActivate = true; - - layout - { - area(Content) - { - repeater(Accounts) - { - Visible = ShowLogo; - FreezeColumn = NameField; - field(LogoField; Rec.LogoBlob) - { - ApplicationArea = All; - ShowCaption = false; - Caption = ' '; - Visible = ShowLogo; - ToolTip = 'Specifies the logo for the type of file account.'; - Width = 1; - } - - field(NameField; Rec.Name) - { - ApplicationArea = All; - ToolTip = 'Specifies the name of the account.'; - Visible = not IsInLookupMode; - - trigger OnDrillDown() - begin - ShowAccountInformation(); - end; - } - - field(NameFieldLookup; Rec.Name) - { - ApplicationArea = All; - ToolTip = 'Specifies the name of the account.'; - Visible = IsInLookupMode; - } - - field(DefaultField; DefaultTxt) - { - ApplicationArea = All; - Caption = 'Default'; - ToolTip = 'Specifies whether the file account will be used for all scenarios for which an account is not specified. You must have a default file account, even if you have only one account.'; - Visible = not IsInLookupMode; - } - - field(FileConnector; Rec.Connector) - { - ApplicationArea = All; - ToolTip = 'Specifies the type of file extension that the account is added to.'; - Visible = false; - } - } - } - - area(factboxes) - { - part(Scenarios; "File Scenarios FactBox") - { - Caption = 'File Scenarios'; - SubPageLink = "Account Id" = field("Account Id"), Connector = field(Connector), Scenario = filter(<> 0); // Do not show Default scenario - ApplicationArea = All; - } - } - } - - actions - { - area(Creation) - { - action(View) - { - ApplicationArea = All; - Image = View; - ToolTip = 'View settings for the file account.'; - ShortcutKey = return; - Visible = false; - - trigger OnAction() - begin - ShowAccountInformation(); - end; - } - - action(AddAccount) - { - ApplicationArea = All; - Promoted = true; - PromotedOnly = true; - PromotedCategory = New; - Image = Add; - Caption = 'Add an file account'; - ToolTip = 'Add an file account.'; - Visible = (not IsInLookupMode) and CanUserManageFileSetup; - - trigger OnAction() - begin - Page.RunModal(Page::"File Account Wizard"); - - UpdateFileAccounts(); - end; - } - } - - area(Processing) - { - action(MakeDefault) - { - ApplicationArea = All; - Image = Default; - Caption = 'Set as default'; - ToolTip = 'Mark the selected file account as the default account. This account will be used for all scenarios for which an account is not specified.'; - Visible = (not IsInLookupMode) and CanUserManageFileSetup; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - PromotedIsBig = true; - Scope = Repeater; - Enabled = not IsDefault; - - trigger OnAction() - begin - FileAccountImpl.MakeDefault(Rec); - - UpdateAccounts := true; - CurrPage.Update(false); - end; - } - action(BrowseAccount) - { - ApplicationArea = All; - Image = SelectField; - Caption = 'Browse Account'; - ToolTip = 'Opens a File Browser and shows the content of the selected account.'; - Visible = (not IsInLookupMode) and CanUserManageFileSetup; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - PromotedIsBig = true; - Scope = Repeater; - - trigger OnAction() - begin - FileAccountImpl.BrowseAccount(Rec); - - UpdateAccounts := true; - CurrPage.Update(false); - end; - } - - action(Delete) - { - ApplicationArea = All; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - Image = Delete; - Caption = 'Delete file account'; - ToolTip = 'Delete the file account.'; - Visible = (not IsInLookupMode) and CanUserManageFileSetup; - Scope = Repeater; - - trigger OnAction() - begin - CurrPage.SetSelectionFilter(Rec); - FileAccountImpl.OnAfterSetSelectionFilter(Rec); - - FileAccountImpl.DeleteAccounts(Rec); - - UpdateFileAccounts(); - end; - } - } - - area(Navigation) - { - action(FileScenarioSetup) - { - ApplicationArea = All; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Category4; - Image = Answers; - Caption = 'File Scenarios'; - ToolTip = 'Assign scenarios to the file accounts.'; - Visible = not IsInLookupMode; - - trigger OnAction() - var - FileScenarioSetup: Page "File Scenario Setup"; - begin - FileScenarioSetup.SetFileAccountId(Rec."Account Id", Rec.Connector); - FileScenarioSetup.Run(); - end; - } - } - } - - trigger OnOpenPage() - var - FeatureTelemetry: Codeunit "Feature Telemetry"; - begin - FeatureTelemetry.LogUptake('0000CTA', 'Fileing', Enum::"Feature Uptake Status"::Discovered); - CanUserManageFileSetup := FileAccountImpl.IsUserFileAdmin(); - Rec.SetCurrentKey("Account Id", Connector); - UpdateFileAccounts(); - ShowLogo := true; - end; - - trigger OnAfterGetRecord() - begin - // Updating the accounts is done via OnAfterGetRecord in the cases when an account was changed from the corresponding connector's page - if UpdateAccounts then begin - UpdateAccounts := false; - UpdateFileAccounts(); - end; - - DefaultTxt := ''; - - IsDefault := DefaultFileAccount."Account Id" = Rec."Account Id"; - if IsDefault then - DefaultTxt := '✓'; - end; - - local procedure UpdateFileAccounts() - var - FileAccount: Codeunit "File Account"; - FileScenario: Codeunit "File Scenario"; - IsSelected: Boolean; - SelectedAccountId: Guid; - begin - // We need this code block to maintain the same selected record. - SelectedAccountId := Rec."Account Id"; - IsSelected := not IsNullGuid(SelectedAccountId); - - FileAccount.GetAllAccounts(true, Rec); // Refresh the file accounts - FileScenario.GetDefaultFileAccount(DefaultFileAccount); // Refresh the default file account - - if IsSelected then begin - Rec."Account Id" := SelectedAccountId; - if Rec.Find() then; - end else - if Rec.FindFirst() then; - - HasFileAccount := not Rec.IsEmpty(); - - CurrPage.Update(false); - end; - - local procedure ShowAccountInformation() - var - FileAccountImpl: Codeunit "File Account Impl."; - Connector: Interface "File System Connector"; - begin - UpdateAccounts := true; - -#pragma warning disable AL0603 - if not FileAccountImpl.IsValidConnector(Rec.Connector.AsInteger()) then -#pragma warning restore AL0603 - Error(FileConnectorHasBeenUninstalledMsg); - - Connector := Rec.Connector; - Connector.ShowAccountInformation(Rec."Account Id"); - end; - - /// - /// Gets the selected file account. - /// - /// The selected file account - procedure GetAccount(var FileAccount: Record "File Account") - begin - FileAccount := Rec; - end; - - /// - /// Sets an file account to be selected. - /// - /// The file account to be initially selected on the page - procedure SetAccount(var FileAccount: Record "File Account") - begin - Rec := FileAccount; - end; - - /// - /// Enables the lookup mode on the page. - /// - procedure EnableLookupMode() - begin - IsInLookupMode := true; - CurrPage.LookupMode(true); - end; - - var - DefaultFileAccount: Record "File Account"; - FileAccountImpl: Codeunit "File Account Impl."; - IsDefault: Boolean; - CanUserManageFileSetup: Boolean; - DefaultTxt: Text; - UpdateAccounts: Boolean; - ShowLogo: Boolean; - IsInLookupMode: Boolean; - HasFileAccount: Boolean; - FileConnectorHasBeenUninstalledMsg: Label 'The selected file extension has been uninstalled. To view information about the file account, you must reinstall the extension.'; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al b/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al deleted file mode 100644 index 7cd7562bf2..0000000000 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Enum.al +++ /dev/null @@ -1,14 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Enum that holds all of the available file connectors. -/// -enum 70000 "File System Connector" implements "File System Connector" -{ - Extensible = true; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al b/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al deleted file mode 100644 index d0a1316638..0000000000 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Interface.al +++ /dev/null @@ -1,148 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// An File System Connector interface used to creating file accounts and handle external files. -/// -interface "File System Connector" -{ - /// - /// Gets a List of Files stored on the provided account. - /// - /// The file account ID which is used to get the file. - /// The file path to list. - /// Defines the pagination data. - /// A list with all files stored in the path. - procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary); - - /// - /// Gets a file from the provided account. - /// - /// The file account ID which is used to get the file. - /// The file path inside the file account. - /// The Stream were the file is read to. - procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream); - - /// - /// Gets a file to the provided account. - /// - /// The file account ID which is used to send out the file. - /// The file path inside the file account. - /// The Stream were the file is read from. - procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream); - - - /// - /// Copies as file inside the provided account. - /// - /// The file account ID which is used to send out the file. - /// The source file path. - /// The target file path. - procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text); - - - /// - /// Move as file inside the provided account. - /// - /// The file account ID which is used to send out the file. - /// The source file path. - /// The target file path. - procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text); - - /// - /// Checks if a file exists on the provided account. - /// - /// The file account ID which is used to send out the file. - /// The file path inside the file account. - /// Returns true if the file exists - procedure FileExists(AccountId: Guid; Path: Text): Boolean; - - /// - /// Deletes a file exists on the provided account. - /// - /// The file account ID which is used to send out the file. - /// The file path inside the file account. - procedure DeleteFile(AccountId: Guid; Path: Text); - - /// - /// Gets a List of Directories stored on the provided account. - /// - /// The file account ID which is used to get the file. - /// The file path to list. - /// Defines the pagination data. - /// A list with all directories stored in the path. - procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary); - - /// - /// Creates a directory on the provided account. - /// - /// The directory path inside the file account. - /// The file account ID which is used to send out the file. - procedure CreateDirectory(AccountId: Guid; Path: Text); - - /// - /// Checks if a directory exists on the provided account. - /// - /// The file account ID which is used to send out the file. - /// The directory path inside the file account. - /// Returns true if the directory exists - procedure DirectoryExists(AccountId: Guid; Path: Text): Boolean; - - /// - /// Deletes a directory exists on the provided account. - /// - /// The file account ID which is used to send out the file. - /// The directory path inside the file account. - procedure DeleteDirectory(AccountId: Guid; Path: Text); - - /// - /// Returns the path separator of the file account. - /// - /// The Path separator like / or \ - procedure PathSeparator(): Text; - - /// - /// Gets the file accounts registered for the connector. - /// - /// Out variable that holds the registered file accounts for the connector. - procedure GetAccounts(var Accounts: Record "File Account"); - - /// - /// Shows the information for an file account. - /// - /// The ID of the file account - procedure ShowAccountInformation(AccountId: Guid); - - /// - /// Registers an file account for the connector. - /// - /// The out parameter must hold the account ID of the added account. - /// Out parameter with the details of the registered Account. - /// True if an account was registered. - procedure RegisterAccount(var FileAccount: Record "File Account"): Boolean - - /// - /// Deletes an file account for the connector. - /// - /// The ID of the file account - /// True if an account was deleted. - procedure DeleteAccount(AccountId: Guid): Boolean - - /// - /// Provides a custom logo for the connector that shows in the Setup File Account Guide. - /// - /// Base64 encoded image. - /// The recomended image size is 128x128. - /// The logo of the connector is Base64 format - procedure GetLogoAsBase64(): Text; - - /// - /// Provides a more detailed description of the connector. - /// - /// A more detailed description of the connector. - procedure GetDescription(): Text[250]; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Connector/FileSystemConnector.Table.al b/Modules/System/File System/src/Connector/FileSystemConnector.Table.al deleted file mode 100644 index 6795e622f2..0000000000 --- a/Modules/System/File System/src/Connector/FileSystemConnector.Table.al +++ /dev/null @@ -1,39 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -table 70001 "File System Connector" -{ - TableType = Temporary; - Access = Internal; - InherentPermissions = X; - InherentEntitlements = X; - - fields - { - field(1; Connector; Enum "File System Connector") - { - DataClassification = SystemMetadata; - } - field(2; Logo; Blob) - { - DataClassification = SystemMetadata; - Subtype = Bitmap; - } - field(3; Description; Text[250]) - { - DataClassification = SystemMetadata; - } - } - - keys - { - key(PK; Connector) - { - Clustered = true; - } - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al b/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al deleted file mode 100644 index 6d3459eea1..0000000000 --- a/Modules/System/File System/src/Connector/FileSystemConnectorLogo.Table.al +++ /dev/null @@ -1,35 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -table 70002 "File System Connector Logo" -{ - DataClassification = SystemMetadata; - Access = Internal; - InherentPermissions = X; - InherentEntitlements = X; - - fields - { - field(1; Connector; Enum "File System Connector") - { - DataClassification = SystemMetadata; - } - field(2; Logo; Media) - { - DataClassification = CustomerContent; - } - } - - keys - { - key(PK; Connector) - { - Clustered = true; - } - } - -} \ No newline at end of file diff --git a/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al deleted file mode 100644 index 41a19f67d8..0000000000 --- a/Modules/System/File System/src/FileSystem/FileSystem.Codeunit.al +++ /dev/null @@ -1,258 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -codeunit 70004 "File System" -{ - var - FileSystemImpl: Codeunit "File System Impl."; - - /// - /// Initialized the File System for the give scenario. - /// - /// File Scenario to use. - procedure Initialize(Scenario: Enum "File Scenario") - begin - FileSystemImpl.Initialize(Scenario); - end; - - /// - /// Initialized the File System for the give file account. - /// - /// File Account to use. - procedure Initialize(FileAccount: Record "File Account") - begin - FileSystemImpl.Initialize(FileAccount); - end; - - /// - /// List all files from the given path. - /// - /// Folder to list - /// Defines the pagination data. - /// File account content. - procedure ListFiles(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) - begin - FileSystemImpl.ListFiles(Path, FilePaginationData, FileAccountContent); - end; - - /// - /// Retireves a file from the file account. - /// - /// File Path to open. - /// Stream which contains the file content. - [TryFunction] - procedure GetFile(Path: Text; Stream: InStream) - begin - FileSystemImpl.GetFile(Path, Stream); - end; - - /// - /// Stores a file in to the file account. - /// - /// File Path inside the file account. - /// Stream to store. - [TryFunction] - procedure SetFile(Path: Text; Stream: InStream) - begin - FileSystemImpl.SetFile(Path, Stream); - end; - - /// - /// Copies a file in the file account. - /// - /// Source path of the file. - /// Target Path of the file copy. - [TryFunction] - procedure CopyFile(SourcePath: Text; TargetPath: Text) - begin - FileSystemImpl.CopyFile(SourcePath, TargetPath); - end; - - /// - /// Moves a file in the file account. - /// - /// Source path of the file. - /// Target Path of the file. - [TryFunction] - procedure MoveFile(SourcePath: Text; TargetPath: Text) - begin - FileSystemImpl.MoveFile(SourcePath, TargetPath); - end; - - /// - /// Checks if a specific file exists in the file account. - /// - /// File path to check. - /// Returns true if the file exists. - procedure FileExists(Path: Text): Boolean - begin - exit(FileSystemImpl.FileExists(Path)); - end; - - /// - /// Deletes a file from the file account. - /// - /// File path of the file to delete. - [TryFunction] - procedure DeleteFile(Path: Text) - begin - FileSystemImpl.DeleteFile(Path); - end; - - /// - /// List all directories from the given path. - /// - /// Folder to list - /// Defines the pagination data. - /// File account content. - [TryFunction] - procedure ListDirectories(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) - begin - FileSystemImpl.ListDirectories(Path, FilePaginationData, FileAccountContent); - end; - - /// - /// Creates a directory in the file account. - /// - /// Path of the new Directory to create. - [TryFunction] - procedure CreateDirectory(Path: Text) - begin - FileSystemImpl.CreateDirectory(Path); - end; - - /// - /// Checks if a specific directory exists in the file account. - /// - /// Path of the directry to check. - /// Returns true if directory exists. - procedure DirectoryExists(Path: Text): Boolean - begin - exit(FileSystemImpl.DirectoryExists(Path)); - end; - - /// - /// Deletes a directory from the file account. - /// - /// Directory to remove. - [TryFunction] - procedure DeleteDirectory(Path: Text) - begin - FileSystemImpl.DeleteDirectory(Path); - end; - - /// - /// Returns the path separator used by the file account system. - /// - /// - procedure PathSeparator(): Text - begin - exit(FileSystemImpl.PathSeparator()); - end; - - /// - /// Combines to paths together. - /// - /// First part to combine. - /// Second part to combine. - /// Correctly combined path. - procedure CombinePath(Path: Text; ChildPath: Text): Text - begin - Exit(FileSystemImpl.CombinePath(Path, ChildPath)); - end; - - /// - /// Gets the Parent Path of the given path. - /// - /// File or directoy path. - /// The parent of the speicfied path. - procedure GetParentPath(Path: Text): Text - begin - exit(FileSystemImpl.GetParentPath(Path)); - end; - - /// - /// Opens a folder selection dialog. - /// - /// Start path of the dialog. - /// Returns the selected Folder. - procedure SelectFolderUI(Path: Text): Text - var - DefaulSelectFolderUILbl: Label 'Select a folder'; - begin - exit(SelectFolderUI(Path, DefaulSelectFolderUILbl)); - end; - - /// - /// Opens a folder selection dialog. - /// - /// Start path of the dialog. - /// Title of the selection dialog. - /// Returns the selected Folder. - procedure SelectFolderUI(Path: Text; DialogTitle: Text): Text - begin - exit(FileSystemImpl.SelectFolderUI(Path, DialogTitle)); - end; - - /// - /// Opens a select file dialog. - /// - /// Start path. - /// A filter string that applies only on files not on folders. - /// Returns the path of the selected file. - procedure SelectFileUI(Path: Text; FileFilter: Text): Text - var - DefaulSelectFileUILbl: Label 'Select a file'; - begin - exit(SelectFileUI(Path, FileFilter, DefaulSelectFileUILbl)); - end; - - /// - /// Opens a select file dialog. - /// - /// Start path of the dialog. - /// A filter string that applies only on files not on folders. - /// Title of the selection dialog. - /// Returns the path of the selected file. - procedure SelectFileUI(Path: Text; FileFilter: Text; DialogTitle: Text): Text - begin - exit(FileSystemImpl.SelectFileUI(Path, FileFilter, DialogTitle)); - end; - - /// - /// Opens a save to dialog. - /// - /// Start path of the dialog. - /// The fileextion without dot (like pdf or txt). - /// Returns the selecte file path. - procedure SaveFileUI(Path: Text; FileExtension: Text): Text - var - DefaultSaveFileUITitleLbl: Label 'Save as'; - begin - exit(SaveFileUI(Path, FileExtension, DefaultSaveFileUITitleLbl)); - end; - - /// - /// Opens a save to dialog. - /// - /// Start path of the dialog. - /// The fileextion without dot (like pdf or txt). - /// Title of the selection dialog. - /// Returns the selecte file path. - procedure SaveFileUI(Path: Text; FileExtension: Text; DialogTitle: Text): Text - begin - exit(FileSystemImpl.SaveFileUI(Path, FileExtension, DialogTitle)); - end; - - /// - /// Opens a File Browser - /// - procedure BrowseAccount() - begin - FileSystemImpl.BrowseAccount(); - end; -} diff --git a/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al b/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al deleted file mode 100644 index c569119e64..0000000000 --- a/Modules/System/File System/src/FileSystem/FileSystemImp.Codeunit.al +++ /dev/null @@ -1,208 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -codeunit 70005 "File System Impl." -{ - var - IFileConnector: Interface "File System Connector"; - CurrFileAccount: Record "File Account"; - Initialized: Boolean; - - internal procedure Initialize(Scenario: Enum "File Scenario") - var - FileAccount: Record "File Account"; - FileScenario: Codeunit "File Scenario"; - NoFileAccountFoundErr: Label 'No defaut file account defined.'; - begin - if not FileScenario.GetFileAccount(Scenario, FileAccount) then - Error(NoFileAccountFoundErr); - - Initialize(FileAccount); - end; - - internal procedure Initialize(FileAccount: Record "File Account") - begin - CurrFileAccount := FileAccount; - IFileConnector := FileAccount.Connector; - Initialized := true; - end; - - internal procedure ListFiles(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) - begin - CheckInitialization(); - IFileConnector.ListFiles(CurrFileAccount."Account Id", Path, FilePaginationData, FileAccountContent); - end; - - internal procedure GetFile(Path: Text; Stream: InStream) - begin - CheckInitialization(); - IFileConnector.GetFile(CurrFileAccount."Account Id", Path, Stream); - end; - - internal procedure SetFile(Path: Text; Stream: InStream) - begin - CheckInitialization(); - IFileConnector.SetFile(CurrFileAccount."Account Id", Path, Stream); - end; - - - internal procedure CopyFile(SourcePath: Text; TargetPath: Text) - begin - CheckInitialization(); - IFileConnector.CopyFile(CurrFileAccount."Account Id", SourcePath, TargetPath); - end; - - internal procedure MoveFile(SourcePath: Text; TargetPath: Text) - begin - CheckInitialization(); - IFileConnector.MoveFile(CurrFileAccount."Account Id", SourcePath, TargetPath); - end; - - internal procedure FileExists(Path: Text): Boolean - begin - CheckInitialization(); - exit(IFileConnector.FileExists(CurrFileAccount."Account Id", Path)); - end; - - internal procedure DeleteFile(Path: Text) - begin - CheckInitialization(); - IFileConnector.DeleteFile(CurrFileAccount."Account Id", Path); - end; - - internal procedure ListDirectories(Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) - begin - CheckInitialization(); - IFileConnector.ListDirectories(CurrFileAccount."Account Id", Path, FilePaginationData, FileAccountContent); - end; - - internal procedure CreateDirectory(Path: Text) - begin - CheckInitialization(); - IFileConnector.CreateDirectory(CurrFileAccount."Account Id", Path); - end; - - internal procedure DirectoryExists(Path: Text): Boolean - begin - CheckInitialization(); - exit(IFileConnector.DirectoryExists(CurrFileAccount."Account Id", Path)); - end; - - internal procedure DeleteDirectory(Path: Text) - begin - CheckInitialization(); - IFileConnector.DeleteDirectory(CurrFileAccount."Account Id", Path); - end; - - internal procedure PathSeparator(): Text - begin - CheckInitialization(); - exit(IFileConnector.PathSeparator()); - end; - - internal procedure CombinePath(Path: Text; ChildPath: Text): Text - begin - if Path = '' then - exit(ChildPath); - - if not Path.EndsWith(PathSeparator()) then - Path += PathSeparator(); - - exit(Path + ChildPath); - end; - - internal procedure GetParentPath(Path: Text) ParentPath: Text - begin - if (Path.TrimEnd(PathSeparator()).Contains(PathSeparator())) then - ParentPath := Path.TrimEnd(PathSeparator()).Substring(1, Path.LastIndexOf(PathSeparator())); - end; - - internal procedure SelectFolderUI(Path: Text; DialogTitle: Text): Text - var - FileAccountContent: Record "File Account Content"; - FileAccountBrowser: Page "File Account Browser"; - begin - CheckInitialization(); - - FileAccountBrowser.SetPageCaption(DialogTitle); - FileAccountBrowser.SetFileAcconut(CurrFileAccount); - FileAccountBrowser.EnableDirectoryLookupMode(Path); - if FileAccountBrowser.RunModal() <> Action::LookupOK then - exit(''); - - FileAccountBrowser.GetRecord(FileAccountContent); - if FileAccountContent.Type <> FileAccountContent.Type::Directory then - exit(''); - - exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); - end; - - internal procedure SelectFileUI(Path: Text; FileFilter: Text; DialogTitle: Text): Text - var - FileAccountContent: Record "File Account Content"; - FileAccountBrowser: Page "File Account Browser"; - begin - CheckInitialization(); - - FileAccountBrowser.SetPageCaption(DialogTitle); - FileAccountBrowser.SetFileAcconut(CurrFileAccount); - FileAccountBrowser.EnableFileLookupMode(Path, FileFilter); - if FileAccountBrowser.RunModal() <> Action::LookupOK then - exit(''); - - FileAccountBrowser.GetRecord(FileAccountContent); - if FileAccountContent.Type <> FileAccountContent.Type::File then - exit(''); - - exit(CombinePath(FileAccountContent."Parent Directory", FileAccountContent.Name)); - end; - - internal procedure SaveFileUI(Path: Text; FileExtension: Text; DialogTitle: Text): Text - var - FileAccountContent: Record "File Account Content"; - FileAccountBrowser: Page "File Account Browser"; - FileName, FileNameWithExtenion : Text; - PleaseProvideFileExtensionErr: Label 'Please provide a valid file extension.'; - FileNameTok: Label '%1.%2', Locked = true; - begin - CheckInitialization(); - - if FileExtension = '' then - Error(PleaseProvideFileExtensionErr); - - FileAccountBrowser.SetPageCaption(DialogTitle); - FileAccountBrowser.SetFileAcconut(CurrFileAccount); - FileAccountBrowser.EnableSaveFileLookupMode(Path, FileExtension); - if FileAccountBrowser.RunModal() <> Action::LookupOK then - exit(''); - - FileName := FileAccountBrowser.GetFileName(); - if FileName = '' then - exit(''); - - FileNameWithExtenion := StrSubstNo(FileNameTok, FileName, FileExtension); - exit(CombinePath(FileAccountBrowser.GetCurrentDirectory(), FileNameWithExtenion)); - end; - - internal procedure BrowseAccount() - var - FileAccountImpl: Codeunit "File Account Impl."; - begin - CheckInitialization(); - FileAccountImpl.BrowseAccount(CurrFileAccount); - end; - - local procedure CheckInitialization() - var - NotInitializedErr: Label 'Please call Initalize() first.'; - begin - if Initialized then - exit; - - Error(NotInitializedErr); - end; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al b/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al deleted file mode 100644 index fec745954b..0000000000 --- a/Modules/System/File System/src/Lookup/FileAccountBrowser.Page.al +++ /dev/null @@ -1,315 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -page 70005 "File Account Browser" -{ - Caption = 'File Account Browser'; - PageType = List; - SourceTable = "File Account Content"; - ModifyAllowed = false; - InsertAllowed = false; - DeleteAllowed = false; - Extensible = false; - - layout - { - area(content) - { - repeater(General) - { - field(Name; Rec.Name) - { - DrillDown = true; - ApplicationArea = All; - ToolTip = 'Specifies the value of the Name field.'; - - trigger OnDrillDown() - begin - if Rec.Type = Rec.Type::Directory then - BrowseFolder(Rec) - else - if not IsInLookupMode then - DownloadFile(Rec); - end; - } - field("Type"; Rec."Type") - { - ApplicationArea = All; - ToolTip = 'Specifies the value of the Type field.'; - } - } - - group(SaveFileNameGroup) - { - Caption = '', Locked = true; - ShowCaption = false; - Visible = ShowFileName; - - field(SaveFileNameField; SaveFileName) - { - ApplicationArea = All; - Caption = 'Filename'; - } - } - } - } - - actions - { - area(Promoted) - { - actionref(UpRef; Up) { } - actionref(UploadRef; Upload) { } - actionref(CreateDirectoryRef; "Create Directory") { } - actionref(DeleteRef; Delete) { } - } - area(Processing) - { - action(Up) - { - Caption = 'Up'; - ApplicationArea = All; - Image = MoveUp; - Enabled = ParentFolderExists; - - trigger OnAction() - var - Path: Text; - begin - if CurrPath = '' then - exit; - - Path := FileSystem.GetParentPath(CurrPath); - BrowseFolder(Path); - end; - } - action(Upload) - { - Caption = 'Upload'; - ApplicationArea = All; - Image = Import; - Ellipsis = true; - Visible = not IsInLookupMode; - Enabled = not IsInLookupMode; - - trigger OnAction() - begin - UploadFile(); - BrowseFolder(CurrPath); - end; - } - action("Create Directory") - { - Caption = 'Create Directory'; - ApplicationArea = All; - Image = Bin; - Ellipsis = true; - Visible = not IsInLookupMode; - Enabled = not IsInLookupMode; - - trigger OnAction() - begin - CreateDirectory(); - BrowseFolder(CurrPath); - end; - } - action(Delete) - { - Caption = 'Delete'; - ApplicationArea = All; - Image = Delete; - Ellipsis = true; - Visible = not IsInLookupMode; - Enabled = not IsInLookupMode; - - trigger OnAction() - begin - DeleteFileOrDirectory(); - BrowseFolder(CurrPath); - end; - } - } - } - - var - FileSystem: Codeunit "File System"; - CurrPath, CurrFileFilter, SaveFileName, CurrPageCaption : Text; - ParentFolderExists, DoNotLoadFields, IsInLookupMode, ShowFileName : Boolean; - - trigger OnOpenPage() - begin - if CurrPageCaption <> '' then - CurrPage.Caption(CurrPageCaption); - end; - - internal procedure SetFileAcconut(FileAccount: Record "File Account") - begin - FileSystem.Initialize(FileAccount); - end; - - internal procedure BrowseFileAccount(Path: Text) - begin - BrowseFolder(''); - end; - - internal procedure EnableFileLookupMode(Path: Text; FileFilter: Text) - begin - CurrFileFilter := FileFilter; - EnableLookupMode(); - BrowseFolder(Path); - end; - - internal procedure EnableDirectoryLookupMode(Path: Text) - begin - DoNotLoadFields := true; - EnableLookupMode(); - BrowseFolder(Path); - end; - - internal procedure EnableSaveFileLookupMode(Path: Text; FileExtension: Text) - var - FileFilterTok: Label '*.%1', Locked = true; - begin - ShowFileName := true; - CurrFileFilter := StrSubstNo(FileFilterTok, FileExtension); - EnableLookupMode(); - BrowseFolder(Path); - end; - - internal procedure GetCurrentDirectory(): Text - begin - exit(CurrPath); - end; - - internal procedure GetFileName(): Text - begin - exit(SaveFileName); - end; - - - internal procedure SetPageCaption(NewCaption: Text) - begin - CurrPageCaption := NewCaption; - end; - - local procedure StripNotsupportChrInFileName(InText: Text): Text - var - InvalidChrStringTxt: Label '"#%&*:<>?\/{|}~', Locked = true; - begin - InText := DelChr(InText, '=', InvalidChrStringTxt); - exit(InText); - end; - - local procedure EnableLookupMode() - begin - IsInLookupMode := true; - CurrPage.LookupMode(true); - end; - - local procedure BrowseFolder(var TempFileAccountContent: Record "File Account Content" temporary) - var - Path: Text; - begin - Path := FileSystem.CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name); - BrowseFolder(Path); - end; - - local procedure BrowseFolder(Path: Text) - var - FilePaginationData: Codeunit "File Pagination Data"; - begin - CurrPath := Path; - ParentFolderExists := Path <> ''; - Rec.DeleteAll(); - - repeat - FileSystem.ListDirectories(Path, FilePaginationData, Rec); - until FilePaginationData.IsEndOfListing(); - - ListFiles(Path); - if Rec.FindFirst() then; - end; - - local procedure DownloadFile(var TempFileAccountContent: Record "File Account Content" temporary) - var - Stream: InStream; - begin - FileSystem.GetFile(FileSystem.CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name), Stream); - DownloadFromStream(Stream, '', '', '', TempFileAccountContent.Name); - end; - - local procedure UploadFile() - var - UploadDialogTxt: Label 'Upload File'; - FromFile: Text; - Stream: InStream; - begin - if not UploadIntoStream(UploadDialogTxt, '', '', FromFile, Stream) then - exit; - - FileSystem.SetFile(FileSystem.CombinePath(CurrPath, FromFile), Stream); - end; - - local procedure CreateDirectory() - var - FolderNameInput: Page "Folder Name Input"; - FolderName: Text; - begin - if FolderNameInput.RunModal() <> Action::OK then - exit; - - FolderName := StripNotsupportChrInFileName(FolderNameInput.GetFolderName()); - FileSystem.CreateDirectory(FileSystem.CombinePath(CurrPath, FolderName)); - end; - - local procedure ListFiles(var Path: Text) - var - FileAccountContent: Record "File Account Content" temporary; - FilePaginationData: Codeunit "File Pagination Data"; - begin - if DoNotLoadFields then - exit; - - repeat - FileSystem.ListFiles(Path, FilePaginationData, FileAccountContent); - until FilePaginationData.IsEndOfListing(); - - AddFiles(FileAccountContent); - end; - - local procedure AddFiles(var FileAccountContent: Record "File Account Content" temporary) - begin - if CurrFileFilter <> '' then - FileAccountContent.SetFilter(Name, CurrFileFilter); - - if not FileAccountContent.FindSet() then - exit; - - repeat - Rec.Init(); - Rec.TransferFields(FileAccountContent); - Rec.Insert(); - until FileAccountContent.Next() = 0; - end; - - local procedure DeleteFileOrDirectory() - var - PathToDelete: Text; - DeleteQst: Label 'Delete %1?', Comment = '%1 - Path to Delete'; - begin - PathToDelete := FileSystem.CombinePath(Rec."Parent Directory", Rec.Name); - if not Confirm(DeleteQst, false, PathToDelete) then - exit; - - case Rec.Type of - Rec.Type::Directory: - FileSystem.DeleteDirectory(PathToDelete); - Rec.Type::File: - FileSystem.DeleteFile(PathToDelete); - end; - end; -} diff --git a/Modules/System/File System/src/Lookup/FileAccountContent.Table.al b/Modules/System/File System/src/Lookup/FileAccountContent.Table.al deleted file mode 100644 index f4ca1d3ead..0000000000 --- a/Modules/System/File System/src/Lookup/FileAccountContent.Table.al +++ /dev/null @@ -1,39 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -table 70005 "File Account Content" -{ - Caption = 'File Account Content'; - DataClassification = SystemMetadata; - TableType = Temporary; - - fields - { - field(1; "Type"; Enum "File Type") - { - Caption = 'Type'; - DataClassification = ToBeClassified; - } - field(2; Name; Text[2048]) - { - Caption = 'Name'; - DataClassification = ToBeClassified; - } - field(10; "Parent Directory"; Text[2048]) - { - Caption = 'Parent Directory'; - DataClassification = ToBeClassified; - } - } - keys - { - key(PK; "Type", Name) - { - Clustered = true; - } - } -} diff --git a/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al b/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al deleted file mode 100644 index 6a5dc90cfc..0000000000 --- a/Modules/System/File System/src/Lookup/FilePaginationData.Codeunit.al +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -codeunit 70006 "File Pagination Data" -{ - var - Marker: Text; - EndOfListing: Boolean; - - procedure SetMarker(NewMarker: Text) - begin - Marker := NewMarker; - end; - - procedure GetMarker(): Text - begin - exit(Marker); - end; - - procedure SetEndOfListing(NewEndOfListing: Boolean) - begin - EndOfListing := NewEndOfListing; - end; - - procedure IsEndOfListing(): Boolean - begin - exit(EndOfListing); - end; -} diff --git a/Modules/System/File System/src/Lookup/FileType.Enum.al b/Modules/System/File System/src/Lookup/FileType.Enum.al deleted file mode 100644 index 54f6ea811f..0000000000 --- a/Modules/System/File System/src/Lookup/FileType.Enum.al +++ /dev/null @@ -1,31 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Indicator of what type the resource is. -/// -enum 70002 "File Type" -{ - Access = Public; - Extensible = false; - - /// - /// Indicates if entry is a directory. - /// - value(0; Directory) - { - Caption = 'Directory', Locked = true; - } - - /// - /// Indicates if entry is a file type. - /// - value(1; File) - { - Caption = 'File', Locked = true; - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Lookup/FolderNameInput.Page.al b/Modules/System/File System/src/Lookup/FolderNameInput.Page.al deleted file mode 100644 index 23239dcce3..0000000000 --- a/Modules/System/File System/src/Lookup/FolderNameInput.Page.al +++ /dev/null @@ -1,33 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -page 70006 "Folder Name Input" -{ - ApplicationArea = All; - Caption = 'Create Folder...'; - PageType = StandardDialog; - Extensible = false; - - layout - { - area(content) - { - field(FolderNameField; FolderName) - { - Caption = 'Folder Name'; - } - } - } - - var - FolderName: Text; - - internal procedure GetFolderName(): Text - begin - exit(FolderName); - end; -} diff --git a/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al b/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al deleted file mode 100644 index 649a7b951e..0000000000 --- a/Modules/System/File System/src/Scenario/FileAccountScenario.Table.al +++ /dev/null @@ -1,74 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Temporary table used to display the tree sctructure in "File Scenario Setup". -/// -table 70003 "File Account Scenario" -{ - Access = Internal; - InherentPermissions = X; - InherentEntitlements = X; - TableType = Temporary; - - fields - { - field(1; Scenario; Integer) - { - DataClassification = SystemMetadata; - } - - field(2; Connector; Enum "File System Connector") - { - DataClassification = SystemMetadata; - } - - field(3; "Account Id"; Guid) - { - DataClassification = SystemMetadata; - } - - field(4; "Display Name"; Text[2048]) - { - DataClassification = SystemMetadata; - } - - field(5; Default; Boolean) - { - DataClassification = SystemMetadata; - } - - field(6; EntryType; Option) - { - DataClassification = SystemMetadata; - OptionMembers = Account,Scenario; - } - - field(7; Position; Integer) - { - DataClassification = SystemMetadata; - } - } - - keys - { - key(PK; Scenario, "Account Id", Connector) - { - Clustered = true; - } - - key(Position; Position) - { - - } - - key(Name; "Display Name") - { - Description = 'Used for sorting by Dispay Name'; - } - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al deleted file mode 100644 index 46a191fb45..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenario.Codeunit.al +++ /dev/null @@ -1,76 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Provides functionality to work with file scenarios. -/// -codeunit 70002 "File Scenario" -{ - /// - /// Gets the default file account. - /// - /// Out parameter holding information about the default file account. - /// True if an account for the the default scenario was found; otherwise - false. - procedure GetDefaultFileAccount(var FileAccount: Record "File Account"): Boolean - begin - exit(FileScenarioImpl.GetFileAccount(Enum::"File Scenario"::Default, FileAccount)); - end; - - /// - /// Gets the file account used by the given file scenario. - /// If the no account is defined for the provided scenario, the default account (if defined) will be returned. - /// - /// The scenario to look for. - /// Out parameter holding information about the file account. - /// True if an account for the specified scenario was found; otherwise - false. - procedure GetFileAccount(Scenario: Enum "File Scenario"; var FileAccount: Record "File Account"): Boolean - begin - exit(FileScenarioImpl.GetFileAccount(Scenario, FileAccount)); - end; - - /// - /// Sets a default file account. - /// - /// The file account to use. - procedure SetDefaultFileAccount(FileAccount: Record "File Account") - begin - FileScenarioImpl.SetFileAccount(Enum::"File Scenario"::Default, FileAccount); - end; - - /// - /// Sets an file account to be used by the given file scenario. - /// - /// The scenario for which to set an file account. - /// The file account to use. - procedure SetFileAccount(Scenario: Enum "File Scenario"; FileAccount: Record "File Account") - begin - FileScenarioImpl.SetFileAccount(Scenario, FileAccount); - end; - - /// - /// Unassign an file scenario. The scenario will then use the default file account. - /// - /// The scenario to unassign. - procedure UnassignScenario(Scenario: Enum "File Scenario") - begin - FileScenarioImpl.UnassignScenario(Scenario); - end; - - /// - /// Event for changing whether an file scenario should be added to the list of assignable scenarios. - /// If the scenario has already been assigned or is the default scenario, this event won't be published. - /// - /// The scenario that is going to be added to the list of assignable scenarios. - /// The return for whether this scenario should be listed in the assignable scenarios list. - [IntegrationEvent(false, false, true)] - internal procedure OnBeforeInsertAvailableFileScenario(Scenario: Enum "File Scenario"; var IsAvailable: Boolean) - begin - end; - - var - FileScenarioImpl: Codeunit "File Scenario Impl."; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenario.Enum.al b/Modules/System/File System/src/Scenario/FileScenario.Enum.al deleted file mode 100644 index 7a31b1238c..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenario.Enum.al +++ /dev/null @@ -1,24 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// File scenarios. -/// Used to decouple file accounts from sending files. -/// -enum 70001 "File Scenario" -{ - Extensible = true; - - /// - /// The default file scenario. - /// Used in the cases where no other scenario is defined. - /// - value(0; Default) - { - Caption = 'Default'; - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenario.Table.al b/Modules/System/File System/src/Scenario/FileScenario.Table.al deleted file mode 100644 index 2258a9c28f..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenario.Table.al +++ /dev/null @@ -1,42 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Holds the mapping between file account and scenarios. -/// One scenarios is mapped to one file account. -/// One file account can be used for multiple scenarios. -/// -table 70004 "File Scenario" -{ - Access = Internal; - - fields - { - field(1; Scenario; Enum "File Scenario") - { - DataClassification = SystemMetadata; - } - - field(2; Connector; Enum "File System Connector") - { - DataClassification = SystemMetadata; - } - - field(3; "Account Id"; Guid) - { - DataClassification = SystemMetadata; - } - } - - keys - { - key(PK; Scenario) - { - Clustered = true; - } - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al b/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al deleted file mode 100644 index 6c0922fdfc..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenarioImpl.Codeunit.al +++ /dev/null @@ -1,329 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System; - -codeunit 70003 "File Scenario Impl." -{ - Access = Internal; - InherentPermissions = X; - InherentEntitlements = X; - Permissions = TableData "File Scenario" = rimd; - - procedure GetFileAccount(Scenario: Enum "File Scenario"; var FileAccount: Record "File Account"): Boolean - var - FileScenario: Record "File Scenario"; - AllFileAccounts: Record "File Account"; - FileAccounts: Codeunit "File Account"; - begin - FileAccounts.GetAllAccounts(AllFileAccounts); - - // Find the account for the provided scenario - if FileScenario.Get(Scenario) then - if AllFileAccounts.Get(FileScenario."Account Id", FileScenario.Connector) then begin - FileAccount := AllFileAccounts; - exit(true); - end; - - // Fallback to the default account if the scenario isn't mapped or the mapped account doesn't exist - if FileScenario.Get(Enum::"File Scenario"::Default) then - if AllFileAccounts.Get(FileScenario."Account Id", FileScenario.Connector) then begin - FileAccount := AllFileAccounts; - exit(true); - end; - - exit(false); - end; - - procedure SetFileAccount(Scenario: Enum "File Scenario"; FileAccount: Record "File Account") - var - FileScenario: Record "File Scenario"; - begin - if not FileScenario.Get(Scenario) then begin - FileScenario.Scenario := Scenario; - FileScenario.Insert(); - end; - - FileScenario."Account Id" := FileAccount."Account Id"; - FileScenario.Connector := FileAccount.Connector; - - FileScenario.Modify(); - end; - - procedure UnassignScenario(Scenario: Enum "File Scenario") - var - FileScenario: Record "File Scenario"; - begin - if FileScenario.Get(Scenario) then - FileScenario.Delete(); - end; - - /// - /// Get a list of entries, representing a tree structure with file accounts and the scenarios, assigned to each accout. - /// - /// - /// Account sales@cronus.com has scenarios "Sales Quote" and "Sales Credit Memo" assigned. - /// Account purchase@cronus.com has scenarios "Purchase Quote" and "Purchase Invoice" assigned. - /// The result of calling the function will be: - /// sales@cronus.com, "Sales Quote", "Sales Credit Memo", purchase@cronus.com, "Purchase Quote", "Purchase Invoice" - /// - /// A flatten tree structure representing the all the file accounts and the scenarios assigned to them. - procedure GetScenariosByFileAccount(var Result: Record "File Account Scenario") - var - FileAccounts: Record "File Account"; - FileAccountScenarios: Record "File Account Scenario"; - DefaultAccount: Record "File Account"; - FileAccount: Codeunit "File Account"; - DisplayName: Text[2048]; - Position: Integer; - Default: Boolean; - begin - Result.Reset(); - Result.DeleteAll(); - - FileAccount.GetAllAccounts(FileAccounts); - - if not FileAccounts.FindSet() then - exit; // No accounts, nothing to do - - // The position is set in order to be able to properly sort the entries (by order of insertion) - Position := 1; - GetDefaultAccount(DefaultAccount); - - repeat - Default := (FileAccounts."Account Id" = DefaultAccount."Account Id") and (FileAccounts.Connector = DefaultAccount.Connector); - DisplayName := FileAccounts.Name; - - // Add entry for the file account. Scenario is -1, because it isn't needed when displaying the file account. - AddEntry(Result, Result.EntryType::Account, -1, FileAccounts."Account Id", FileAccounts.Connector, DisplayName, Default, Position); - - // Get the file scenarios assigned to the current file account, sorted by "Display Name" - GetFileScenariosForAccount(FileAccounts, FileAccountScenarios); - - if FileAccountScenarios.FindSet() then - repeat - // Add entry for every scenario that is assigned to the current file account - AddEntry(Result, FileAccountScenarios.EntryType::Scenario, FileAccountScenarios.Scenario, FileAccountScenarios."Account Id", FileAccountScenarios.Connector, FileAccountScenarios."Display Name", false, Position); - until FileAccountScenarios.Next() = 0; - until FileAccounts.Next() = 0; - - // Order by position to show accurate results - Result.SetCurrentKey(Position); - end; - - local procedure GetFileScenariosForAccount(FileAccount: Record "File Account"; var FileAccountScenarios: Record "File Account Scenario") - var - FileScenarios: Record "File Scenario"; - ValidFileScenarios: DotNet Hashtable; - IsScenarioValid: Boolean; - Scenario: Integer; - begin - FileAccountScenarios.Reset(); - FileAccountScenarios.DeleteAll(); - - // Get all file scenarios assigned to the file account - FileScenarios.SetRange("Account Id", FileAccount."Account Id"); - FileScenarios.SetRange(Connector, FileAccount.Connector); - - if not FileScenarios.FindSet() then - exit; - - // Find all valid scenarios. Invalid scenario may occur if the extension that added them was removed. - ValidFileScenarios := ValidFileScenarios.Hashtable(); - foreach Scenario in Enum::"File Scenario".Ordinals() do - ValidFileScenarios.Add(Scenario, Scenario); - - // Convert File Scenario-s to File Account Scenario-s so they can be sorted by "Display Name" - repeat - IsScenarioValid := ValidFileScenarios.Contains(FileScenarios.Scenario.AsInteger()); - - // Add entry for every scenario that exists and uses the file account. Skip the default scenario. - if (FileScenarios.Scenario <> Enum::"File Scenario"::Default) and IsScenarioValid then begin - FileAccountScenarios.Scenario := FileScenarios.Scenario.AsInteger(); - FileAccountScenarios."Account Id" := FileScenarios."Account Id"; - FileAccountScenarios.Connector := FileScenarios.Connector; - FileAccountScenarios."Display Name" := Format(FileScenarios.Scenario); - - FileAccountScenarios.Insert(); - end; - until FileScenarios.Next() = 0; - - FileAccountScenarios.SetCurrentKey("Display Name"); // sort scenarios by "Display Name" - end; - - local procedure AddEntry(var Result: Record "File Account Scenario"; EntryType: Option; Scenario: Integer; AccountId: Guid; Connector: Enum "File System Connector"; DisplayName: Text[2048]; Default: Boolean; var Position: Integer) - begin - // Add entry to the result while maintaining the position so that the tree represents the data correctly - Result.EntryType := EntryType; - Result.Scenario := Scenario; - Result."Account Id" := AccountId; - Result.Connector := Connector; - Result."Display Name" := DisplayName; - Result.Default := Default; - Result.Position := Position; - - Result.Insert(); - - Position := Position + 1; - end; - - procedure AddScenarios(FileAccount: Record "File Account Scenario"): Boolean - var - FileScenario: Record "File Scenario"; - SelectedScenarios: Record "File Account Scenario"; - ScenariosForAccount: Page "File Scenarios For Account"; - begin - FileAccountImpl.CheckPermissions(); - - if FileAccount.EntryType <> FileAccount.EntryType::Account then // wrong entry, the entry should be of type "Account" - exit; - - ScenariosForAccount.Caption := StrSubstNo(ScenariosForAccountCaptionTxt, FileAccount."Display Name"); - ScenariosForAccount.LookupMode(true); - ScenariosForAccount.SetRecord(FileAccount); - - if ScenariosForAccount.RunModal() <> Action::LookupOK then - exit(false); - - ScenariosForAccount.GetSelectedScenarios(SelectedScenarios); - - if not SelectedScenarios.FindSet() then - exit(false); - - repeat - if not FileScenario.Get(SelectedScenarios.Scenario) then begin - FileScenario."Account Id" := FileAccount."Account Id"; - FileScenario.Connector := FileAccount.Connector; - FileScenario.Scenario := Enum::"File Scenario".FromInteger(SelectedScenarios.Scenario); - - FileScenario.Insert(); - end else begin - FileScenario."Account Id" := FileAccount."Account Id"; - FileScenario.Connector := FileAccount.Connector; - - FileScenario.Modify(); - end; - until SelectedScenarios.Next() = 0; - - exit(true); - end; - - procedure GetAvailableScenariosForAccount(FileAccount: Record "File Account Scenario"; var FileScenarios: Record "File Account Scenario") - var - Scenario: Record "File Scenario"; - FileScenario: Codeunit "File Scenario"; - CurrentScenario, i : Integer; - IsAvailable: Boolean; - begin - FileScenarios.Reset(); - FileScenarios.DeleteAll(); - i := 1; - - foreach CurrentScenario in Enum::"File Scenario".Ordinals() do begin - Clear(Scenario); - Scenario.SetRange("Account Id", FileAccount."Account Id"); - Scenario.SetRange(Connector, FileAccount.Connector); - Scenario.SetRange(Scenario, CurrentScenario); - - // If the scenario isn't already connected to the file account, then it's available. Natually, we skip the default scenario - IsAvailable := Scenario.IsEmpty() and (not (CurrentScenario = Enum::"File Scenario"::Default.AsInteger())); - - // If the scenario is available, allow partner to determine if it should be shown - if IsAvailable then - FileScenario.OnBeforeInsertAvailableFileScenario(Enum::"File Scenario".FromInteger(CurrentScenario), IsAvailable); - - if IsAvailable then begin - FileScenarios."Account Id" := FileAccount."Account Id"; - FileScenarios.Connector := FileAccount.Connector; - FileScenarios.Scenario := CurrentScenario; - FileScenarios."Display Name" := Format(Enum::"File Scenario".FromInteger(Enum::"File Scenario".Ordinals().Get(i))); - - FileScenarios.Insert(); - end; - - i := i + 1; - end; - end; - - procedure ChangeAccount(var FileScenario: Record "File Account Scenario"): Boolean - var - SelectedAccount: Record "File Account"; - Scenario: Record "File Scenario"; - FileAccount: Codeunit "File Account"; - AccountsPage: Page "File Accounts"; - begin - FileAccountImpl.CheckPermissions(); - - if not FileScenario.FindSet() then - exit(false); - - FileAccount.GetAllAccounts(false, SelectedAccount); - if SelectedAccount.Get(FileScenario."Account Id", FileScenario.Connector) then; - - AccountsPage.EnableLookupMode(); - AccountsPage.SetRecord(SelectedAccount); - AccountsPage.Caption := ChangeFileAccountForScenarioTxt; - - if AccountsPage.RunModal() <> Action::LookupOK then - exit(false); - - AccountsPage.GetAccount(SelectedAccount); - - if IsNullGuid(SelectedAccount."Account Id") then // defensive check, no account was selected - exit; - - repeat - if Scenario.Get(FileScenario.Scenario) then begin - Scenario."Account Id" := SelectedAccount."Account Id"; - Scenario.Connector := SelectedAccount.Connector; - - Scenario.Modify(); - end; - until FileScenario.Next() = 0; - - exit(true); - end; - - procedure DeleteScenario(var FileScenario: Record "File Account Scenario"): Boolean - var - Scenario: Record "File Scenario"; - begin - FileAccountImpl.CheckPermissions(); - - if not FileScenario.FindSet() then - exit(false); - - repeat - if FileScenario.EntryType = FileScenario.EntryType::Scenario then begin - Scenario.SetRange(Scenario, FileScenario.Scenario); - Scenario.SetRange("Account Id", FileScenario."Account Id"); - Scenario.SetRange(Connector, FileScenario.Connector); - - Scenario.DeleteAll(); - end; - until FileScenario.Next() = 0; - - exit(true); - end; - - local procedure GetDefaultAccount(var FileAccount: Record "File Account") - var - Scenario: Record "File Scenario"; - begin - if not Scenario.Get(Enum::"File Scenario"::Default) then - exit; - - FileAccount."Account Id" := Scenario."Account Id"; - FileAccount.Connector := Scenario.Connector; - end; - - var - FileAccountImpl: Codeunit "File Account Impl."; - AccountDisplayLbl: Label '%1 (%2)', Locked = true; - ChangeFileAccountForScenarioTxt: Label 'Change file account used for the selected scenarios'; - ScenariosForAccountCaptionTxt: Label 'Assign scenarios to account %1', Comment = '%1 = the name of the e-file account'; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al b/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al deleted file mode 100644 index 2d6c1c1396..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenarioSetup.Page.al +++ /dev/null @@ -1,198 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -using System.Telemetry; - -/// -/// Page is used to display file scenarios usage by file accounts. -/// -page 70002 "File Scenario Setup" -{ - Caption = 'File Scenario Assignment'; - PageType = List; - UsageCategory = Administration; - ApplicationArea = All; - Extensible = false; - Editable = false; - DeleteAllowed = false; - InsertAllowed = false; - ModifyAllowed = false; - SourceTable = "File Account Scenario"; - InstructionalText = 'Assign file scenarios'; - - layout - { - area(Content) - { - repeater(ScenariosByFile) - { - IndentationColumn = Indentation; - IndentationControls = Name; - ShowAsTree = true; - - field(Name; Rec."Display Name") - { - ApplicationArea = All; - Caption = 'Scenarios by file accounts'; - ToolTip = 'Specifies the scenarios that are using the file account.'; - Editable = false; - StyleExpr = Style; - } - - field(Default; DefaultTxt) - { - ApplicationArea = All; - Caption = 'Default'; - ToolTip = 'Specifies whether this is the default account to use for scenarios when no other account is specified.'; - StyleExpr = Style; - } - } - } - } - - actions - { - area(Processing) - { - group(Account) - { - action(AddScenario) - { - Visible = (TypeOfEntry = TypeOfEntry::Account) and CanUserManageFileSetup; - - ApplicationArea = All; - Caption = 'Assign scenarios'; - ToolTip = 'Assign file scenarios for the selected file account. When assigned, everyone will use the account for the scenario. For example, if you assign the Sales Order scenario, everyone will use the account to send sales orders.'; - Image = NewDocument; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - PromotedIsBig = true; - Scope = Repeater; - - trigger OnAction() - begin - SelectedFileAccountScenario := Rec; - FileScenarioImpl.AddScenarios(Rec); - - FileScenarioImpl.GetScenariosByFileAccount(Rec); - SetSelectedRecord(); - end; - } - } - - group(Scenario) - { - action(ChangeAccount) - { - Visible = (TypeOfEntry = TypeOfEntry::Scenario) and CanUserManageFileSetup; - - ApplicationArea = All; - Caption = 'Reassign'; - ToolTip = 'Reassign the selected scenarios to another file account.'; - Image = Change; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - PromotedIsBig = true; - Scope = Repeater; - - trigger OnAction() - begin - CurrPage.SetSelectionFilter(Rec); - SelectedFileAccountScenario := Rec; - - FileScenarioImpl.ChangeAccount(Rec); - FileScenarioImpl.GetScenariosByFileAccount(Rec); // refresh the data on the page - SetSelectedRecord(); - end; - } - - action(Unassign) - { - Visible = (TypeOfEntry = TypeOfEntry::Scenario) and CanUserManageFileSetup; - - ApplicationArea = All; - Caption = 'Unassign'; - ToolTip = 'Unassign the selected scenarios. Afterward, the default file account will be used to send files for the scenarios.'; - Image = Delete; - Promoted = true; - PromotedOnly = true; - PromotedCategory = Process; - PromotedIsBig = true; - Scope = Repeater; - - trigger OnAction() - begin - CurrPage.SetSelectionFilter(Rec); - SelectedFileAccountScenario := Rec; - - FileScenarioImpl.DeleteScenario(Rec); - FileScenarioImpl.GetScenariosByFileAccount(Rec); // refresh the data on the page - SetSelectedRecord(); - end; - } - } - } - } - - trigger OnOpenPage() - var - FeatureTelemetry: Codeunit "Feature Telemetry"; - begin - FeatureTelemetry.LogUptake('0000CTN', 'File Access', Enum::"Feature Uptake Status"::Discovered); - CanUserManageFileSetup := FileAccountImpl.IsUserFileAdmin(); - FileScenarioImpl.GetScenariosByFileAccount(Rec); - - // Set selection - if not Rec.Get(-1, FileAccountId, FileConnector) then - if Rec.FindFirst() then; - end; - - trigger OnAfterGetRecord() - begin - DefaultTxt := ''; - - TypeOfEntry := Rec.EntryType; - - if TypeOfEntry = TypeOfEntry::Account then begin - Indentation := 0; - Style := 'Strong'; - if Rec.Default then - DefaultTxt := '✓' - end; - - if TypeOfEntry = TypeOfEntry::Scenario then begin - Indentation := 1; - Style := 'Standard'; - end; - end; - - // Used to set the focus on an file account - internal procedure SetFileAccountId(AccountId: Guid; Connector: Enum "File System Connector") - begin - FileAccountId := AccountId; - FileConnector := Connector; - end; - - local procedure SetSelectedRecord() - begin - if not Rec.Get(SelectedFileAccountScenario.Scenario, SelectedFileAccountScenario."Account Id", SelectedFileAccountScenario.Connector) then - Rec.FindFirst(); - end; - - var - SelectedFileAccountScenario: Record "File Account Scenario"; - FileScenarioImpl: Codeunit "File Scenario Impl."; - FileAccountImpl: Codeunit "File Account Impl."; - FileAccountId: Guid; - FileConnector: Enum "File System Connector"; - Style, DefaultTxt : Text; - TypeOfEntry: Option Account,Scenario; - Indentation: Integer; - CanUserManageFileSetup: Boolean; -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al b/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al deleted file mode 100644 index df5bd47cc4..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenariosFactBox.Page.al +++ /dev/null @@ -1,40 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Lists of all scenarios assigned to an account. -/// -page 70003 "File Scenarios FactBox" -{ - PageType = ListPart; - Extensible = false; - SourceTable = "File Scenario"; - InsertAllowed = false; - ModifyAllowed = false; - DeleteAllowed = false; - Editable = false; - ShowFilter = false; - LinksAllowed = false; - Permissions = tabledata "File Scenario" = r; - - layout - { - area(Content) - { - repeater(ScenariosByFile) - { - field(Name; Format(Rec.Scenario)) - { - ApplicationArea = All; - ToolTip = 'The file scenario.'; - Caption = 'File scenario'; - Editable = false; - } - } - } - } -} \ No newline at end of file diff --git a/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al b/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al deleted file mode 100644 index ee44822462..0000000000 --- a/Modules/System/File System/src/Scenario/FileScenariosForAccount.Page.al +++ /dev/null @@ -1,65 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.FileSystem; - -/// -/// Displays the scenarios that could be linked to a provided file account. -/// -page 70004 "File Scenarios for Account" -{ - PageType = List; - Extensible = false; - SourceTable = "File Account Scenario"; - InsertAllowed = false; - ModifyAllowed = false; - DeleteAllowed = false; - Editable = false; - ShowFilter = false; - LinksAllowed = false; - - layout - { - area(Content) - { - repeater(ScenariosByFile) - { - field(Name; Rec."Display Name") - { - ApplicationArea = All; - ToolTip = 'The file scenario.'; - Caption = 'File scenario'; - Editable = false; - } - } - } - } - - internal procedure GetSelectedScenarios(var ResultFileAccountScenario: Record "File Account Scenario") - begin - ResultFileAccountScenario.Reset(); - ResultFileAccountScenario.DeleteAll(); - - CurrPage.SetSelectionFilter(Rec); - - if not Rec.FindSet() then - exit; - - repeat - ResultFileAccountScenario.Copy(Rec); - ResultFileAccountScenario.Insert(); - until Rec.Next() = 0; - end; - - trigger OnOpenPage() - begin - FileScenarioImpl.GetAvailableScenariosForAccount(Rec, Rec); - Rec.SetCurrentKey("Display Name"); - if Rec.FindFirst() then; // set the selection to the first record - end; - - var - FileScenarioImpl: Codeunit "File Scenario Impl."; -} \ No newline at end of file From d256d81391e6012ef1990568ddded31978d14f71 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 09:33:04 +0100 Subject: [PATCH 19/33] Remove app.json --- Modules/System/File System/app.json | 40 ----------------------------- 1 file changed, 40 deletions(-) delete mode 100644 Modules/System/File System/app.json diff --git a/Modules/System/File System/app.json b/Modules/System/File System/app.json deleted file mode 100644 index c326959d5d..0000000000 --- a/Modules/System/File System/app.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "c9c54414-80c3-4cc9-98c6-589158882774", - "name": "File System", - "publisher": "Microsoft", - "brief": "Enables user to send emails from Business Central.", - "description": "Enables user to send emails from Business Central.", - "version": "23.0.0.0", - "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", - "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", - "help": "https://go.microsoft.com/fwlink/?linkid=2103698", - "url": "https://go.microsoft.com/fwlink/?linkid=724011", - "logo": "", - "dependencies": [ - ], - "internalsVisibleTo": [ - { - "id": "eb303ff1-7a68-45b5-b7de-bf6965244dd7", - "name": "Email Test", - "publisher": "Microsoft" - }, - { - "id": "949b9041-d2cb-4e69-bf31-c1e8fcb9462b", - "name": "Email Test Library", - "publisher": "Microsoft" - } - ], - "screenshots": [ - - ], - "platform": "23.0.0.0", - "application": "23.0.0.0", - "idRanges": [ - { - "from": 70000, - "to": 70199 - } - ], - "target": "OnPrem", - "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" -} \ No newline at end of file From 5965d1b194e40f2e1d144667953e6e7112822072 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 09:34:54 +0100 Subject: [PATCH 20/33] Update app.json --- Apps/W1/File - Azure BLOB Storage Connector/app/app.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json index e65367c8c3..a1075d5141 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -4,13 +4,13 @@ "publisher": "Microsoft", "brief": "Enable all users to use a Azure Blob Storage to save files from Business Central.", "description": "This app enables all users to store files in the smae azure blob storage in Business Central.", - "version": "23.0.0.0", + "version": "25.0.0.0", "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", "help": "https://go.microsoft.com/fwlink/?linkid=2134520", "url": "https://go.microsoft.com/fwlink/?linkid=724011", "logo": "ExtensionLogo.png", - "application": "22.0.0.0", + "application": "25.0.0.0", "internalsVisibleTo": [ ], @@ -19,13 +19,13 @@ "id": "c9c54414-80c3-4cc9-98c6-589158882774", "name": "File System", "publisher": "Microsoft", - "version": "22.1.0.0" + "version": "25.0.0.0" } ], "screenshots": [ ], - "platform": "23.0.0.0", + "platform": "25.0.0.0", "idRanges": [ { "from": 80100, From 4bda1364f4f8a38782a3149597f7916b8ad0d623 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 18:20:10 +0100 Subject: [PATCH 21/33] Fix app.json --- .../File - Azure BLOB Storage Connector/app/app.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json index a1075d5141..9e2a06b2be 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -10,22 +10,16 @@ "help": "https://go.microsoft.com/fwlink/?linkid=2134520", "url": "https://go.microsoft.com/fwlink/?linkid=724011", "logo": "ExtensionLogo.png", - "application": "25.0.0.0", + "application": "24.0.0.0", "internalsVisibleTo": [ ], "dependencies": [ - { - "id": "c9c54414-80c3-4cc9-98c6-589158882774", - "name": "File System", - "publisher": "Microsoft", - "version": "25.0.0.0" - } ], "screenshots": [ ], - "platform": "25.0.0.0", + "platform": "24.0.0.0", "idRanges": [ { "from": 80100, From 7c10d314f53a0fe1fe9c336fe85500144c086e9d Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 19:34:22 +0100 Subject: [PATCH 22/33] Update to BC24 --- .../File - Azure BLOB Storage Connector/app/app.json | 11 +++-------- .../app/src/BlobStorageAccount.Page.al | 9 +++++++++ .../app/src/BlobStorageAccount.Table.al | 2 +- .../app/src/BlobStorageAccountWizard.Page.al | 1 + .../app/src/BlobStorageConnectorImpl.Codeunit.al | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json index 9e2a06b2be..ba6f5ec63e 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/app.json @@ -11,14 +11,9 @@ "url": "https://go.microsoft.com/fwlink/?linkid=724011", "logo": "ExtensionLogo.png", "application": "24.0.0.0", - "internalsVisibleTo": [ - - ], - "dependencies": [ - ], - "screenshots": [ - - ], + "internalsVisibleTo": [], + "dependencies": [], + "screenshots": [], "platform": "24.0.0.0", "idRanges": [ { diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al index 3f287ab726..90d08d89fd 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al @@ -57,6 +57,15 @@ page 80100 "Blob Storage Account" ApplicationArea = All; Caption = 'Container Name'; ToolTip = 'Specifies the Azure Storage Container name.'; + + trigger OnLookup(var Text: Text): Boolean + var + BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; + begin + CurrPage.Update(); + BlobStorageConnectorImpl.LookUpContainer(Rec, Rec.GetPassword(Rec."Password Key"), Text); + exit(true); + end; } } } diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al index 438acc8007..38856da789 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al @@ -70,7 +70,7 @@ table 80100 "Blob Storage Account" end; [NonDebuggable] - procedure GetPassword(PasswordKey: Guid) Password: Text + procedure GetPassword(PasswordKey: Guid) Password: SecretText begin if not IsolatedStorage.Get(Format(PasswordKey), DataScope::Company, Password) then Error(UnableToGetPasswordMsg); diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al index c24d3108cd..1e43a84aed 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al @@ -86,6 +86,7 @@ page 80101 "Blob Storage Account Wizard" var BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; begin + CurrPage.Update(); BlobStorageConnectorImpl.LookUpContainer(Rec, Password, Text); exit(true); end; diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 4fad377aa7..7af193c184 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -405,7 +405,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" FileAccount.Connector := Enum::"File System Connector"::"Blob Storage"; end; - internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; Password: Text; var NewContainerName: Text[2048]) + internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; Password: SecretText; var NewContainerName: Text[2048]) var ABSContainers: Record "ABS Container"; ABSContainerClient: Codeunit "ABS Container Client"; From a02efa808092b5b59aa08002bd82be69727b6651 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 19:37:56 +0100 Subject: [PATCH 23/33] Rename app --- .../app/ExtensionLogo.png | Bin .../app/README.md | 0 .../app/app.json | 2 +- .../app/permissions/BlobStorEdit.PermissionSet.al | 0 .../permissions/BlobStorObjects.PermissionSet.al | 0 .../app/permissions/BlobStorRead.PermissionSet.al | 0 .../FileSystemAdminBlobStor.PermissionSetExt.al | 0 .../app/src/BlobStorageAccount.Page.al | 0 .../app/src/BlobStorageAccount.Table.al | 0 .../app/src/BlobStorageAccountWizard.Page.al | 0 .../app/src/BlobStorageConnector.EnumExt.al | 0 .../app/src/BlobStorageConnectorImpl.Codeunit.al | 0 .../app/src/BlobStorageContainerLookup.Page.al | 0 13 files changed, 1 insertion(+), 1 deletion(-) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/ExtensionLogo.png (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/README.md (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/app.json (96%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/permissions/BlobStorEdit.PermissionSet.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/permissions/BlobStorObjects.PermissionSet.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/permissions/BlobStorRead.PermissionSet.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageAccount.Page.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageAccount.Table.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageAccountWizard.Page.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageConnector.EnumExt.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageConnectorImpl.Codeunit.al (100%) rename Apps/W1/{File - Azure BLOB Storage Connector => File - Azure Blob Service Connector}/app/src/BlobStorageContainerLookup.Page.al (100%) diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/ExtensionLogo.png b/Apps/W1/File - Azure Blob Service Connector/app/ExtensionLogo.png similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/ExtensionLogo.png rename to Apps/W1/File - Azure Blob Service Connector/app/ExtensionLogo.png diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/README.md b/Apps/W1/File - Azure Blob Service Connector/app/README.md similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/README.md rename to Apps/W1/File - Azure Blob Service Connector/app/README.md diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json b/Apps/W1/File - Azure Blob Service Connector/app/app.json similarity index 96% rename from Apps/W1/File - Azure BLOB Storage Connector/app/app.json rename to Apps/W1/File - Azure Blob Service Connector/app/app.json index ba6f5ec63e..b4cf3cb43d 100644 --- a/Apps/W1/File - Azure BLOB Storage Connector/app/app.json +++ b/Apps/W1/File - Azure Blob Service Connector/app/app.json @@ -1,6 +1,6 @@ { "id": "c9ce86fe-cb70-4b79-be03-d21856b1a4ca", - "name": "File - Azure BLOB Storage Connector", + "name": "File - Azure Blob Service Connector", "publisher": "Microsoft", "brief": "Enable all users to use a Azure Blob Storage to save files from Business Central.", "description": "This app enables all users to store files in the smae azure blob storage in Business Central.", diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al b/Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorEdit.PermissionSet.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorEdit.PermissionSet.al rename to Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorEdit.PermissionSet.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al b/Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorObjects.PermissionSet.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorObjects.PermissionSet.al rename to Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorObjects.PermissionSet.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al b/Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorRead.PermissionSet.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/permissions/BlobStorRead.PermissionSet.al rename to Apps/W1/File - Azure Blob Service Connector/app/permissions/BlobStorRead.PermissionSet.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al b/Apps/W1/File - Azure Blob Service Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al rename to Apps/W1/File - Azure Blob Service Connector/app/permissions/FileSystemAdminBlobStor.PermissionSetExt.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Page.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccount.Table.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageAccountWizard.Page.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnector.EnumExt.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageConnectorImpl.Codeunit.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al diff --git a/Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageContainerLookup.Page.al similarity index 100% rename from Apps/W1/File - Azure BLOB Storage Connector/app/src/BlobStorageContainerLookup.Page.al rename to Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageContainerLookup.Page.al From 1008fbd1b1c03ea8f2616ecde5680a708463719f Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Thu, 29 Feb 2024 21:15:20 +0100 Subject: [PATCH 24/33] Add file share connector --- .../app/app.json | 4 - .../app/src/BlobStorageAccount.Page.al | 5 + .../app/src/BlobStorageConnector.EnumExt.al | 2 +- .../app/ExtensionLogo.png | Bin 0 -> 4681 bytes .../app/README.md | 0 .../app/app.json | 31 ++ .../FileShareEdit.PermissionSet.al | 18 + .../FileShareObjects.PermissionSet.al | 19 + .../FileShareRead.PermissionSet.al | 18 + ...leSystemAdminFileShare.PermissionSetExt.al | 11 + .../app/src/FileShareAccount.Page.al | 81 ++++ .../app/src/FileShareAccount.Table.al | 78 +++ .../app/src/FileShareAccountWizard.Page.al | 158 +++++++ .../app/src/FileShareConnector.EnumExt.al | 21 + .../src/FileShareConnectorImpl.Codeunit.al | 445 ++++++++++++++++++ 15 files changed, 886 insertions(+), 5 deletions(-) create mode 100644 Apps/W1/File - Azure File Service Connector/app/ExtensionLogo.png create mode 100644 Apps/W1/File - Azure File Service Connector/app/README.md create mode 100644 Apps/W1/File - Azure File Service Connector/app/app.json create mode 100644 Apps/W1/File - Azure File Service Connector/app/permissions/FileShareEdit.PermissionSet.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/permissions/FileShareObjects.PermissionSet.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/permissions/FileShareRead.PermissionSet.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/permissions/FileSystemAdminFileShare.PermissionSetExt.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareConnector.EnumExt.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al diff --git a/Apps/W1/File - Azure Blob Service Connector/app/app.json b/Apps/W1/File - Azure Blob Service Connector/app/app.json index b4cf3cb43d..ab7b0f06db 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/app.json +++ b/Apps/W1/File - Azure Blob Service Connector/app/app.json @@ -19,10 +19,6 @@ { "from": 80100, "to": 80199 - }, - { - "from": 2147483647, - "to": 2147483647 } ], "target": "OnPrem", diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al index 90d08d89fd..d85be8ac64 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al @@ -82,4 +82,9 @@ page 80100 "Blob Storage Account" if not IsNullGuid(Rec."Password Key") then Password := '***'; end; + + trigger OnAfterGetCurrRecord() + begin + PasswordEditable := CurrPage.Editable(); + end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al index 8fcab6400e..ac5da525b1 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnector.EnumExt.al @@ -13,7 +13,7 @@ enumextension 80100 "Blob Storage Connector" extends "File System Connector" /// /// The Blob Storage connector. /// - value(2147483647; "Blob Storage") // Max int value so it appears last //FIXME Id from Module + value(80100; "Blob Storage") { Caption = 'Blob Storage'; Implementation = "File System Connector" = "Blob Storage Connector Impl."; diff --git a/Apps/W1/File - Azure File Service Connector/app/ExtensionLogo.png b/Apps/W1/File - Azure File Service Connector/app/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..30941b354fa335cad3ea5426ac24cadb2ee328e5 GIT binary patch literal 4681 zcmc&&XHyeg(#9JF|QCI_JtTHP)d9vw?|-h^X~+HO+3~{=Y^}dgJd| zZsgwtiU3`kU?L()hJOt(_A~a9h=`t`r>Slc{(PrmE|9&MA$7@|AnwlN2KOad*5-LZ zZg_ubG>D{{Px4B^#6TNkLBhxOAsX)Ol$^SOw zvZLIccJIo2ZC;ipTEG~N!1t+4MgO(1ocz=&{>BE|@yR*aROgK4^XH9?bgQ9eo|T$2 z6l!y3C1rpGY)!vF5S|kLVc?dAO{n^NaC4y7A$5xyq{4&2xU?mDNSr8?LDuG_>zCyZ zMmd-N3i^d_=BK?~+&*xxd{s;*BZe3@#i{;k|B?t7ib{8VhQ+kos)UnRWw0-ve7c@IZGQGChp2H5uiLtFoF>!=+5h+)Vqd2MalH&qcY*VhHwShqLNQkSQmz zx(Ux}b=r7Ru;1fj4p|MrOTgaM>+V_Y(V<`iV8%YaffoQ7ig`lDj1D3*#c=}U&uONF zIosINLm%p|KD1ud%PQw=_(nCI1iJ?SpJ^rs?0J#i5i@Spf$_d12So!H2%bp+8Yki3 zOZKd6gMdtjd8RlWd^@)buBzb>*eI4V2Xy}JnUuSSLY37i!MjayWbs(Phvq}rPMnL| z7~lhWMAlXx*%FN~UX?Cz_uER%#+@*={-i(v?>O2o9=HHlZi-kIAD>hv7MqSVvz)wJ z&bUJd#saK=;uqRYVwa`s#nGLmYC)36pI9Zq3RL0fnkg;kUYbW^fuu?8cPR@d5>VsX`?mBJ+fjqc}G7!Vve%fub#3v@{_W`-dHq{~Qo!o)BeL;? z96pVT_Vi3$=LctJzu6idoi2h3Tu9=0sdXV8kQ3hS$irlnFwGZg& zYtn8zVY&n5z!QI|--Wkqz%0K*@A5~O92Al5HZvVWsr-1)+X_V(3Fd3DrI?%F45s+4 zFdGV%nI4@7{H-?wJ!14$FIC_?Hy7XX-6>K;9%3s_5#1rjova{p+y2)PrONtXL00@| zGp~5hVnCyO9vu4g_a8GFJv7wbqpSTOAhi>0c0OGG4{?>5t}6?@^9w4c$KMvBjstDhHvv{k zO>{5P>{Dc|X5UeYg(W-+esE8*5QTc|VX_6!7<8*LvW2@=xS6Ftd*}}HQE6iQM@yP< zNk1*p(|qo$Z3yyr{KbGa!u|Khypj`Py#8>qdWl`xq4XNe1qtIYWp_nVD6%C#k~q6E zU;>rmoT;E_i7?~!V#(t}tHbXg)>I=j;z05@jNhAu(MnXDe!IfoGkU#!c)6=HB4W>K ze^)$Lz;f?rX(P_L_>$bgeN1)?yoe-uC|hK|uH4uY)~)KSHx2)mhMoHg5)057xya|r zbJ;?Niu!Z|4YmksTLwc~&M6fqY{a6#B8XILkE0jd5a@{`kw|dWA0@A`wyXA2bd zv}ejN*gxL8qI{w>f!MHcOBhWGZ`}>Uf8`Z$^G@G#vF?oeqTb$# zH(Hdn!S7rwCgddLI%tYUEB<*lW=&~yTI%7hterNbSr>pTc=utO!FoNiBjxCvCu`tU ztDMINoR6c<2Y!%BEoh1l9znkjW84`53iW2H0T>PbrBaPSdr1cm7>(w*An~TXhp(L# zT_tPF$p3Tfih|r3TD@OH1P;sA&{C{Q;k<~ddd0bJ^wqEI8bd zS3v)O1XpB8#Wr8vkoQw1fJ;~`te4A!9*}T^D5nvLwY90S`mtsc!4+Ghv zmZrJbpWu24>Yux5vG1rUR$14OtkL5k`wPRc^#_)dmn9m<3yX?tJ!F^P9r{iM5~G3> zB*^@)U-tp)Jd0EXSa^6vOFD-}NTS{o&6XodVO&q>u zNwD^2z@TER$ON7f*e?m0u8S+Zb_eGjzveFHk{v1jzGk+@4HuME^3zPahz6qAS17eMw(}tO0%*2QC>t*bDe1S!Q0S?S4!Wv15@KAse9@I` zfv5_*ZT%ugk0x?&a@%lGI_UDv6N05JzH@%v^DyL}t>2yu95L!!-o~h7*M7DQVFrTTIrA*=ncA z<&EB8j%cTd9$moRbMHO&kIXE@inc|W;x&bBr{-P3j}CIrgbc^hPO%;pnv%>-nGhxw ze4GYhi0ZCWz&m!%W!AkbMleL&D^{TGlor)N{-C_=NiDPFa;r6Fzq+A)AYq`OMEY`v ztdQKVi|}b(Q40*}t5Y~_9kmU4?BxI2^XnQJAWT;djD3sRM4?bpo3?LxR(lCFTr9SV z3Isl!4PbVKRWPdbrp=&E^Mv|e4)D#5RS!ek}pFm#AF^=S;m zVK^3%vFiC+%t1nAUW!%X%XFB3!?aGz4guR_WNC*k!Xk~MJ`NvhW#Xf&kP1pZTg1Dx z&n5TAPpHd!IWK0L$7+#Y-rQD7Ar-XZa#B5p1Ex?JV@^F`e4>Xn(dfFqJa))@C+Wi4 z%lXmd)U-_?a;tVjAGv%j^U=e1$>DI~{Q-HzC%{a2MBE*94r9S`r| zN9NOQ42!LAJ>!n}7OpX(qtna!HGzMy0@JJu+8;i%Q{E(O!YL-ap3PaYxtj~j*(fYN zb!@^#7=QVa2}ib{X0BY@-yS!5^Xh#_MZzEG<1*>td?-nD^4#pk;#PmfUWrC`$<9#n zx+`BXRT<`Uwz!N4XEKc$rn-{^TvC{A$aBxy+rx>I;?2eig5mhYKkhcNHu^ zFiDEsnxUB3zLW#i)6R_ekB_A3#a>%~GI{)L>&g8h=d3?UHBiKQFW>Zqtz(EbK$O^T zp4_`YhniACTR6k#;;-?@`Jssg$5l~8_aY|}h=7#17ay$?n)WR-S_TJCw1(P4 zRcU?Bt#zek%fF?h6>ntM*f0f6Fqw5Z{E2*9tC#HtnhtJ2zYIXCfDanbnbU6Dutl6v zBpbGWh5F%9wAQ>9DX>+#$4hiP@U6fp%Nc`%pe$nQtXDhYYrL7B&8g~8Rl}SQ)qLt{ zY`(=0lCU>c^!y8KNJEpRqjP`Hau-^Pp2T^uY+X#&dnOE_d8^avU<%%P6}89R{XuJw zw!loi=R8y4syicQav~2+ne{qv`<|3*rZua7yXU#$7=3uWfr*gvK?~3sUV6?su*u8# z`KO)yuh{WB@!@fO3TrLuT$GBs4`tHj%VC9u^B^dC7k-4%_3(9Y_coZSH!TZy7y|YT z7INN7W6BzcY_N;X=Ah`zuk6|i+Q~?XxNoD-$kn;&WNK7EdH`KtFDZLd$=9_7_7sge z@q0qX(NDY<-wLALGFi@H=6AGGqq)e*FmYxdQKQfuji8la%VGoYB$cB{;f5a}ljzsut6 zj{DRn)ux7p#?GiW_`2E6So%02zC4otKpvK~sVIK-gV7c~#xB+wQhV#=n8Q6PaLIGd zf!_%0`Dp_K-*{h2I(^^3wj<7 zGG6p(4f>w86)mzgzPX-VkqpW!_X2qSW4zO^wE{aFxF2El%UHY8j!Ypv^M2j7 zvLPYcImMqVM-TErj+B&Aej8>YHIhn=PgrnBQzz0)*$%1Rj<|&F?HnrfX)LkS{iR>Q zi?Dck2C;2fN2A0Lx}E%1sS_PNW(D8q6ZN7sC(Ka3NcMZbZ+y_B+G_Bd419KM66Q-r z+CT>o0u1^0Ti>~*WCNH8RWS&sX7U|o;)kCSxU^^LIZe-v+*ut_4^V_^2@2JARf zBWoJZ8taqGhxOkih#27lzdUy!j!5ZVPI{~#*k;w4wWFU**iVdg%sD&Lq^!%SB-xH_ z$r^vX5!Kc89YSu`(%Rs5)U+Px!{&V9q4mj6F~uuwnFN~9AIV83K_=d)J$fT}C~fzu k|F5)=|A;PkhMKR5uhCGUej7iN8*PY4Ps>=d0R~U}KX8%E761SM literal 0 HcmV?d00001 diff --git a/Apps/W1/File - Azure File Service Connector/app/README.md b/Apps/W1/File - Azure File Service Connector/app/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Apps/W1/File - Azure File Service Connector/app/app.json b/Apps/W1/File - Azure File Service Connector/app/app.json new file mode 100644 index 0000000000..ecdd40add9 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/app.json @@ -0,0 +1,31 @@ +{ + "id": "79447b11-8301-4d02-a546-2261eb811296", + "name": "File - Azure File Service Connector", + "publisher": "Microsoft", + "brief": "Enable all users to use a Azure File Share to save files from Business Central.", + "description": "This app enables all users to store files in the same Azure File Share in Business Central.", + "version": "25.0.0.0", + "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", + "help": "https://go.microsoft.com/fwlink/?linkid=2134520", + "url": "https://go.microsoft.com/fwlink/?linkid=724011", + "logo": "ExtensionLogo.png", + "application": "24.0.0.0", + "internalsVisibleTo": [], + "dependencies": [], + "screenshots": [], + "platform": "24.0.0.0", + "idRanges": [ + { + "from": 80200, + "to": 80299 + } + ], + "target": "OnPrem", + "resourceExposurePolicy": { + "allowDebugging": false, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" +} \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareEdit.PermissionSet.al b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareEdit.PermissionSet.al new file mode 100644 index 0000000000..cfcb9cb713 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareEdit.PermissionSet.al @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80202 "File Share - Edit" +{ + Assignable = false; + Access = Public; + Caption = 'File Share - Edit'; + + IncludedPermissionSets = "File Share - Read"; + + Permissions = + tabledata "File Share Account" = imd; +} diff --git a/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareObjects.PermissionSet.al b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareObjects.PermissionSet.al new file mode 100644 index 0000000000..a2f8650d7d --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareObjects.PermissionSet.al @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80200 "File Share - Objects" +{ + Assignable = false; + Access = Public; + Caption = 'File Share - Objects'; + + Permissions = + table "File Share Account" = X, + codeunit "File Share Connector Impl." = X, + page "File Share Account Wizard" = X, + page "File Share Account" = X; +} diff --git a/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareRead.PermissionSet.al b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareRead.PermissionSet.al new file mode 100644 index 0000000000..aa3bfde021 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/permissions/FileShareRead.PermissionSet.al @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80201 "File Share - Read" +{ + Assignable = false; + Access = Public; + Caption = 'File Share - Read'; + + IncludedPermissionSets = "File Share - Objects"; + + Permissions = + tabledata "File Share Account" = r; +} diff --git a/Apps/W1/File - Azure File Service Connector/app/permissions/FileSystemAdminFileShare.PermissionSetExt.al b/Apps/W1/File - Azure File Service Connector/app/permissions/FileSystemAdminFileShare.PermissionSetExt.al new file mode 100644 index 0000000000..4078aeb18b --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/permissions/FileSystemAdminFileShare.PermissionSetExt.al @@ -0,0 +1,11 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionsetextension 80200 "File System - Admin - File Share" extends "File System - Admin" +{ + IncludedPermissionSets = "File Share - Edit"; +} diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al new file mode 100644 index 0000000000..6d8daec4c5 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Displays an account that was registered via the File Share connector. +/// +page 80200 "File Share Account" +{ + SourceTable = "File Share Account"; + Caption = 'Azure File Share Account'; + Permissions = tabledata "File Share Account" = rimd; + PageType = Card; + Extensible = false; + InsertAllowed = false; + DataCaptionExpression = Rec.Name; + + layout + { + area(Content) + { + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the storage account connection.'; + ShowMandatory = true; + NotBlank = true; + } + + field(StorageAccountNameField; Rec."Storage Account Name") + { + ApplicationArea = All; + Caption = 'Storage Account Name'; + ToolTip = 'Specifies the Azure Storage name.'; + } + + field(SASTokenField; SASToken) + { + ApplicationArea = All; + Caption = 'SAS Token'; + Editable = PasswordEditable; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the shared access signature to access the file share.'; + + trigger OnValidate() + begin + Rec.SetSAS(SASToken); + end; + } + + field(FileShareNameField; Rec."File Share Name") + { + ApplicationArea = All; + Caption = 'File Share Name'; + ToolTip = 'Specifies the Azure File Share name.'; + } + } + } + + var + PasswordEditable: Boolean; + [NonDebuggable] + SASToken: Text; + + trigger OnOpenPage() + begin + Rec.SetCurrentKey(Name); + + if not IsNullGuid(Rec."SAS Key") then + SASToken := '***'; + end; + + trigger OnAfterGetCurrRecord() + begin + PasswordEditable := CurrPage.Editable(); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al new file mode 100644 index 0000000000..fff3cdb65c --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Holds the information for all file accounts that are registered via the File Share connector +/// +table 80200 "File Share Account" +{ + Access = Internal; + + Caption = 'Azure File Share Account'; + + fields + { + field(1; "Id"; Guid) + { + DataClassification = SystemMetadata; + Caption = 'Primary Key'; + } + + field(2; Name; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Name of account'; + } + field(3; "Storage Account Name"; Text[2048]) + { + Caption = 'Storage Account Name'; + } + field(4; "File Share Name"; Text[2048]) + { + Caption = 'File Share Name'; + } + field(8; "SAS Key"; Guid) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Id) + { + Clustered = true; + } + } + + var + UnableToGetPasswordMsg: Label 'Unable to get File Share Account Key'; + UnableToSetPasswordMsg: Label 'Unable to set File Share Account Key'; + + trigger OnDelete() + begin + if not IsNullGuid(Rec."SAS Key") then + if IsolatedStorage.Delete(Rec."SAS Key") then; + end; + + [NonDebuggable] + procedure SetSAS(SASToken: Text) + begin + if IsNullGuid(Rec."SAS Key") then + Rec."SAS Key" := CreateGuid(); + + if not IsolatedStorage.Set(Format(Rec."SAS Key"), SASToken, DataScope::Company) then + Error(UnableToSetPasswordMsg); + end; + + [NonDebuggable] + procedure GetSAS(SASKey: Guid) Password: Text + begin + if not IsolatedStorage.Get(Format(SASKey), DataScope::Company, Password) then + Error(UnableToGetPasswordMsg); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al new file mode 100644 index 0000000000..09acfca3c1 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al @@ -0,0 +1,158 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +Using System.Environment; + +/// +/// Displays an account that is being registered via the File Share connector. +/// +page 80201 "File Share Account Wizard" +{ + Caption = 'Setup Azure File Share Account'; + SourceTable = "File Share Account"; + SourceTableTemporary = true; + Permissions = tabledata "File Share Account" = rimd; + PageType = NavigatePage; + Extensible = false; + Editable = true; + + layout + { + area(Content) + { + group(TopBanner) + { + Editable = false; + ShowCaption = false; + Visible = TopBannerVisible; + field(NotDoneIcon; MediaResources."Media Reference") + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + ToolTip = ' '; + Caption = ' '; + } + } + + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the Azure File Share account.'; + ShowMandatory = true; + NotBlank = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field(StorageAccountNameField; Rec."Storage Account Name") + { + ApplicationArea = All; + Caption = 'Storage Account Name'; + ToolTip = 'Specifies the Azure Storage name.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field(SASTokenField; SASToken) + { + ApplicationArea = All; + Caption = 'SAS Token'; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the shared access signature to access the file share.'; + ShowMandatory = true; + } + + field(FileShareNameField; Rec."File Share Name") + { + ApplicationArea = All; + Caption = 'File Share Name'; + ToolTip = 'Specifies the file share to use of the storage account.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + } + } + + actions + { + area(processing) + { + action(Back) + { + ApplicationArea = All; + Caption = 'Back'; + ToolTip = 'Back'; + Image = Cancel; + InFooterBar = true; + + trigger OnAction() + begin + CurrPage.Close(); + end; + } + + action(Next) + { + ApplicationArea = All; + Caption = 'Next'; + Image = NextRecord; + Enabled = IsNextEnabled; + InFooterBar = true; + ToolTip = 'Next'; + + trigger OnAction() + begin + FileShareConnectorImpl.CreateAccount(Rec, SASToken, FileShareAccount); + CurrPage.Close(); + end; + } + } + } + + var + FileShareAccount: Record "File Account"; + MediaResources: Record "Media Resources"; + FileShareConnectorImpl: Codeunit "File Share Connector Impl."; + [NonDebuggable] + SASToken: Text; + IsNextEnabled: Boolean; + TopBannerVisible: Boolean; + + trigger OnOpenPage() + var + AssistedSetupLogoTok: Label 'ASSISTEDSETUP-NOTEXT-400PX.PNG', Locked = true; + begin + Rec.Init(); + Rec.Insert(); + + if MediaResources.Get(AssistedSetupLogoTok) and (CurrentClientType() = ClientType::Web) then + TopBannerVisible := MediaResources."Media Reference".HasValue(); + end; + + internal procedure GetAccount(var FileAccount: Record "File Account"): Boolean + begin + if IsNullGuid(FileShareAccount."Account Id") then + exit(false); + + FileAccount := FileShareAccount; + + exit(true); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnector.EnumExt.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnector.EnumExt.al new file mode 100644 index 0000000000..60f5b72426 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnector.EnumExt.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Enum extension to register the File Share connector. +/// +enumextension 80200 "File Share Connector" extends "File System Connector" +{ + /// + /// The File Share connector. + /// + value(80200; "File Share") + { + Caption = 'File Share'; + Implementation = "File System Connector" = "File Share Connector Impl."; + } +} \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al new file mode 100644 index 0000000000..e6872b03ae --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al @@ -0,0 +1,445 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +using System.Utilities; +using System.Azure.Storage; +using System.Azure.Storage.Files; + +codeunit 80200 "File Share Connector Impl." implements "File System Connector" +{ + Access = Internal; + Permissions = tabledata "File Share Account" = rimd; + + var + ConnectorDescriptionTxt: Label 'Use Azure File Share to store and retrieve files.'; + NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; + ConnectorBase64LogoTxt: Label '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', Locked = true; + MarkerFileNameTok: Label 'BusinessCentral.FileSystem.txt', Locked = true; + + /// + /// Gets a List of Files stored on the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path to list. + /// Defines the pagination data. + /// A list with all files stored in the path. + procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) + var + AFSDirectoryContent: Record "AFS Directory Content"; + begin + GetDirectoryContent(AccountId, Path, FilePaginationData, AFSDirectoryContent); + + AFSDirectoryContent.SetRange("Parent Directory", Path); + AFSDirectoryContent.SetRange("Resource Type", AFSDirectoryContent."Resource Type"::File); + if not AFSDirectoryContent.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := AFSDirectoryContent.Name; + FileAccountContent.Type := FileAccountContent.Type::"File"; + FileAccountContent."Parent Directory" := AFSDirectoryContent."Parent Directory"; + FileAccountContent.Insert(); + until AFSDirectoryContent.Next() = 0; + end; + + /// + /// Gets a file from the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path inside the file account. + /// The Stream were the file is read to. + procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.GetFileAsStream(Path, Stream); + + if AFSOperationResponse.IsSuccessful() then + exit; + + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Gets a file to the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + /// The Stream were the file is read from. + procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + + AFSOperationResponse := AFSFileClient.CreateFile(Path, Stream); + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + + AFSOperationResponse := AFSFileClient.PutFileStream(Path, Stream); + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Copies as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.CopyFile(TargetPath, SourcePath); + + if AFSOperationResponse.IsSuccessful() then + exit; + + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Move as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.RenameFile(TargetPath, SourcePath); + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Checks if a file exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + /// Returns true if the file exists + procedure FileExists(AccountId: Guid; Path: Text): Boolean + var + AFSFileClient: Codeunit "AFS File Client"; + AFSDirectoryContent: Record "AFS Directory Content"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + AFSOptionalParameters: Codeunit "AFS Optional Parameters"; + begin + if Path = '' then + exit(false); + + InitFileClient(AccountId, AFSFileClient); + AFSOptionalParameters.Prefix(Path); + AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + + exit(not AFSDirectoryContent.IsEmpty()); + end; + + /// + /// Deletes a file exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + procedure DeleteFile(AccountId: Guid; Path: Text) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.DeleteFile(Path); + + if AFSOperationResponse.IsSuccessful() then + exit; + + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Gets a List of Directories stored on the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path to list. + /// Defines the pagination data. + /// A list with all directories stored in the path. + procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) + var + AFSDirectoryContent: Record "AFS Directory Content"; + begin + GetDirectoryContent(AccountId, Path, FilePaginationData, AFSDirectoryContent); + + AFSDirectoryContent.SetRange("Parent Directory", Path); + AFSDirectoryContent.SetRange("Resource Type", AFSDirectoryContent."Resource Type"::Directory); + if not AFSDirectoryContent.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := AFSDirectoryContent.Name; + FileAccountContent.Type := FileAccountContent.Type::Directory; + FileAccountContent."Parent Directory" := AFSDirectoryContent."Parent Directory"; + FileAccountContent.Insert(); + until AFSDirectoryContent.Next() = 0; + end; + + /// + /// Creates a directory on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + procedure CreateDirectory(AccountId: Guid; Path: Text) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + DirectoryAlreadyExistsErr: Label 'Directory already exists.'; + begin + if DirectoryExists(AccountId, Path) then + Error(DirectoryAlreadyExistsErr); + + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.CreateDirectory(Path); + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Checks if a directory exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + /// Returns true if the directory exists + procedure DirectoryExists(AccountId: Guid; Path: Text): Boolean + var + AFSFileClient: Codeunit "AFS File Client"; + AFSDirectoryContent: Record "AFS Directory Content"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + AFSOptionalParameters: Codeunit "AFS Optional Parameters"; + NotFoundTok: Label '404', Locked = true; + begin + if Path = '' then + exit(true); + + InitFileClient(AccountId, AFSFileClient); + AFSOptionalParameters.Prefix(Path); + AFSOptionalParameters.MaxResults(1); + AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); + if not AFSOperationResponse.IsSuccessful() then + if AFSOperationResponse.GetError().Contains(NotFoundTok) then + exit(false) + else + Error(AFSOperationResponse.GetError()); + + exit(not AFSDirectoryContent.IsEmpty()); + end; + + /// + /// Deletes a directory exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + procedure DeleteDirectory(AccountId: Guid; Path: Text) + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + begin + InitFileClient(AccountId, AFSFileClient); + AFSOperationResponse := AFSFileClient.DeleteDirectory(Path); + + if AFSOperationResponse.IsSuccessful() then + exit; + + Error(AFSOperationResponse.GetError()); + end; + + /// + /// Returns the path separator of the file account. + /// + /// The Path separator like / or \ + procedure PathSeparator(): Text + begin + exit('/'); + end; + + /// + /// Gets the registered accounts for the File Share connector. + /// + /// Out parameter holding all the registered accounts for the File Share connector. + procedure GetAccounts(var Accounts: Record "File Account") + var + Account: Record "File Share Account"; + begin + if not Account.FindSet() then + exit; + + repeat + Accounts."Account Id" := Account.Id; + Accounts.Name := Account.Name; + Accounts.Connector := Enum::"File System Connector"::"File Share"; + Accounts.Insert(); + until Account.Next() = 0; + end; + + /// + /// Shows accounts information. + /// + /// The ID of the account to show. + procedure ShowAccountInformation(AccountId: Guid) + var + FileShareAccountLocal: Record "File Share Account"; + begin + if not FileShareAccountLocal.Get(AccountId) then + Error(NotRegisteredAccountErr); + + FileShareAccountLocal.SetRecFilter(); + Page.Run(Page::"File Share Account", FileShareAccountLocal); + end; + + /// + /// Register an file account for the File Share connector. + /// + /// Out parameter holding details of the registered account. + /// True if the registration was successful; false - otherwise. + procedure RegisterAccount(var Account: Record "File Account"): Boolean + var + FileShareAccountWizard: Page "File Share Account Wizard"; + begin + FileShareAccountWizard.RunModal(); + + exit(FileShareAccountWizard.GetAccount(Account)); + end; + + /// + /// Deletes an file account for the File Share connector. + /// + /// The ID of the File Share account + /// True if an account was deleted. + procedure DeleteAccount(AccountId: Guid): Boolean + var + FileShareAccountLocal: Record "File Share Account"; + begin + if FileShareAccountLocal.Get(AccountId) then + exit(FileShareAccountLocal.Delete()); + + exit(false); + end; + + /// + /// Gets a description of the File Share connector. + /// + /// A short description of the File Share connector. + procedure GetDescription(): Text[250] + begin + exit(ConnectorDescriptionTxt); + end; + + /// + /// Gets the File Share connector logo. + /// + /// A base64-formatted image to be used as logo. + procedure GetLogoAsBase64(): Text + begin + exit(ConnectorBase64LogoTxt); + end; + + internal procedure IsAccountValid(var Account: Record "File Share Account" temporary): Boolean + begin + if Account.Name = '' then + exit(false); + + if Account."Storage Account Name" = '' then + exit(false); + + if Account."File Share Name" = '' then + exit(false); + + exit(true); + end; + + [NonDebuggable] + internal procedure CreateAccount(var AccountToCopy: Record "File Share Account"; Password: Text; var FileAccount: Record "File Account") + var + NewFileShareAccount: Record "File Share Account"; + begin + NewFileShareAccount.TransferFields(AccountToCopy); + + NewFileShareAccount.Id := CreateGuid(); + NewFileShareAccount.SetSAS(Password); + + NewFileShareAccount.Insert(); + + FileAccount."Account Id" := NewFileShareAccount.Id; + FileAccount.Name := NewFileShareAccount.Name; + FileAccount.Connector := Enum::"File System Connector"::"File Share"; + end; + + local procedure InitFileClient(var AccountId: Guid; var AFSFileClient: Codeunit "AFS File Client") + var + FileShareAccount: Record "File Share Account"; + StorageServiceAuthorization: Codeunit "Storage Service Authorization"; + Authorization: Interface "Storage Service Authorization"; + begin + FileShareAccount.Get(AccountId); + Authorization := StorageServiceAuthorization.UseReadySAS(FileShareAccount.GetSAS(FileShareAccount."SAS Key")); + AFSFileClient.Initialize(FileShareAccount."Storage Account Name", FileShareAccount."File Share Name", Authorization); + end; + + local procedure CheckPath(var Path: Text) + begin + if (Path <> '') and not Path.EndsWith(PathSeparator()) then + Path += PathSeparator(); + end; + + local procedure CombinePath(Path: Text; ChildPath: Text): Text + begin + if Path = '' then + exit(ChildPath); + + if not Path.EndsWith(PathSeparator()) then + Path += PathSeparator(); + + exit(Path + ChildPath); + end; + + local procedure InitOptionalParameters(var Path: Text; var FilePaginationData: Codeunit "File Pagination Data"; var AFSOptionalParameters: Codeunit "AFS Optional Parameters") + begin + AFSOptionalParameters.Prefix(Path); + AFSOptionalParameters.MaxResults(500); + AFSOptionalParameters.Marker(FilePaginationData.GetMarker()); + end; + + local procedure ValidateListingResponse(var FilePaginationData: Codeunit "File Pagination Data"; var AFSOperationResponse: Codeunit "AFS Operation Response") + begin + if not AFSOperationResponse.IsSuccessful() then + Error(AFSOperationResponse.GetError()); + + FilePaginationData.SetEndOfListing(true); + end; + + local procedure GetDirectoryContent(var AccountId: Guid; var Path: Text; var FilePaginationData: Codeunit "File Pagination Data"; var AFSDirectoryContent: Record "AFS Directory Content") + var + AFSFileClient: Codeunit "AFS File Client"; + AFSOperationResponse: Codeunit "AFS Operation Response"; + AFSOptionalParameters: Codeunit "AFS Optional Parameters"; + begin + InitFileClient(AccountId, AFSFileClient); + CheckPath(Path); + InitOptionalParameters(Path, FilePaginationData, AFSOptionalParameters); + AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); + ValidateListingResponse(FilePaginationData, AFSOperationResponse); + end; +} \ No newline at end of file From 474d088bba483bf8fef939f8788ff2485c85a7fd Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 08:48:15 +0100 Subject: [PATCH 25/33] Add both auth types --- .../app/src/BlobStorageAccount.Page.al | 26 +++++++++------- .../app/src/BlobStorageAccount.Table.al | 30 +++++++++++-------- .../app/src/BlobStorageAccountWizard.Page.al | 18 +++++++---- .../app/src/BlobStorageAuthType.Enum.al | 21 +++++++++++++ .../src/BlobStorageConnectorImpl.Codeunit.al | 26 +++++++++++++--- .../app/src/FileShareAccount.Page.al | 26 +++++++++------- .../app/src/FileShareAccount.Table.al | 30 +++++++++++-------- .../app/src/FileShareAccountWizard.Page.al | 16 ++++++---- .../app/src/FileShareAuthType.Enum.al | 21 +++++++++++++ .../src/FileShareConnectorImpl.Codeunit.al | 16 ++++++++-- 10 files changed, 167 insertions(+), 63 deletions(-) create mode 100644 Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAuthType.Enum.al create mode 100644 Apps/W1/File - Azure File Service Connector/app/src/FileShareAuthType.Enum.al diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al index d85be8ac64..cc7f61c194 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Page.al @@ -38,17 +38,23 @@ page 80100 "Blob Storage Account" ToolTip = 'Specifies the Azure Storage name.'; } - field(Password; Password) + field("Authorization Type"; Rec."Authorization Type") + { + ApplicationArea = All; + ToolTip = 'The way of authorizing used to access the Blob Storage.'; + } + + field(SecretField; Secret) { ApplicationArea = All; Caption = 'Password'; - Editable = PasswordEditable; + Editable = SecretEditable; ExtendedDatatype = Masked; - ToolTip = 'Specifies the shared key to access the Storage Blob.'; + ToolTip = 'Specifies the Shared access signature Token or SharedKey.'; trigger OnValidate() begin - Rec.SetPassword(Password); + Rec.SetSecret(Secret); end; } @@ -63,7 +69,7 @@ page 80100 "Blob Storage Account" BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; begin CurrPage.Update(); - BlobStorageConnectorImpl.LookUpContainer(Rec, Rec.GetPassword(Rec."Password Key"), Text); + BlobStorageConnectorImpl.LookUpContainer(Rec, Rec."Authorization Type", Rec.GetSecret(Rec."Secret Key"), Text); exit(true); end; } @@ -71,20 +77,20 @@ page 80100 "Blob Storage Account" } var - PasswordEditable: Boolean; + SecretEditable: Boolean; [NonDebuggable] - Password: Text; + Secret: Text; trigger OnOpenPage() begin Rec.SetCurrentKey(Name); - if not IsNullGuid(Rec."Password Key") then - Password := '***'; + if not IsNullGuid(Rec."Secret Key") then + Secret := '***'; end; trigger OnAfterGetCurrRecord() begin - PasswordEditable := CurrPage.Editable(); + SecretEditable := CurrPage.Editable(); end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al index 38856da789..050211ca27 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al @@ -35,7 +35,11 @@ table 80100 "Blob Storage Account" { Caption = 'Container Name'; } - field(8; "Password Key"; Guid) + field(7; "Authorization Type"; Enum "Blob Storage Auth. Type") + { + Caption = 'Authorization Type'; + } + field(8; "Secret Key"; Guid) { DataClassification = SystemMetadata; } @@ -50,29 +54,29 @@ table 80100 "Blob Storage Account" } var - UnableToGetPasswordMsg: Label 'Unable to get Blob Storage Account Key'; - UnableToSetPasswordMsg: Label 'Unable to set Blob Storage Account Key'; + UnableToGetSecretMsg: Label 'Unable to get Blob Storage secret.'; + UnableToSetSecretMsg: Label 'Unable to set Blob Storage secret.'; trigger OnDelete() begin - if not IsNullGuid(Rec."Password Key") then - if IsolatedStorage.Delete(Rec."Password Key") then; + if not IsNullGuid(Rec."Secret Key") then + if IsolatedStorage.Delete(Rec."Secret Key") then; end; [NonDebuggable] - procedure SetPassword(Password: Text) + procedure SetSecret(Secret: Text) begin - if IsNullGuid(Rec."Password Key") then - Rec."Password Key" := CreateGuid(); + if IsNullGuid(Rec."Secret Key") then + Rec."Secret Key" := CreateGuid(); - if not IsolatedStorage.Set(Format(Rec."Password Key"), Password, DataScope::Company) then - Error(UnableToSetPasswordMsg); + if not IsolatedStorage.Set(Format(Rec."Secret Key"), Secret, DataScope::Company) then + Error(UnableToSetSecretMsg); end; [NonDebuggable] - procedure GetPassword(PasswordKey: Guid) Password: SecretText + procedure GetSecret(SecretKey: Guid) Secret: SecretText begin - if not IsolatedStorage.Get(Format(PasswordKey), DataScope::Company, Password) then - Error(UnableToGetPasswordMsg); + if not IsolatedStorage.Get(Format(SecretKey), DataScope::Company, Secret) then + Error(UnableToGetSecretMsg); end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al index 1e43a84aed..5272131235 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccountWizard.Page.al @@ -66,12 +66,18 @@ page 80101 "Blob Storage Account Wizard" end; } - field(PasswordField; Password) + field("Authorization Type"; Rec."Authorization Type") { ApplicationArea = All; - Caption = 'Password'; + ToolTip = 'The way of authorizing used to access the Blob Storage.'; + } + + field(SecretField; Secret) + { + ApplicationArea = All; + Caption = 'Secret'; ExtendedDatatype = Masked; - ToolTip = 'Specifies the shared key of the Storage Blob.'; + ToolTip = 'Specifies the Shared access signature Token or SharedKey.'; ShowMandatory = true; } @@ -87,7 +93,7 @@ page 80101 "Blob Storage Account Wizard" BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; begin CurrPage.Update(); - BlobStorageConnectorImpl.LookUpContainer(Rec, Password, Text); + BlobStorageConnectorImpl.LookUpContainer(Rec, "Authorization Type", Secret, Text); exit(true); end; @@ -128,7 +134,7 @@ page 80101 "Blob Storage Account Wizard" trigger OnAction() begin - BlobStorageConnectorImpl.CreateAccount(Rec, Password, BlobStorageAccount); + BlobStorageConnectorImpl.CreateAccount(Rec, Secret, BlobStorageAccount); CurrPage.Close(); end; } @@ -140,7 +146,7 @@ page 80101 "Blob Storage Account Wizard" MediaResources: Record "Media Resources"; BlobStorageConnectorImpl: Codeunit "Blob Storage Connector Impl."; [NonDebuggable] - Password: Text; + Secret: Text; IsNextEnabled: Boolean; TopBannerVisible: Boolean; diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAuthType.Enum.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAuthType.Enum.al new file mode 100644 index 0000000000..46a4102913 --- /dev/null +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAuthType.Enum.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +enum 80100 "Blob Storage Auth. Type" +{ + Access = Internal; + + value(0; + SasToken) + { + Caption = 'Shared Access Signature'; + } + value(1; SharedKey) + { + Caption = 'Shared Key'; + } +} diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 7af193c184..eef1c6df65 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -396,7 +396,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" NewBlobStorageAccount.TransferFields(AccountToCopy); NewBlobStorageAccount.Id := CreateGuid(); - NewBlobStorageAccount.SetPassword(Password); + NewBlobStorageAccount.SetSecret(Password); NewBlobStorageAccount.Insert(); @@ -405,7 +405,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" FileAccount.Connector := Enum::"File System Connector"::"Blob Storage"; end; - internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; Password: SecretText; var NewContainerName: Text[2048]) + internal procedure LookUpContainer(var Account: Record "Blob Storage Account"; AuthType: Enum "Blob Storage Auth. Type"; Secret: SecretText; var NewContainerName: Text[2048]) var ABSContainers: Record "ABS Container"; ABSContainerClient: Codeunit "ABS Container Client"; @@ -414,7 +414,13 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" Authorization: Interface "Storage Service Authorization"; begin Account.TestField("Storage Account Name"); - Authorization := StorageServiceAuthorization.CreateSharedKey(Password); + case AuthType of + AuthType::SasToken: + Authorization := SetReadySAS(StorageServiceAuthorization, Secret); + AuthType::SharedKey: + Authorization := StorageServiceAuthorization.CreateSharedKey(Secret); + end; + ABSContainerClient.Initialize(Account."Storage Account Name", Authorization); ABSOperationResponse := ABSContainerClient.ListContainers(ABSContainers); if not ABSOperationResponse.IsSuccessful() then @@ -436,7 +442,12 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" Authorization: Interface "Storage Service Authorization"; begin BlobStorageAccount.Get(AccountId); - Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetPassword(BlobStorageAccount."Password Key")); + case BlobStorageAccount."Authorization Type" of + "Blob Storage Auth. Type"::SharedKey: + Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetSecret(BlobStorageAccount."Secret Key")); + "Blob Storage Auth. Type"::SasToken: + Authorization := SetReadySAS(StorageServiceAuthorization, BlobStorageAccount.GetSecret(BlobStorageAccount."Secret Key")); + end; ABSBlobClient.Initialize(BlobStorageAccount."Storage Account Name", BlobStorageAccount."Container Name", Authorization); end; @@ -472,4 +483,11 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" FilePaginationData.SetMarker(ABSOperationResponse.GetNextMarker()); FilePaginationData.SetEndOfListing(ABSOperationResponse.GetNextMarker() = ''); end; + + + [NonDebuggable] + local procedure SetReadySAS(var StorageServiceAuthorization: Codeunit "Storage Service Authorization"; Secret: SecretText): Interface System.Azure.Storage."Storage Service Authorization" + begin + exit(StorageServiceAuthorization.UseReadySAS(Secret.Unwrap())); + end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al index 6d8daec4c5..17d98eec11 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Page.al @@ -38,17 +38,23 @@ page 80200 "File Share Account" ToolTip = 'Specifies the Azure Storage name.'; } - field(SASTokenField; SASToken) + field("Authorization Type"; Rec."Authorization Type") { ApplicationArea = All; - Caption = 'SAS Token'; - Editable = PasswordEditable; + ToolTip = 'The way of authorizing used to access the Blob Storage.'; + } + + field(SecretField; Secret) + { + ApplicationArea = All; + Caption = 'Password'; + Editable = SecretEditable; ExtendedDatatype = Masked; - ToolTip = 'Specifies the shared access signature to access the file share.'; + ToolTip = 'Specifies the Shared access signature Token or SharedKey.'; trigger OnValidate() begin - Rec.SetSAS(SASToken); + Rec.SetSecret(Secret); end; } @@ -62,20 +68,20 @@ page 80200 "File Share Account" } var - PasswordEditable: Boolean; + SecretEditable: Boolean; [NonDebuggable] - SASToken: Text; + Secret: Text; trigger OnOpenPage() begin Rec.SetCurrentKey(Name); - if not IsNullGuid(Rec."SAS Key") then - SASToken := '***'; + if not IsNullGuid(Rec."Secret Key") then + Secret := '***'; end; trigger OnAfterGetCurrRecord() begin - PasswordEditable := CurrPage.Editable(); + SecretEditable := CurrPage.Editable(); end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al index fff3cdb65c..3b58a6454d 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al @@ -35,7 +35,11 @@ table 80200 "File Share Account" { Caption = 'File Share Name'; } - field(8; "SAS Key"; Guid) + field(7; "Authorization Type"; Enum "File Share Auth. Type") + { + Caption = 'Authorization Type'; + } + field(8; "Secret Key"; Guid) { DataClassification = SystemMetadata; } @@ -50,29 +54,29 @@ table 80200 "File Share Account" } var - UnableToGetPasswordMsg: Label 'Unable to get File Share Account Key'; - UnableToSetPasswordMsg: Label 'Unable to set File Share Account Key'; + UnableToGetSecretMsg: Label 'Unable to get File Share Account secret.'; + UnableToSetSecretMsg: Label 'Unable to set File Share Account secret.'; trigger OnDelete() begin - if not IsNullGuid(Rec."SAS Key") then - if IsolatedStorage.Delete(Rec."SAS Key") then; + if not IsNullGuid(Rec."Secret Key") then + if IsolatedStorage.Delete(Rec."Secret Key") then; end; [NonDebuggable] - procedure SetSAS(SASToken: Text) + procedure SetSecret(Secret: SecretText) begin - if IsNullGuid(Rec."SAS Key") then - Rec."SAS Key" := CreateGuid(); + if IsNullGuid(Rec."Secret Key") then + Rec."Secret Key" := CreateGuid(); - if not IsolatedStorage.Set(Format(Rec."SAS Key"), SASToken, DataScope::Company) then - Error(UnableToSetPasswordMsg); + if not IsolatedStorage.Set(Format(Rec."Secret Key"), Secret, DataScope::Company) then + Error(UnableToSetSecretMsg); end; [NonDebuggable] - procedure GetSAS(SASKey: Guid) Password: Text + procedure GetSecret(SecretKey: Guid) Secret: SecretText begin - if not IsolatedStorage.Get(Format(SASKey), DataScope::Company, Password) then - Error(UnableToGetPasswordMsg); + if not IsolatedStorage.Get(Format(SecretKey), DataScope::Company, Secret) then + Error(UnableToGetSecretMsg); end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al index 09acfca3c1..27c4433fbf 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccountWizard.Page.al @@ -66,12 +66,18 @@ page 80201 "File Share Account Wizard" end; } - field(SASTokenField; SASToken) + field("Authorization Type"; Rec."Authorization Type") { ApplicationArea = All; - Caption = 'SAS Token'; + ToolTip = 'The way of authorizing used to access the Blob Storage.'; + } + + field(SecretField; Secret) + { + ApplicationArea = All; + Caption = 'Secret'; ExtendedDatatype = Masked; - ToolTip = 'Specifies the shared access signature to access the file share.'; + ToolTip = 'Specifies the Shared access signature Token or SharedKey.'; ShowMandatory = true; } @@ -119,7 +125,7 @@ page 80201 "File Share Account Wizard" trigger OnAction() begin - FileShareConnectorImpl.CreateAccount(Rec, SASToken, FileShareAccount); + FileShareConnectorImpl.CreateAccount(Rec, Secret, FileShareAccount); CurrPage.Close(); end; } @@ -131,7 +137,7 @@ page 80201 "File Share Account Wizard" MediaResources: Record "Media Resources"; FileShareConnectorImpl: Codeunit "File Share Connector Impl."; [NonDebuggable] - SASToken: Text; + Secret: Text; IsNextEnabled: Boolean; TopBannerVisible: Boolean; diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAuthType.Enum.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAuthType.Enum.al new file mode 100644 index 0000000000..531f3965d3 --- /dev/null +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAuthType.Enum.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +enum 80200 "File Share Auth. Type" +{ + Access = Internal; + + value(0; + SasToken) + { + Caption = 'Shared Access Signature'; + } + value(1; SharedKey) + { + Caption = 'Shared Key'; + } +} diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al index e6872b03ae..9b5d822bc6 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al @@ -378,7 +378,7 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" NewFileShareAccount.TransferFields(AccountToCopy); NewFileShareAccount.Id := CreateGuid(); - NewFileShareAccount.SetSAS(Password); + NewFileShareAccount.SetSecret(Password); NewFileShareAccount.Insert(); @@ -394,7 +394,13 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" Authorization: Interface "Storage Service Authorization"; begin FileShareAccount.Get(AccountId); - Authorization := StorageServiceAuthorization.UseReadySAS(FileShareAccount.GetSAS(FileShareAccount."SAS Key")); + case FileShareAccount."Authorization Type" of + FileShareAccount."Authorization Type"::SasToken: + Authorization := StorageServiceAuthorization.CreateSharedKey(FileShareAccount.GetSecret(FileShareAccount."Secret Key")); + FileShareAccount."Authorization Type"::SharedKey: + Authorization := SetReadySAS(StorageServiceAuthorization, FileShareAccount.GetSecret(FileShareAccount."Secret Key")); + end; + AFSFileClient.Initialize(FileShareAccount."Storage Account Name", FileShareAccount."File Share Name", Authorization); end; @@ -442,4 +448,10 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); ValidateListingResponse(FilePaginationData, AFSOperationResponse); end; + + [NonDebuggable] + local procedure SetReadySAS(var StorageServiceAuthorization: Codeunit "Storage Service Authorization"; Secret: SecretText): Interface System.Azure.Storage."Storage Service Authorization" + begin + exit(StorageServiceAuthorization.UseReadySAS(Secret.Unwrap())); + end; } \ No newline at end of file From bfca3619ea982421fbb4f45f16afae3a8f70d197 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 09:20:01 +0100 Subject: [PATCH 26/33] Apply interface changes --- .../src/BlobStorageConnectorImpl.Codeunit.al | 20 ++++++++----------- .../src/FileShareConnectorImpl.Codeunit.al | 18 +++++++---------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index eef1c6df65..2fd3262d73 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -75,12 +75,12 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" end; /// - /// Gets a file to the provided account. + /// Create a file in the provided account. /// /// The file account ID which is used to send out the file. /// The file path inside the file account. /// The Stream were the file is read from. - procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream) + procedure CreateFile(AccountId: Guid; Path: Text; Stream: InStream) var ABSBlobClient: Codeunit "ABS Blob Client"; ABSOperationResponse: Codeunit "ABS Operation Response"; @@ -235,7 +235,7 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" OStream.WriteText(MarkerFileContentTok); TempBlob.CreateInStream(IStream); - SetFile(AccountId, Path, IStream); + CreateFile(AccountId, Path, IStream); end; /// @@ -284,15 +284,6 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" DeleteFile(AccountId, CombinePath(Path, MarkerFileNameTok)); end; - /// - /// Returns the path separator of the file account. - /// - /// The Path separator like / or \ - procedure PathSeparator(): Text - begin - exit('/'); - end; - /// /// Gets the registered accounts for the Blob Storage connector. /// @@ -490,4 +481,9 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" begin exit(StorageServiceAuthorization.UseReadySAS(Secret.Unwrap())); end; + + local procedure PathSeparator(): Text + begin + exit('/'); + end; } \ No newline at end of file diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al index 9b5d822bc6..aed11ead4b 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al @@ -68,12 +68,12 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" end; /// - /// Gets a file to the provided account. + /// Create a file in the provided account. /// /// The file account ID which is used to send out the file. /// The file path inside the file account. /// The Stream were the file is read from. - procedure SetFile(AccountId: Guid; Path: Text; Stream: InStream) + procedure CreateFile(AccountId: Guid; Path: Text; Stream: InStream) var AFSFileClient: Codeunit "AFS File Client"; AFSOperationResponse: Codeunit "AFS Operation Response"; @@ -266,15 +266,6 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" Error(AFSOperationResponse.GetError()); end; - /// - /// Returns the path separator of the file account. - /// - /// The Path separator like / or \ - procedure PathSeparator(): Text - begin - exit('/'); - end; - /// /// Gets the registered accounts for the File Share connector. /// @@ -454,4 +445,9 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" begin exit(StorageServiceAuthorization.UseReadySAS(Secret.Unwrap())); end; + + local procedure PathSeparator(): Text + begin + exit('/'); + end; } \ No newline at end of file From d3997594f67b0f8df7fe85a56bc6244402e4430b Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 09:45:04 +0100 Subject: [PATCH 27/33] Fix auth case type --- .../app/src/BlobStorageConnectorImpl.Codeunit.al | 4 ++-- .../app/src/FileShareConnectorImpl.Codeunit.al | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al index 2fd3262d73..2189c88871 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageConnectorImpl.Codeunit.al @@ -434,10 +434,10 @@ codeunit 80100 "Blob Storage Connector Impl." implements "File System Connector" begin BlobStorageAccount.Get(AccountId); case BlobStorageAccount."Authorization Type" of - "Blob Storage Auth. Type"::SharedKey: - Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetSecret(BlobStorageAccount."Secret Key")); "Blob Storage Auth. Type"::SasToken: Authorization := SetReadySAS(StorageServiceAuthorization, BlobStorageAccount.GetSecret(BlobStorageAccount."Secret Key")); + "Blob Storage Auth. Type"::SharedKey: + Authorization := StorageServiceAuthorization.CreateSharedKey(BlobStorageAccount.GetSecret(BlobStorageAccount."Secret Key")); end; ABSBlobClient.Initialize(BlobStorageAccount."Storage Account Name", BlobStorageAccount."Container Name", Authorization); end; diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al index aed11ead4b..7588a7be8e 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al @@ -387,9 +387,9 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" FileShareAccount.Get(AccountId); case FileShareAccount."Authorization Type" of FileShareAccount."Authorization Type"::SasToken: - Authorization := StorageServiceAuthorization.CreateSharedKey(FileShareAccount.GetSecret(FileShareAccount."Secret Key")); - FileShareAccount."Authorization Type"::SharedKey: Authorization := SetReadySAS(StorageServiceAuthorization, FileShareAccount.GetSecret(FileShareAccount."Secret Key")); + FileShareAccount."Authorization Type"::SharedKey: + Authorization := StorageServiceAuthorization.CreateSharedKey(FileShareAccount.GetSecret(FileShareAccount."Secret Key")); end; AFSFileClient.Initialize(FileShareAccount."Storage Account Name", FileShareAccount."File Share Name", Authorization); From b82e490eb59553cce8bf8594e2bff0e7e564b1da Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 10:01:57 +0100 Subject: [PATCH 28/33] Fix File Exists --- .../app/src/FileShareConnectorImpl.Codeunit.al | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al index 7588a7be8e..365796164d 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareConnectorImpl.Codeunit.al @@ -19,6 +19,7 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; ConnectorBase64LogoTxt: Label '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', Locked = true; MarkerFileNameTok: Label 'BusinessCentral.FileSystem.txt', Locked = true; + NotFoundTok: Label '404', Locked = true; /// /// Gets a List of Files stored on the provided account. @@ -138,17 +139,21 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" AFSDirectoryContent: Record "AFS Directory Content"; AFSOperationResponse: Codeunit "AFS Operation Response"; AFSOptionalParameters: Codeunit "AFS Optional Parameters"; + TargetText: Text; begin if Path = '' then exit(false); InitFileClient(AccountId, AFSFileClient); - AFSOptionalParameters.Prefix(Path); - AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); - if not AFSOperationResponse.IsSuccessful() then + AFSOptionalParameters.Range(0, 1); + + AFSOperationResponse := AFSFileClient.GetFileAsText(Path, TargetText, AFSOptionalParameters); + if AFSOperationResponse.GetError().Contains(NotFoundTok) then + exit(false) + else Error(AFSOperationResponse.GetError()); - exit(not AFSDirectoryContent.IsEmpty()); + exit(true); end; /// @@ -229,13 +234,11 @@ codeunit 80200 "File Share Connector Impl." implements "File System Connector" AFSDirectoryContent: Record "AFS Directory Content"; AFSOperationResponse: Codeunit "AFS Operation Response"; AFSOptionalParameters: Codeunit "AFS Optional Parameters"; - NotFoundTok: Label '404', Locked = true; begin if Path = '' then exit(true); InitFileClient(AccountId, AFSFileClient); - AFSOptionalParameters.Prefix(Path); AFSOptionalParameters.MaxResults(1); AFSOperationResponse := AFSFileClient.ListDirectory(Path, AFSDirectoryContent, AFSOptionalParameters); if not AFSOperationResponse.IsSuccessful() then From 5864984208ea993996cf3f607ebd260d1da2bc85 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 11:00:05 +0100 Subject: [PATCH 29/33] Add SharePoint Connector --- .../app/ExtensionLogo.png | Bin 0 -> 4681 bytes .../File - SharePoint Connector/app/README.md | 3 + .../File - SharePoint Connector/app/app.json | 31 ++ .../SharePointAdmin.PermissionSetExt.al | 11 + .../SharePointEdit.PermissionSet.al | 18 + .../SharePointObjects.PermissionSet.al | 19 + .../SharePointRead.PermissionSet.al | 18 + .../app/src/SharePointAccount.Page.al | 92 ++++ .../app/src/SharePointAccount.Table.al | 86 ++++ .../app/src/SharePointAccountWizard.Page.al | 181 ++++++++ .../app/src/SharePointConnector.EnumExt.al | 21 + .../src/SharePointConnectorImpl.Codeunit.al | 395 ++++++++++++++++++ 12 files changed, 875 insertions(+) create mode 100644 Apps/W1/File - SharePoint Connector/app/ExtensionLogo.png create mode 100644 Apps/W1/File - SharePoint Connector/app/README.md create mode 100644 Apps/W1/File - SharePoint Connector/app/app.json create mode 100644 Apps/W1/File - SharePoint Connector/app/permissions/SharePointAdmin.PermissionSetExt.al create mode 100644 Apps/W1/File - SharePoint Connector/app/permissions/SharePointEdit.PermissionSet.al create mode 100644 Apps/W1/File - SharePoint Connector/app/permissions/SharePointObjects.PermissionSet.al create mode 100644 Apps/W1/File - SharePoint Connector/app/permissions/SharePointRead.PermissionSet.al create mode 100644 Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Page.al create mode 100644 Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al create mode 100644 Apps/W1/File - SharePoint Connector/app/src/SharePointAccountWizard.Page.al create mode 100644 Apps/W1/File - SharePoint Connector/app/src/SharePointConnector.EnumExt.al create mode 100644 Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al diff --git a/Apps/W1/File - SharePoint Connector/app/ExtensionLogo.png b/Apps/W1/File - SharePoint Connector/app/ExtensionLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..30941b354fa335cad3ea5426ac24cadb2ee328e5 GIT binary patch literal 4681 zcmc&&XHyeg(#9JF|QCI_JtTHP)d9vw?|-h^X~+HO+3~{=Y^}dgJd| zZsgwtiU3`kU?L()hJOt(_A~a9h=`t`r>Slc{(PrmE|9&MA$7@|AnwlN2KOad*5-LZ zZg_ubG>D{{Px4B^#6TNkLBhxOAsX)Ol$^SOw zvZLIccJIo2ZC;ipTEG~N!1t+4MgO(1ocz=&{>BE|@yR*aROgK4^XH9?bgQ9eo|T$2 z6l!y3C1rpGY)!vF5S|kLVc?dAO{n^NaC4y7A$5xyq{4&2xU?mDNSr8?LDuG_>zCyZ zMmd-N3i^d_=BK?~+&*xxd{s;*BZe3@#i{;k|B?t7ib{8VhQ+kos)UnRWw0-ve7c@IZGQGChp2H5uiLtFoF>!=+5h+)Vqd2MalH&qcY*VhHwShqLNQkSQmz zx(Ux}b=r7Ru;1fj4p|MrOTgaM>+V_Y(V<`iV8%YaffoQ7ig`lDj1D3*#c=}U&uONF zIosINLm%p|KD1ud%PQw=_(nCI1iJ?SpJ^rs?0J#i5i@Spf$_d12So!H2%bp+8Yki3 zOZKd6gMdtjd8RlWd^@)buBzb>*eI4V2Xy}JnUuSSLY37i!MjayWbs(Phvq}rPMnL| z7~lhWMAlXx*%FN~UX?Cz_uER%#+@*={-i(v?>O2o9=HHlZi-kIAD>hv7MqSVvz)wJ z&bUJd#saK=;uqRYVwa`s#nGLmYC)36pI9Zq3RL0fnkg;kUYbW^fuu?8cPR@d5>VsX`?mBJ+fjqc}G7!Vve%fub#3v@{_W`-dHq{~Qo!o)BeL;? z96pVT_Vi3$=LctJzu6idoi2h3Tu9=0sdXV8kQ3hS$irlnFwGZg& zYtn8zVY&n5z!QI|--Wkqz%0K*@A5~O92Al5HZvVWsr-1)+X_V(3Fd3DrI?%F45s+4 zFdGV%nI4@7{H-?wJ!14$FIC_?Hy7XX-6>K;9%3s_5#1rjova{p+y2)PrONtXL00@| zGp~5hVnCyO9vu4g_a8GFJv7wbqpSTOAhi>0c0OGG4{?>5t}6?@^9w4c$KMvBjstDhHvv{k zO>{5P>{Dc|X5UeYg(W-+esE8*5QTc|VX_6!7<8*LvW2@=xS6Ftd*}}HQE6iQM@yP< zNk1*p(|qo$Z3yyr{KbGa!u|Khypj`Py#8>qdWl`xq4XNe1qtIYWp_nVD6%C#k~q6E zU;>rmoT;E_i7?~!V#(t}tHbXg)>I=j;z05@jNhAu(MnXDe!IfoGkU#!c)6=HB4W>K ze^)$Lz;f?rX(P_L_>$bgeN1)?yoe-uC|hK|uH4uY)~)KSHx2)mhMoHg5)057xya|r zbJ;?Niu!Z|4YmksTLwc~&M6fqY{a6#B8XILkE0jd5a@{`kw|dWA0@A`wyXA2bd zv}ejN*gxL8qI{w>f!MHcOBhWGZ`}>Uf8`Z$^G@G#vF?oeqTb$# zH(Hdn!S7rwCgddLI%tYUEB<*lW=&~yTI%7hterNbSr>pTc=utO!FoNiBjxCvCu`tU ztDMINoR6c<2Y!%BEoh1l9znkjW84`53iW2H0T>PbrBaPSdr1cm7>(w*An~TXhp(L# zT_tPF$p3Tfih|r3TD@OH1P;sA&{C{Q;k<~ddd0bJ^wqEI8bd zS3v)O1XpB8#Wr8vkoQw1fJ;~`te4A!9*}T^D5nvLwY90S`mtsc!4+Ghv zmZrJbpWu24>Yux5vG1rUR$14OtkL5k`wPRc^#_)dmn9m<3yX?tJ!F^P9r{iM5~G3> zB*^@)U-tp)Jd0EXSa^6vOFD-}NTS{o&6XodVO&q>u zNwD^2z@TER$ON7f*e?m0u8S+Zb_eGjzveFHk{v1jzGk+@4HuME^3zPahz6qAS17eMw(}tO0%*2QC>t*bDe1S!Q0S?S4!Wv15@KAse9@I` zfv5_*ZT%ugk0x?&a@%lGI_UDv6N05JzH@%v^DyL}t>2yu95L!!-o~h7*M7DQVFrTTIrA*=ncA z<&EB8j%cTd9$moRbMHO&kIXE@inc|W;x&bBr{-P3j}CIrgbc^hPO%;pnv%>-nGhxw ze4GYhi0ZCWz&m!%W!AkbMleL&D^{TGlor)N{-C_=NiDPFa;r6Fzq+A)AYq`OMEY`v ztdQKVi|}b(Q40*}t5Y~_9kmU4?BxI2^XnQJAWT;djD3sRM4?bpo3?LxR(lCFTr9SV z3Isl!4PbVKRWPdbrp=&E^Mv|e4)D#5RS!ek}pFm#AF^=S;m zVK^3%vFiC+%t1nAUW!%X%XFB3!?aGz4guR_WNC*k!Xk~MJ`NvhW#Xf&kP1pZTg1Dx z&n5TAPpHd!IWK0L$7+#Y-rQD7Ar-XZa#B5p1Ex?JV@^F`e4>Xn(dfFqJa))@C+Wi4 z%lXmd)U-_?a;tVjAGv%j^U=e1$>DI~{Q-HzC%{a2MBE*94r9S`r| zN9NOQ42!LAJ>!n}7OpX(qtna!HGzMy0@JJu+8;i%Q{E(O!YL-ap3PaYxtj~j*(fYN zb!@^#7=QVa2}ib{X0BY@-yS!5^Xh#_MZzEG<1*>td?-nD^4#pk;#PmfUWrC`$<9#n zx+`BXRT<`Uwz!N4XEKc$rn-{^TvC{A$aBxy+rx>I;?2eig5mhYKkhcNHu^ zFiDEsnxUB3zLW#i)6R_ekB_A3#a>%~GI{)L>&g8h=d3?UHBiKQFW>Zqtz(EbK$O^T zp4_`YhniACTR6k#;;-?@`Jssg$5l~8_aY|}h=7#17ay$?n)WR-S_TJCw1(P4 zRcU?Bt#zek%fF?h6>ntM*f0f6Fqw5Z{E2*9tC#HtnhtJ2zYIXCfDanbnbU6Dutl6v zBpbGWh5F%9wAQ>9DX>+#$4hiP@U6fp%Nc`%pe$nQtXDhYYrL7B&8g~8Rl}SQ)qLt{ zY`(=0lCU>c^!y8KNJEpRqjP`Hau-^Pp2T^uY+X#&dnOE_d8^avU<%%P6}89R{XuJw zw!loi=R8y4syicQav~2+ne{qv`<|3*rZua7yXU#$7=3uWfr*gvK?~3sUV6?su*u8# z`KO)yuh{WB@!@fO3TrLuT$GBs4`tHj%VC9u^B^dC7k-4%_3(9Y_coZSH!TZy7y|YT z7INN7W6BzcY_N;X=Ah`zuk6|i+Q~?XxNoD-$kn;&WNK7EdH`KtFDZLd$=9_7_7sge z@q0qX(NDY<-wLALGFi@H=6AGGqq)e*FmYxdQKQfuji8la%VGoYB$cB{;f5a}ljzsut6 zj{DRn)ux7p#?GiW_`2E6So%02zC4otKpvK~sVIK-gV7c~#xB+wQhV#=n8Q6PaLIGd zf!_%0`Dp_K-*{h2I(^^3wj<7 zGG6p(4f>w86)mzgzPX-VkqpW!_X2qSW4zO^wE{aFxF2El%UHY8j!Ypv^M2j7 zvLPYcImMqVM-TErj+B&Aej8>YHIhn=PgrnBQzz0)*$%1Rj<|&F?HnrfX)LkS{iR>Q zi?Dck2C;2fN2A0Lx}E%1sS_PNW(D8q6ZN7sC(Ka3NcMZbZ+y_B+G_Bd419KM66Q-r z+CT>o0u1^0Ti>~*WCNH8RWS&sX7U|o;)kCSxU^^LIZe-v+*ut_4^V_^2@2JARf zBWoJZ8taqGhxOkih#27lzdUy!j!5ZVPI{~#*k;w4wWFU**iVdg%sD&Lq^!%SB-xH_ z$r^vX5!Kc89YSu`(%Rs5)U+Px!{&V9q4mj6F~uuwnFN~9AIV83K_=d)J$fT}C~fzu k|F5)=|A;PkhMKR5uhCGUej7iN8*PY4Ps>=d0R~U}KX8%E761SM literal 0 HcmV?d00001 diff --git a/Apps/W1/File - SharePoint Connector/app/README.md b/Apps/W1/File - SharePoint Connector/app/README.md new file mode 100644 index 0000000000..4f3165deab --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/README.md @@ -0,0 +1,3 @@ +# File SharePoint Connector +This conenctor allows access to Sahre Point Files and Folder. +A proper App Registration with Sites.ReadWrite.All permission is needed. \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/app.json b/Apps/W1/File - SharePoint Connector/app/app.json new file mode 100644 index 0000000000..517d661b93 --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/app.json @@ -0,0 +1,31 @@ +{ + "id": "34bfcef7-f8ed-449f-94be-74024cadba3b", + "name": "File - SharePoint Connector", + "publisher": "Microsoft", + "brief": "Enable all users to use SharePoint Folders to save files from Business Central.", + "description": "This app enables all users to store files in SharePoint Folders with Business Central.", + "version": "25.0.0.0", + "privacyStatement": "https://go.microsoft.com/fwlink/?linkid=724009", + "EULA": "https://go.microsoft.com/fwlink/?linkid=2009120", + "help": "https://go.microsoft.com/fwlink/?linkid=2134520", + "url": "https://go.microsoft.com/fwlink/?linkid=724011", + "logo": "ExtensionLogo.png", + "application": "24.0.0.0", + "internalsVisibleTo": [], + "dependencies": [], + "screenshots": [], + "platform": "24.0.0.0", + "idRanges": [ + { + "from": 80300, + "to": 80399 + } + ], + "target": "OnPrem", + "resourceExposurePolicy": { + "allowDebugging": false, + "allowDownloadingSource": true, + "includeSourceInSymbolFile": true + }, + "contextSensitiveHelpUrl": "https://go.microsoft.com/fwlink/?linkid=2134520" +} \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/permissions/SharePointAdmin.PermissionSetExt.al b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointAdmin.PermissionSetExt.al new file mode 100644 index 0000000000..02a7fbc33e --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointAdmin.PermissionSetExt.al @@ -0,0 +1,11 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionsetextension 80300 "SharePoint - Admin" extends "File System - Admin" +{ + IncludedPermissionSets = "SharePoint - Edit"; +} diff --git a/Apps/W1/File - SharePoint Connector/app/permissions/SharePointEdit.PermissionSet.al b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointEdit.PermissionSet.al new file mode 100644 index 0000000000..3e42a969ce --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointEdit.PermissionSet.al @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80302 "SharePoint - Edit" +{ + Assignable = false; + Access = Public; + Caption = 'SharePoint - Edit'; + + IncludedPermissionSets = "SharePoint - Read"; + + Permissions = + tabledata "SharePoint Account" = imd; +} diff --git a/Apps/W1/File - SharePoint Connector/app/permissions/SharePointObjects.PermissionSet.al b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointObjects.PermissionSet.al new file mode 100644 index 0000000000..2daddfeaec --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointObjects.PermissionSet.al @@ -0,0 +1,19 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80300 "SharePoint - Objects" +{ + Assignable = false; + Access = Public; + Caption = 'SharePoint - Objects'; + + Permissions = + table "SharePoint Account" = X, + codeunit "SharePoint Connector Impl." = X, + page "SharePoint Account Wizard" = X, + page "SharePoint Account" = X; +} diff --git a/Apps/W1/File - SharePoint Connector/app/permissions/SharePointRead.PermissionSet.al b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointRead.PermissionSet.al new file mode 100644 index 0000000000..dc11bef5da --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/permissions/SharePointRead.PermissionSet.al @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +permissionset 80301 "SharePoint - Read" +{ + Assignable = false; + Access = Public; + Caption = 'SharePoint - Read'; + + IncludedPermissionSets = "SharePoint - Objects"; + + Permissions = + tabledata "SharePoint Account" = r; +} diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Page.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Page.al new file mode 100644 index 0000000000..34224f494c --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Page.al @@ -0,0 +1,92 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Displays an account that was registered via the SharePoint connector. +/// +page 80300 "SharePoint Account" +{ + SourceTable = "SharePoint Account"; + Caption = 'SharePoint Account'; + Permissions = tabledata "SharePoint Account" = rimd; + PageType = Card; + Extensible = false; + InsertAllowed = false; + DataCaptionExpression = Rec.Name; + + layout + { + area(Content) + { + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the storage account connection.'; + ShowMandatory = true; + NotBlank = true; + } + + field("Tenant Id"; Rec."Tenant Id") + { + ApplicationArea = All; + ToolTip = 'Specifies the Tenant Id of the App Registration.'; + } + + field("Client Id"; Rec."Client Id") + { + ApplicationArea = All; + ToolTip = 'Specifies the the Client Id of the App Registration.'; + } + + field(SecretField; ClientSecret) + { + ApplicationArea = All; + Caption = 'Password'; + Editable = ClientSecretEditable; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the the Client Secret of the App Registration.'; + + trigger OnValidate() + begin + Rec.SetClientSecret(ClientSecret); + end; + } + + field("SharePoint Url"; Rec."SharePoint Url") + { + ApplicationArea = All; + Caption = 'SharePoint Url'; + ToolTip = 'Specifies the the url to your SharePoint site.'; + } + + field("Base Relative Folder Path"; Rec."Base Relative Folder Path") + { + ApplicationArea = All; + ToolTip = 'Specifies the Base Relative Folder Path to use for this account.'; + } + } + } + + var + ClientSecretEditable: Boolean; + [NonDebuggable] + ClientSecret: Text; + + trigger OnOpenPage() + begin + Rec.SetCurrentKey(Name); + + if not IsNullGuid(Rec."Client Secret Key") then + ClientSecret := '***'; + end; + + trigger OnAfterGetCurrRecord() + begin + ClientSecretEditable := CurrPage.Editable(); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al new file mode 100644 index 0000000000..577dbe9ee1 --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Holds the information for all file accounts that are registered via the SharePoint connector +/// +table 80300 "SharePoint Account" +{ + Access = Internal; + + Caption = 'SharePoint Account'; + + fields + { + field(1; "Id"; Guid) + { + DataClassification = SystemMetadata; + Caption = 'Primary Key'; + } + + field(2; Name; Text[250]) + { + DataClassification = CustomerContent; + Caption = 'Name of account'; + } + field(4; "SharePoint Url"; Text[2048]) + { + Caption = 'SharePoint Url'; + } + field(5; "Base Relative Folder Path"; Text[2048]) + { + Caption = 'Base Relative Folder Path'; + } + field(6; "Tenant Id"; Text[36]) + { + Caption = 'Tenant Id'; + } + field(7; "Client Id"; Text[36]) + { + Caption = 'Client Id'; + } + field(8; "Client Secret Key"; Guid) + { + DataClassification = SystemMetadata; + } + } + + keys + { + key(PK; Id) + { + Clustered = true; + } + } + + var + UnableToGetClientMsg: Label 'Unable to get SharePoint Account Client Secret.'; + UnableToSetClientSecretMsg: Label 'Unable to set SharePoint Client Secret.'; + + trigger OnDelete() + begin + if not IsNullGuid(Rec."Client Secret Key") then + if IsolatedStorage.Delete(Rec."Client Secret Key") then; + end; + + [NonDebuggable] + procedure SetClientSecret(ClientSecret: SecretText) + begin + if IsNullGuid(Rec."Client Secret Key") then + Rec."Client Secret Key" := CreateGuid(); + + if not IsolatedStorage.Set(Format(Rec."Client Secret Key"), ClientSecret, DataScope::Company) then + Error(UnableToSetClientSecretMsg); + end; + + [NonDebuggable] + procedure GetClientSecret(ClientSecretKey: Guid) ClientSecret: SecretText + begin + if not IsolatedStorage.Get(Format(ClientSecretKey), DataScope::Company, ClientSecret) then + Error(UnableToGetClientMsg); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccountWizard.Page.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccountWizard.Page.al new file mode 100644 index 0000000000..900a6a27af --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccountWizard.Page.al @@ -0,0 +1,181 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +Using System.Environment; + +/// +/// Displays an account that is being registered via the SharePoint connector. +/// +page 80301 "SharePoint Account Wizard" +{ + Caption = 'Setup SharePoint Account'; + SourceTable = "SharePoint Account"; + SourceTableTemporary = true; + Permissions = tabledata "SharePoint Account" = rimd; + PageType = NavigatePage; + Extensible = false; + Editable = true; + + layout + { + area(Content) + { + group(TopBanner) + { + Editable = false; + ShowCaption = false; + Visible = TopBannerVisible; + field(NotDoneIcon; MediaResources."Media Reference") + { + ApplicationArea = All; + Editable = false; + ShowCaption = false; + ToolTip = ' '; + Caption = ' '; + } + } + + field(NameField; Rec.Name) + { + ApplicationArea = All; + Caption = 'Account Name'; + ToolTip = 'Specifies the name of the Azure SharePoint account.'; + ShowMandatory = true; + NotBlank = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field("Tenant Id"; Rec."Tenant Id") + { + ApplicationArea = All; + ToolTip = 'Specifies the Tenant Id of the App Registration.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field("Client Id"; Rec."Client Id") + { + ApplicationArea = All; + ToolTip = 'Specifies the the Client Id of the App Registration.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field(ClientSecretField; ClientSecret) + { + ApplicationArea = All; + Caption = 'Client Secret'; + ExtendedDatatype = Masked; + ToolTip = 'Specifies the Client Secret of the App Registration.'; + ShowMandatory = true; + } + + field("SharePoint Url"; Rec."SharePoint Url") + { + ApplicationArea = All; + Caption = 'SharePoint Name'; + ToolTip = 'Specifies the SharePoint to use of the storage account.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + + field("Base Relative Folder Path"; Rec."Base Relative Folder Path") + { + ApplicationArea = All; + ToolTip = 'Specifies the Base Relative Folder Path to use for this account.'; + ShowMandatory = true; + + trigger OnValidate() + begin + IsNextEnabled := FileShareConnectorImpl.IsAccountValid(Rec); + end; + } + } + } + + actions + { + area(processing) + { + action(Back) + { + ApplicationArea = All; + Caption = 'Back'; + ToolTip = 'Back'; + Image = Cancel; + InFooterBar = true; + + trigger OnAction() + begin + CurrPage.Close(); + end; + } + + action(Next) + { + ApplicationArea = All; + Caption = 'Next'; + Image = NextRecord; + Enabled = IsNextEnabled; + InFooterBar = true; + ToolTip = 'Next'; + + trigger OnAction() + begin + FileShareConnectorImpl.CreateAccount(Rec, ClientSecret, FileShareAccount); + CurrPage.Close(); + end; + } + } + } + + var + FileShareAccount: Record "File Account"; + MediaResources: Record "Media Resources"; + FileShareConnectorImpl: Codeunit "SharePoint Connector Impl."; + [NonDebuggable] + ClientSecret: Text; + IsNextEnabled: Boolean; + TopBannerVisible: Boolean; + + trigger OnOpenPage() + var + AssistedSetupLogoTok: Label 'ASSISTEDSETUP-NOTEXT-400PX.PNG', Locked = true; + begin + Rec.Init(); + Rec.Insert(); + + if MediaResources.Get(AssistedSetupLogoTok) and (CurrentClientType() = ClientType::Web) then + TopBannerVisible := MediaResources."Media Reference".HasValue(); + end; + + internal procedure GetAccount(var FileAccount: Record "File Account"): Boolean + begin + if IsNullGuid(FileShareAccount."Account Id") then + exit(false); + + FileAccount := FileShareAccount; + + exit(true); + end; +} \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnector.EnumExt.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnector.EnumExt.al new file mode 100644 index 0000000000..6c6a44bc9f --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnector.EnumExt.al @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +/// +/// Enum extension to register the SharePoint connector. +/// +enumextension 80300 "SharePoint Connector" extends "File System Connector" +{ + /// + /// The SharePoint connector. + /// + value(80300; "SharePoint") + { + Caption = 'SharePoint'; + Implementation = "File System Connector" = "SharePoint Connector Impl."; + } +} \ No newline at end of file diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al new file mode 100644 index 0000000000..8537bdd882 --- /dev/null +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al @@ -0,0 +1,395 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.FileSystem; + +using System.Integration.Sharepoint; +using System.Utilities; +using System.Azure.Storage; +using System.Azure.Storage.Files; + +codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" +{ + Access = Internal; + Permissions = tabledata "SharePoint Account" = rimd; + + var + ConnectorDescriptionTxt: Label 'Use SharePoint to store and retrieve files.'; + NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; + ConnectorBase64LogoTxt: Label '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', Locked = true; + MarkerFileNameTok: Label 'BusinessCentral.FileSystem.txt', Locked = true; + NotFoundTok: Label '404', Locked = true; + + /// + /// Gets a List of Files stored on the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path to list. + /// Defines the pagination data. + /// A list with all files stored in the path. + procedure ListFiles(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) + var + SharePointFile: Record "SharePoint File"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if not SharePointClient.GetFolderFilesByServerRelativeUrl(Path, SharePointFile) then + ShowError(SharePointClient); + + FilePaginationData.SetEndOfListing(true); + + if not SharePointFile.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := SharePointFile.Name; + FileAccountContent.Type := FileAccountContent.Type::"File"; + FileAccountContent."Parent Directory" := Path; + FileAccountContent.Insert(); + until SharePointFile.Next() = 0; + end; + + /// + /// Gets a file from the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path inside the file account. + /// The Stream were the file is read to. + procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream) + var + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.DownloadFileContentByServerRelativeUrl(Path, Stream) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Create a file in the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + /// The Stream were the file is read from. + procedure CreateFile(AccountId: Guid; Path: Text; Stream: InStream) + var + SharePointFile: Record "SharePoint File"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.AddFileToFolder(Path, SharePointFile) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Copies as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure CopyFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + TempBlob: Codeunit "Temp Blob"; + Stream: InStream; + begin + TempBlob.CreateInStream(Stream); + + GetFile(AccountId, SourcePath, Stream); + CreateFile(AccountId, TargetPath, Stream); + end; + + /// + /// Move as file inside the provided account. + /// + /// The file account ID which is used to send out the file. + /// The source file path. + /// The target file path. + procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) + var + TempBlob: Codeunit "Temp Blob"; + Stream: InStream; + begin + TempBlob.CreateInStream(Stream); + + GetFile(AccountId, SourcePath, Stream); + CreateFile(AccountId, TargetPath, Stream); + DeleteFile(AccountId, SourcePath); + end; + + /// + /// Checks if a file exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + /// Returns true if the file exists + procedure FileExists(AccountId: Guid; Path: Text): Boolean + var + SharePointFile: Record "SharePoint File"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if not SharePointClient.GetFolderFilesByServerRelativeUrl(GetParentPath(Path), SharePointFile) then + ShowError(SharePointClient); + + SharePointFile.SetRange(Name, GetFileName(Path)); + exit(not SharePointFile.IsEmpty()); + end; + + /// + /// Deletes a file exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The file path inside the file account. + procedure DeleteFile(AccountId: Guid; Path: Text) + var + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.DeleteFileByServerRelativeUrl(Path) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Gets a List of Directories stored on the provided account. + /// + /// The file account ID which is used to get the file. + /// The file path to list. + /// Defines the pagination data. + /// A list with all directories stored in the path. + procedure ListDirectories(AccountId: Guid; Path: Text; FilePaginationData: Codeunit "File Pagination Data"; var FileAccountContent: Record "File Account Content" temporary) + var + SharePointFolder: Record "SharePoint Folder"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if not SharePointClient.GetSubFoldersByServerRelativeUrl(Path, SharePointFolder) then + ShowError(SharePointClient); + + FilePaginationData.SetEndOfListing(true); + + if not SharePointFolder.FindSet() then + exit; + + repeat + FileAccountContent.Init(); + FileAccountContent.Name := SharePointFolder.Name; + FileAccountContent.Type := FileAccountContent.Type::Directory; + FileAccountContent."Parent Directory" := Path; + FileAccountContent.Insert(); + until SharePointFolder.Next() = 0; + end; + + /// + /// Creates a directory on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + procedure CreateDirectory(AccountId: Guid; Path: Text) + var + SharePointFolder: Record "SharePoint Folder"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.CreateFolder(Path, SharePointFolder) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Checks if a directory exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + /// Returns true if the directory exists + procedure DirectoryExists(AccountId: Guid; Path: Text): Boolean + var + SharePointFolder: Record "SharePoint Folder"; + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.GetSubFoldersByServerRelativeUrl(Path, SharePointFolder) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Deletes a directory exists on the provided account. + /// + /// The file account ID which is used to send out the file. + /// The directory path inside the file account. + procedure DeleteDirectory(AccountId: Guid; Path: Text) + var + SharePointClient: Codeunit "SharePoint Client"; + begin + InitSharePointClient(AccountId, SharePointClient); + if SharePointClient.DeleteFolderByServerRelativeUrl(Path) then + exit; + + ShowError(SharePointClient); + end; + + /// + /// Gets the registered accounts for the SharePoint connector. + /// + /// Out parameter holding all the registered accounts for the SharePoint connector. + procedure GetAccounts(var Accounts: Record "File Account") + var + Account: Record "SharePoint Account"; + begin + if not Account.FindSet() then + exit; + + repeat + Accounts."Account Id" := Account.Id; + Accounts.Name := Account.Name; + Accounts.Connector := Enum::"File System Connector"::"SharePoint"; + Accounts.Insert(); + until Account.Next() = 0; + end; + + /// + /// Shows accounts information. + /// + /// The ID of the account to show. + procedure ShowAccountInformation(AccountId: Guid) + var + FileShareAccountLocal: Record "SharePoint Account"; + begin + if not FileShareAccountLocal.Get(AccountId) then + Error(NotRegisteredAccountErr); + + FileShareAccountLocal.SetRecFilter(); + Page.Run(Page::"SharePoint Account", FileShareAccountLocal); + end; + + /// + /// Register an file account for the SharePoint connector. + /// + /// Out parameter holding details of the registered account. + /// True if the registration was successful; false - otherwise. + procedure RegisterAccount(var Account: Record "File Account"): Boolean + var + FileShareAccountWizard: Page "SharePoint Account Wizard"; + begin + FileShareAccountWizard.RunModal(); + + exit(FileShareAccountWizard.GetAccount(Account)); + end; + + /// + /// Deletes an file account for the SharePoint connector. + /// + /// The ID of the SharePoint account + /// True if an account was deleted. + procedure DeleteAccount(AccountId: Guid): Boolean + var + FileShareAccountLocal: Record "SharePoint Account"; + begin + if FileShareAccountLocal.Get(AccountId) then + exit(FileShareAccountLocal.Delete()); + + exit(false); + end; + + /// + /// Gets a description of the SharePoint connector. + /// + /// A short description of the SharePoint connector. + procedure GetDescription(): Text[250] + begin + exit(ConnectorDescriptionTxt); + end; + + /// + /// Gets the SharePoint connector logo. + /// + /// A base64-formatted image to be used as logo. + procedure GetLogoAsBase64(): Text + begin + exit(ConnectorBase64LogoTxt); + end; + + internal procedure IsAccountValid(var Account: Record "SharePoint Account" temporary): Boolean + begin + if Account.Name = '' then + exit(false); + + if Account."Client Id" = '' then + exit(false); + + if Account."Tenant Id" = '' then + exit(false); + + if Account."SharePoint Url" = '' then + exit(false); + + if Account."Base Relative Folder Path" = '' then + exit(false); + + exit(true); + end; + + [NonDebuggable] + internal procedure CreateAccount(var AccountToCopy: Record "SharePoint Account"; Password: Text; var FileAccount: Record "File Account") + var + NewFileShareAccount: Record "SharePoint Account"; + begin + NewFileShareAccount.TransferFields(AccountToCopy); + + NewFileShareAccount.Id := CreateGuid(); + NewFileShareAccount.SetClientSecret(Password); + + NewFileShareAccount.Insert(); + + FileAccount."Account Id" := NewFileShareAccount.Id; + FileAccount.Name := NewFileShareAccount.Name; + FileAccount.Connector := Enum::"File System Connector"::"SharePoint"; + end; + + local procedure InitSharePointClient(var AccountId: Guid; var SharePointClient: Codeunit "SharePoint Client") + var + FileShareAccount: Record "SharePoint Account"; + SharePointAuth: Codeunit "SharePoint Auth."; + SharePointAuthorization: Interface "SharePoint Authorization"; + Scopes: List of [Text]; + begin + FileShareAccount.Get(AccountId); + Scopes.Add('00000003-0000-0ff1-ce00-000000000000/.default'); + SharePointAuthorization := SharePointAuth.CreateAuthorizationCode(FileShareAccount."Tenant Id", FileShareAccount."Client Id", FileShareAccount.GetClientSecret(FileShareAccount."Client Secret Key"), Scopes); + SharePointClient.Initialize(FileShareAccount."SharePoint Url", SharePointAuthorization); + end; + + local procedure PathSeparator(): Text + begin + exit('/'); + end; + + local procedure ShowError(var SharePointClient: Codeunit "SharePoint Client") + var + ErrorTok: Label 'An error occured.\%1'; + begin + Error(ErrorTok, SharePointClient.GetDiagnostics().GetErrorMessage()); + end; + + local procedure GetParentPath(Path: Text) ParentPath: Text + begin + if (Path.TrimEnd(PathSeparator()).Contains(PathSeparator())) then + ParentPath := Path.TrimEnd(PathSeparator()).Substring(1, Path.LastIndexOf(PathSeparator())); + end; + + local procedure GetFileName(Path: Text) FileName: Text + begin + if (Path.TrimEnd(PathSeparator()).Contains(PathSeparator())) then + FileName := Path.TrimEnd(PathSeparator()).Substring(Path.LastIndexOf(PathSeparator()) + 1); + end; +} \ No newline at end of file From 3b82bc06cb0a734d1ee1856e64a0debf8a20b2d2 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Fri, 1 Mar 2024 13:50:26 +0100 Subject: [PATCH 30/33] Fix sharepoint connector --- .../src/SharePointConnectorImpl.Codeunit.al | 65 ++++++++++++++++--- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al index 8537bdd882..f26cefe239 100644 --- a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al @@ -7,8 +7,6 @@ namespace System.FileSystem; using System.Integration.Sharepoint; using System.Utilities; -using System.Azure.Storage; -using System.Azure.Storage.Files; codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" { @@ -33,7 +31,10 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointFile: Record "SharePoint File"; SharePointClient: Codeunit "SharePoint Client"; + OrginalPath: Text; begin + OrginalPath := Path; + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if not SharePointClient.GetFolderFilesByServerRelativeUrl(Path, SharePointFile) then ShowError(SharePointClient); @@ -47,7 +48,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" FileAccountContent.Init(); FileAccountContent.Name := SharePointFile.Name; FileAccountContent.Type := FileAccountContent.Type::"File"; - FileAccountContent."Parent Directory" := Path; + FileAccountContent."Parent Directory" := OrginalPath; FileAccountContent.Insert(); until SharePointFile.Next() = 0; end; @@ -60,13 +61,22 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" /// The Stream were the file is read to. procedure GetFile(AccountId: Guid; Path: Text; Stream: InStream) var + SharePointFile: Record "SharePoint File"; SharePointClient: Codeunit "SharePoint Client"; + TempBlob, TempBlob2 : Codeunit "Temp Blob"; + Content: HttpContent; + TempBlobStream: InStream; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); - if SharePointClient.DownloadFileContentByServerRelativeUrl(Path, Stream) then - exit; - ShowError(SharePointClient); + TempBlob.CreateInStream(Stream); + if not SharePointClient.DownloadFileContentByServerRelativeUrl(Path, TempBlobStream) then + ShowError(SharePointClient); + + // Platform fix: For some reason the Stream from DownloadFileContentByServerRelativeUrl dies after leaving the interface + Content.WriteFrom(TempBlobStream); + Content.ReadAs(Stream); end; /// @@ -79,9 +89,12 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointFile: Record "SharePoint File"; SharePointClient: Codeunit "SharePoint Client"; + ParentPath, FileName : Text; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); - if SharePointClient.AddFileToFolder(Path, SharePointFile) then + SplitPath(Path, ParentPath, FileName); + if SharePointClient.AddFileToFolder(ParentPath, FileName, Stream, SharePointFile, false) then exit; ShowError(SharePointClient); @@ -133,6 +146,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" SharePointFile: Record "SharePoint File"; SharePointClient: Codeunit "SharePoint Client"; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if not SharePointClient.GetFolderFilesByServerRelativeUrl(GetParentPath(Path), SharePointFile) then ShowError(SharePointClient); @@ -150,6 +164,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointClient: Codeunit "SharePoint Client"; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if SharePointClient.DeleteFileByServerRelativeUrl(Path) then exit; @@ -168,7 +183,10 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointFolder: Record "SharePoint Folder"; SharePointClient: Codeunit "SharePoint Client"; + OrginalPath: Text; begin + OrginalPath := Path; + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if not SharePointClient.GetSubFoldersByServerRelativeUrl(Path, SharePointFolder) then ShowError(SharePointClient); @@ -182,7 +200,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" FileAccountContent.Init(); FileAccountContent.Name := SharePointFolder.Name; FileAccountContent.Type := FileAccountContent.Type::Directory; - FileAccountContent."Parent Directory" := Path; + FileAccountContent."Parent Directory" := OrginalPath; FileAccountContent.Insert(); until SharePointFolder.Next() = 0; end; @@ -197,6 +215,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" SharePointFolder: Record "SharePoint Folder"; SharePointClient: Codeunit "SharePoint Client"; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if SharePointClient.CreateFolder(Path, SharePointFolder) then exit; @@ -215,6 +234,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" SharePointFolder: Record "SharePoint Folder"; SharePointClient: Codeunit "SharePoint Client"; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if SharePointClient.GetSubFoldersByServerRelativeUrl(Path, SharePointFolder) then exit; @@ -231,6 +251,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointClient: Codeunit "SharePoint Client"; begin + InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); if SharePointClient.DeleteFolderByServerRelativeUrl(Path) then exit; @@ -392,4 +413,32 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" if (Path.TrimEnd(PathSeparator()).Contains(PathSeparator())) then FileName := Path.TrimEnd(PathSeparator()).Substring(Path.LastIndexOf(PathSeparator()) + 1); end; + + local procedure InitPath(AccountId: Guid; var Path: Text) + var + FileShareAccount: Record "SharePoint Account"; + begin + FileShareAccount.Get(AccountId); + Path := CombinePath(FileShareAccount."Base Relative Folder Path", Path); + end; + + local procedure CombinePath(Parent: Text; Child: Text): Text + begin + if Parent = '' then + exit(Child); + + if Child = '' then + exit(Parent); + + if not Parent.EndsWith(PathSeparator()) then + Parent += PathSeparator(); + + exit(Parent + Child); + end; + + local procedure SplitPath(Path: Text; var ParentPath: Text; var FileName: Text) + begin + ParentPath := Path.TrimEnd(PathSeparator()).Substring(1, Path.LastIndexOf(PathSeparator())); + FileName := Path.TrimEnd(PathSeparator()).Substring(Path.LastIndexOf(PathSeparator()) + 1); + end; } \ No newline at end of file From e6e5ec982059eb67f3b3b9e2e83a4a9b24dd3bd2 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Sun, 3 Mar 2024 18:36:06 +0100 Subject: [PATCH 31/33] Add review suggestions --- .../app/src/SharePointAccount.Table.al | 4 ++-- .../app/src/SharePointConnectorImpl.Codeunit.al | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al index 577dbe9ee1..71be486c5d 100644 --- a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al @@ -35,11 +35,11 @@ table 80300 "SharePoint Account" { Caption = 'Base Relative Folder Path'; } - field(6; "Tenant Id"; Text[36]) + field(6; "Tenant Id"; Guid) { Caption = 'Tenant Id'; } - field(7; "Client Id"; Text[36]) + field(7; "Client Id"; Guid) { Caption = 'Client Id'; } diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al index f26cefe239..113df42ec9 100644 --- a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al @@ -14,7 +14,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" Permissions = tabledata "SharePoint Account" = rimd; var - ConnectorDescriptionTxt: Label 'Use SharePoint to store and retrieve files.'; + ConnectorDescriptionTxt: Label 'Use SharePoint to store and retrieve files.', MaxLength = 250; NotRegisteredAccountErr: Label 'We could not find the account. Typically, this is because the account has been deleted.'; ConnectorBase64LogoTxt: Label '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', Locked = true; MarkerFileNameTok: Label 'BusinessCentral.FileSystem.txt', Locked = true; @@ -386,7 +386,7 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" begin FileShareAccount.Get(AccountId); Scopes.Add('00000003-0000-0ff1-ce00-000000000000/.default'); - SharePointAuthorization := SharePointAuth.CreateAuthorizationCode(FileShareAccount."Tenant Id", FileShareAccount."Client Id", FileShareAccount.GetClientSecret(FileShareAccount."Client Secret Key"), Scopes); + SharePointAuthorization := SharePointAuth.CreateAuthorizationCode(Format(FileShareAccount."Tenant Id", 0, 4), Format(FileShareAccount."Client Id", 0, 4), FileShareAccount.GetClientSecret(FileShareAccount."Client Secret Key"), Scopes); SharePointClient.Initialize(FileShareAccount."SharePoint Url", SharePointAuthorization); end; From 56d55258292344eb618059407ee00bf460473635 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Tue, 5 Mar 2024 10:07:21 +0100 Subject: [PATCH 32/33] Remove nondebuggable from SecretText Get And Set procedures --- .../app/src/BlobStorageAccount.Table.al | 2 -- .../app/src/FileShareAccount.Table.al | 2 -- .../app/src/SharePointAccount.Table.al | 2 -- 3 files changed, 6 deletions(-) diff --git a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al index 050211ca27..7b1c2bb15b 100644 --- a/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al +++ b/Apps/W1/File - Azure Blob Service Connector/app/src/BlobStorageAccount.Table.al @@ -63,7 +63,6 @@ table 80100 "Blob Storage Account" if IsolatedStorage.Delete(Rec."Secret Key") then; end; - [NonDebuggable] procedure SetSecret(Secret: Text) begin if IsNullGuid(Rec."Secret Key") then @@ -73,7 +72,6 @@ table 80100 "Blob Storage Account" Error(UnableToSetSecretMsg); end; - [NonDebuggable] procedure GetSecret(SecretKey: Guid) Secret: SecretText begin if not IsolatedStorage.Get(Format(SecretKey), DataScope::Company, Secret) then diff --git a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al index 3b58a6454d..38803079ef 100644 --- a/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al +++ b/Apps/W1/File - Azure File Service Connector/app/src/FileShareAccount.Table.al @@ -63,7 +63,6 @@ table 80200 "File Share Account" if IsolatedStorage.Delete(Rec."Secret Key") then; end; - [NonDebuggable] procedure SetSecret(Secret: SecretText) begin if IsNullGuid(Rec."Secret Key") then @@ -73,7 +72,6 @@ table 80200 "File Share Account" Error(UnableToSetSecretMsg); end; - [NonDebuggable] procedure GetSecret(SecretKey: Guid) Secret: SecretText begin if not IsolatedStorage.Get(Format(SecretKey), DataScope::Company, Secret) then diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al index 71be486c5d..836d5fff75 100644 --- a/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointAccount.Table.al @@ -67,7 +67,6 @@ table 80300 "SharePoint Account" if IsolatedStorage.Delete(Rec."Client Secret Key") then; end; - [NonDebuggable] procedure SetClientSecret(ClientSecret: SecretText) begin if IsNullGuid(Rec."Client Secret Key") then @@ -77,7 +76,6 @@ table 80300 "SharePoint Account" Error(UnableToSetClientSecretMsg); end; - [NonDebuggable] procedure GetClientSecret(ClientSecretKey: Guid) ClientSecret: SecretText begin if not IsolatedStorage.Get(Format(ClientSecretKey), DataScope::Company, ClientSecret) then From 7a31007e907465008a1cb34c28864fcf60994ac5 Mon Sep 17 00:00:00 2001 From: Thomas Williamson Date: Wed, 10 Apr 2024 10:04:16 +0200 Subject: [PATCH 33/33] Remove old analyse variables --- .../app/src/SharePointConnectorImpl.Codeunit.al | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al index 113df42ec9..93c4baf992 100644 --- a/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al +++ b/Apps/W1/File - SharePoint Connector/app/src/SharePointConnectorImpl.Codeunit.al @@ -63,14 +63,12 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" var SharePointFile: Record "SharePoint File"; SharePointClient: Codeunit "SharePoint Client"; - TempBlob, TempBlob2 : Codeunit "Temp Blob"; Content: HttpContent; TempBlobStream: InStream; begin InitPath(AccountId, Path); InitSharePointClient(AccountId, SharePointClient); - TempBlob.CreateInStream(Stream); if not SharePointClient.DownloadFileContentByServerRelativeUrl(Path, TempBlobStream) then ShowError(SharePointClient); @@ -125,11 +123,8 @@ codeunit 80300 "SharePoint Connector Impl." implements "File System Connector" /// The target file path. procedure MoveFile(AccountId: Guid; SourcePath: Text; TargetPath: Text) var - TempBlob: Codeunit "Temp Blob"; Stream: InStream; begin - TempBlob.CreateInStream(Stream); - GetFile(AccountId, SourcePath, Stream); CreateFile(AccountId, TargetPath, Stream); DeleteFile(AccountId, SourcePath);