diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index 50a29f5f57..a4dd493f5f 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -8,26 +8,18 @@ on: - 'src/sdk/PnP.Core/**' - 'src/sdk/PnP.Core.Auth/**' - 'src/sdk/PnP.Core.Admin/**' - - 'src/sdk/PnP.Core.Transformation/**' - - 'src/sdk/PnP.Core.Transformation.SharePoint/**' - 'src/sdk/PnP.Core.Test/**' - 'src/sdk/PnP.Core.Auth.Test/**' - 'src/sdk/PnP.Core.Admin.Test/**' - - 'src/sdk/PnP.Core.Transformation.Test/**' - - 'src/sdk/PnP.Core.Transformation.SharePoint.Test/**' pull_request: branches: [ dev ] paths: - 'src/sdk/PnP.Core/**' - 'src/sdk/PnP.Core.Auth/**' - 'src/sdk/PnP.Core.Admin/**' - - 'src/sdk/PnP.Core.Transformation/**' - - 'src/sdk/PnP.Core.Transformation.SharePoint/**' - 'src/sdk/PnP.Core.Test/**' - 'src/sdk/PnP.Core.Auth.Test/**' - 'src/sdk/PnP.Core.Admin.Test/**' - - 'src/sdk/PnP.Core.Transformation.Test/**' - - 'src/sdk/PnP.Core.Transformation.SharePoint.Test/**' jobs: build: diff --git a/README.md b/README.md index 5f8c1c4965..b135d0ec9a 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,7 @@ Nuget package | Downloads | Stable | Preview [PnP.Core](https://pnp.github.io/pnpcore/using-the-sdk/readme.html) | [![Downloads](https://img.shields.io/nuget/dt/pnp.core.svg)](https://www.nuget.org/packages/PnP.Core/) | [![PnP.Core Nuget package](https://img.shields.io/nuget/v/PnP.Core.svg)](https://www.nuget.org/packages/PnP.Core/) | [![PnP.Core Nuget package](https://img.shields.io/nuget/vpre/PnP.Core.svg)](https://www.nuget.org/packages/PnP.Core/) [PnP.Core.Auth](https://pnp.github.io/pnpcore/using-the-sdk/configuring%20authentication.html) | [![Downloads](https://img.shields.io/nuget/dt/pnp.core.auth.svg)](https://www.nuget.org/packages/PnP.Core.Auth/) |[![PnP.Core.Auth Nuget package](https://img.shields.io/nuget/v/PnP.Core.Auth.svg)](https://www.nuget.org/packages/PnP.Core.Auth/) | [![PnP.Core.Auth Nuget package](https://img.shields.io/nuget/vpre/PnP.Core.Auth.svg)](https://www.nuget.org/packages/PnP.Core.Auth/) [PnP.Core.Admin](https://pnp.github.io/pnpcore/using-the-sdk/admin-sharepoint-tenant.html) | [![Downloads](https://img.shields.io/nuget/dt/pnp.core.admin.svg)](https://www.nuget.org/packages/PnP.Core.Admin/) | [![PnP.Core.Admin Nuget package](https://img.shields.io/nuget/v/PnP.Core.Admin.svg)](https://www.nuget.org/packages/PnP.Core.Admin/) | [![PnP.Core.Admin Nuget package](https://img.shields.io/nuget/vpre/PnP.Core.Admin.svg)](https://www.nuget.org/packages/PnP.Core.Admin/) -[PnP.Core.Transformation](https://pnp.github.io/pnpcore/using-the-sdk/transformation-getting-started.html) | [![Downloads](https://img.shields.io/nuget/dt/pnp.core.transformation.svg)](https://www.nuget.org/packages/PnP.Core.Transformation/) | soon | [![PnP.Core.Transformation Nuget package](https://img.shields.io/nuget/vpre/PnP.Core.Transformation.svg)](https://www.nuget.org/packages/PnP.Core.Transformation/) -[PnP.Core.Transformation.SharePoint](https://pnp.github.io/pnpcore/using-the-sdk/transformation-getting-started.html) | [![Downloads](https://img.shields.io/nuget/dt/pnp.core.transformation.sharepoint.svg)](https://www.nuget.org/packages/PnP.Core.Transformation.sharepoint) | soon | [![PnP.Core.Transformation Nuget package](https://img.shields.io/nuget/vpre/PnP.Core.Transformation.sharepoint.svg)](https://www.nuget.org/packages/PnP.Core.Transformation.sharepoint/) + ## Getting started 🚀 For more details on how to get started with the PnP.Core SDK checkout our [documentation](https://pnp.github.io/pnpcore/using-the-sdk/readme.html). diff --git a/build/README.MD b/build/README.MD index e87c8c3db1..601161bb92 100644 --- a/build/README.MD +++ b/build/README.MD @@ -6,7 +6,7 @@ Releasing a new major PnP Core version takes these steps: - Update the major version number in the `version.release` file by 1 (e.g. `1.{minorrelease}.0` will become `2.{minorrelease}.0`) - Reset the minor release version counter by setting it to -1 in the `version.release.increment` file -- Update the `Version` tag in PnP.Core.csproj, PnP.Core.Auth.csproj, PnP.Core.Admin.csproj, PnP.Core.Transformation.csproj and PnP.Core.Transformation.SharePoint.csproj to match the new version +- Update the `Version` tag in PnP.Core.csproj, PnP.Core.Auth.csproj, PnP.Core.Admin.csproj to match the new version - Update the nightly version number in the `version.debug` file to match the major and minor versions of the new release (e.g. `1.3.{incremental}-nightly` will become `2.0.{incremental}-nightly`) - Reset the nightly release version counter by setting it to 0 in the `version.debug.increment` file - Run the `release-official.ps1` script and follow the steps @@ -21,7 +21,7 @@ Releasing a new minor PnP Core version takes these steps: - Update the nightly version number in the `version.debug` file to match the minor version of the new minor release (e.g. `1.0.{incremental}-nightly` will become `1.1.{incremental}-nightly`) - Reset the nightly release version counter by setting it to 0 in the `version.debug.increment` file -- Update the `Version` tag in PnP.Core.csproj, PnP.Core.Auth.csproj, PnP.Core.Admin.csproj, PnP.Core.Transformation.csproj and PnP.Core.Transformation.SharePoint.csproj to match the new version +- Update the `Version` tag in PnP.Core.csproj, PnP.Core.Auth.csproj, PnP.Core.Admin.csproj to match the new version - Run the `release-official.ps1` script and follow the steps - Update readme.md if needed - Update the changelog to reflect the released version diff --git a/build/build-debug.ps1 b/build/build-debug.ps1 index 3bc4e3bc11..dad2e22e28 100644 --- a/build/build-debug.ps1 +++ b/build/build-debug.ps1 @@ -15,12 +15,6 @@ dotnet build ..\src\sdk\PnP.Core.Auth\PnP.Core.Auth.csproj --no-incremental /p:V Write-Host "Building PnP.Core.Admin versions $version" dotnet build ..\src\sdk\PnP.Core.Admin\PnP.Core.Admin.csproj --no-incremental /p:Version=$version -Write-Host "Building PnP.Core.Transformation versions $version" -dotnet build ..\src\sdk\PnP.Core.Transformation\PnP.Core.Transformation.csproj --no-incremental /p:Version=$version - -Write-Host "Building PnP.Core.Transformation.SharePoint versions $version" -dotnet build ..\src\sdk\PnP.Core.Transformation.SharePoint\PnP.Core.Transformation.SharePoint.csproj --no-incremental /p:Version=$version - #Write-Host "Writing $version to git" #Set-Content -Path .\version.debug.increment -Value $versionIncrement diff --git a/build/build.ps1 b/build/build.ps1 index b83a123a0d..eea83abafb 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -27,12 +27,6 @@ dotnet build ./src/sdk/PnP.Core.Auth/PnP.Core.Auth.csproj --configuration Releas Write-Host "Building PnP.Core.Admin version $version" dotnet build ./src/sdk/PnP.Core.Admin/PnP.Core.Admin.csproj --configuration Release --no-incremental /p:Version=$version -Write-Host "Building PnP.Core.Transformation version $version" -dotnet build ./src/sdk/PnP.Core.Transformation/PnP.Core.Transformation.csproj --configuration Release --no-incremental /p:Version=$version - -Write-Host "Building PnP.Core.Transformation.SharePoint version $version" -dotnet build ./src/sdk/PnP.Core.Transformation.SharePoint/PnP.Core.Transformation.SharePoint.csproj --configuration Release --no-incremental /p:Version=$version - Write-Host "Packinging PnP.Core version $version" dotnet pack ./src/sdk/PnP.Core/PnP.Core.csproj --configuration Release --no-build /p:PackageVersion=$version @@ -42,18 +36,10 @@ dotnet pack ./src/sdk/PnP.Core.Auth/PnP.Core.Auth.csproj --configuration Release Write-Host "Packinging PnP.Core.Admin version $version" dotnet pack ./src/sdk/PnP.Core.Admin/PnP.Core.Admin.csproj --configuration Release --no-build /p:PackageVersion=$version -Write-Host "Packinging PnP.Core.Transformation version $version" -dotnet pack ./src/sdk/PnP.Core.Transformation/PnP.Core.Transformation.csproj --configuration Release --no-build /p:PackageVersion=$version - -Write-Host "Packinging PnP.Core.Transformation.SharePoint version $version" -dotnet pack ./src/sdk/PnP.Core.Transformation.SharePoint/PnP.Core.Transformation.SharePoint.csproj --configuration Release --no-build /p:PackageVersion=$version - Write-Host "Publishing to nuget" $nupkg = $("./src/sdk/PnP.Core/bin/Release/PnP.Core.$version.nupkg") $authNupkg = $("./src/sdk/PnP.Core.Auth/bin/Release/PnP.Core.Auth.$version.nupkg") $adminNupkg = $("./src/sdk/PnP.Core.Admin/bin/Release/PnP.Core.Admin.$version.nupkg") -$transformationNupkg = $("./src/sdk/PnP.Core.Transformation/bin/Release/PnP.Core.Transformation.$version.nupkg") -$transformationSharePointNupkg = $("./src/sdk/PnP.Core.Transformation.SharePoint/bin/Release/PnP.Core.Transformation.SharePoint.$version.nupkg") $apiKey = $("$env:NUGET_API_KEY") #Write-Host "API Key starts with:" $apiKey.Substring(0,10) @@ -61,8 +47,6 @@ $apiKey = $("$env:NUGET_API_KEY") dotnet nuget push $nupkg --api-key $apiKey --source https://api.nuget.org/v3/index.json --timeout 600 dotnet nuget push $authNupkg --api-key $apiKey --source https://api.nuget.org/v3/index.json --timeout 600 dotnet nuget push $adminNupkg --api-key $apiKey --source https://api.nuget.org/v3/index.json --timeout 600 -dotnet nuget push $transformationNupkg --api-key $apiKey --source https://api.nuget.org/v3/index.json --timeout 600 -dotnet nuget push $transformationSharePointNupkg --api-key $apiKey --source https://api.nuget.org/v3/index.json --timeout 600 Write-Host "Writing $version to git" Set-Content -Path ./build/version.debug.increment -Value $versionIncrement diff --git a/build/release-signed-nightly.ps1 b/build/release-signed-nightly.ps1 index 0b672101f0..f0138fd562 100644 --- a/build/release-signed-nightly.ps1 +++ b/build/release-signed-nightly.ps1 @@ -16,10 +16,6 @@ Write-Host "Building PnP.Core.Auth version $version..." dotnet build $PSScriptRoot\..\src\sdk\PnP.Core.Auth\PnP.Core.Auth.csproj --configuration Release --no-incremental --force --nologo /p:Version=$version Write-Host "Building PnP.Core.Admin version $version..." dotnet build $PSScriptRoot\..\src\sdk\PnP.Core.Admin\PnP.Core.Admin.csproj --configuration Release --no-incremental --force --nologo /p:Version=$version -Write-Host "Building PnP.Core.Transformation version $version..." -dotnet build $PSScriptRoot\..\src\sdk\PnP.Core.Transformation\PnP.Core.Transformation.csproj --configuration Release --no-incremental --force --nologo /p:Version=$version -Write-Host "Building PnP.Core.Transformation.SharePoint version $version..." -dotnet build $PSScriptRoot\..\src\sdk\PnP.Core.Transformation.SharePoint\PnP.Core.Transformation.SharePoint.csproj --configuration Release --no-incremental --force --nologo /p:Version=$version # Sign the binaries Write-Host "Signing the binaries..." @@ -32,10 +28,6 @@ Write-Host "Packinging PnP.Core.Auth version $version..." dotnet pack $PSScriptRoot\..\src\sdk\PnP.Core.Auth\PnP.Core.Auth.csproj --configuration Release --no-build /p:PackageVersion=$version Write-Host "Packinging PnP.Core.Admin version $version..." dotnet pack $PSScriptRoot\..\src\sdk\PnP.Core.Admin\PnP.Core.Admin.csproj --configuration Release --no-build /p:PackageVersion=$version -Write-Host "Packinging PnP.Core.Transformation version $version..." -dotnet pack $PSScriptRoot\..\src\sdk\PnP.Core.Transformation\PnP.Core.Transformation.csproj --configuration Release --no-build /p:PackageVersion=$version -Write-Host "Packinging PnP.Core.Transformation.SharePoint version $version..." -dotnet pack $PSScriptRoot\..\src\sdk\PnP.Core.Transformation.SharePoint\PnP.Core.Transformation.SharePoint.csproj --configuration Release --no-build /p:PackageVersion=$version # Sign the nuget package is not needed as Nuget signs the package automatically @@ -49,8 +41,6 @@ if ($apiKey.Length -gt 0) nuget push q:\github\pnpcore\src\sdk\PnP.Core\bin\release\PnP.Core.$version.nupkg -ApiKey $apiKey -source https://api.nuget.org/v3/index.json nuget push q:\github\pnpcore\src\sdk\PnP.Core.Auth\bin\release\PnP.Core.Auth.$version.nupkg -ApiKey $apiKey -source https://api.nuget.org/v3/index.json nuget push q:\github\pnpcore\src\sdk\PnP.Core.Admin\bin\release\PnP.Core.Admin.$version.nupkg -ApiKey $apiKey -source https://api.nuget.org/v3/index.json - nuget push q:\github\pnpcore\src\sdk\PnP.Core.Transformation\bin\release\PnP.Core.Transformation.$version.nupkg -ApiKey $apiKey -source https://api.nuget.org/v3/index.json - nuget push q:\github\pnpcore\src\sdk\PnP.Core.Transformation.SharePoint\bin\release\PnP.Core.Transformation.SharePoint.$version.nupkg -ApiKey $apiKey -source https://api.nuget.org/v3/index.json # Persist last used version Write-Host "Writing $version to git" diff --git a/docs/PnP Transformation Framework Architecture.pptx b/docs/PnP Transformation Framework Architecture.pptx deleted file mode 100644 index 8a9908e70a..0000000000 Binary files a/docs/PnP Transformation Framework Architecture.pptx and /dev/null differ diff --git a/docs/using-the-sdk/toc.yml b/docs/using-the-sdk/toc.yml index d407ec24c7..ee34530089 100644 --- a/docs/using-the-sdk/toc.yml +++ b/docs/using-the-sdk/toc.yml @@ -204,10 +204,6 @@ href: teams-channel-tabs.md - name: Tags href: teams-tags.md -- name: "Transformation Framework" - items: - - name: Getting started - href: transformation-getting-started.md - name: Webs items: - name: Overview diff --git a/docs/using-the-sdk/transformation-getting-started.md b/docs/using-the-sdk/transformation-getting-started.md deleted file mode 100644 index 3e0e4e591b..0000000000 --- a/docs/using-the-sdk/transformation-getting-started.md +++ /dev/null @@ -1,179 +0,0 @@ -# PnP Transformation Framework - Getting Started - -The PnP Transformation Framework is a library to transform content pages from any source platform into Microsoft SharePoint Online modern pages. - -At the time of this writing, there is native support for transforming SharePoint classic pages from SharePoint 2013, 2016, 2019, and Online into SharePoint modern pages in SharePoint Online. However, the architecture of the framework is open and extensible -and can be used to create any custom data source for reading content pages from any third party platform. - -You can use the PnP Transformation Framework either via [PnP PowerShell](https://github.com/pnp/powershell) or in your own .NET code as a referenced library. - -> [!Important] -> The transformation framework is not 100% baked and still not officially released. - -## Transforming SharePoint pages via PnP PowerShell - -In order to use the PnP Transformation Framework in PnP PowerShell, you need to [install](https://pnp.github.io/powershell/#getting-up-and-running) the latest build of the PowerShell library. - -Once you have done that, you can simply invoke the *Invoke-PnPTransformation* cmdlet to trigger a page transformation. -Here you can see a sample code excerpt to transform a page from a SharePoint Online classic site into another SharePoint Online modern site. - -```powershell -# Connect to the target site -$targetConnection = Connect-PnPOnline https://target-tenant.sharepoint.com/sites/TargetModernSite/ -ReturnConnection - -# Connect to the source site -Connect-PnPOnline https://source-tenant.sharepoint.com/sites/SourceClassicSite/ - -# Trigger transformation -Invoke-PnPTransformation -Identity source-page.aspx -TargetConnection $targetConnection -``` - -The *Invoke-PnPTransformation* cmdlet supports a rich set of options to easily customize the transformation behavior. In the following example, you can see an invocation with additional settings levaraging the parameters splatting capability of PowerShell and reusing the same settings to transform multiple pages. - -```powershell -# Connect to the target site -$targetConnection = Connect-PnPOnline https://target-tenant.sharepoint.com/sites/TargetModernSite/ -ReturnConnection - -# Connect to the source site -Connect-PnPOnline https://source-tenant.sharepoint.com/sites/SourceClassicSite/ - -# Or you can use parameter splatting -$transformationParams = @{ - LocalStoragePath = "c:\temp" - CopyPageMetadata = $true - KeepPageCreationModificationInformation = $true - KeepPageSpecificPermissions = $true - Overwrite = $true - SetAuthorInPageHeader = $true - TargetPagePrefix = "Migrated_" - RemoveEmptySectionsAndColumns = $true - HandleWikiImagesAndVideos = $true - AddTableListImageAsImageWebPart = $true - IncludeTitleBarWebPart = $true - SkipHiddenWebParts = $true - TargetConnection = $targetConnection -} - -Invoke-PnPTransformation -Identity first-page.aspx @transformationParams -Invoke-PnPTransformation -Identity second-page.aspx @transformationParams -``` -Notice that the *Invoke-PnPTransformation* cmdlet supports SharePoint classic as the unique data source, so you cannot use it to plug into the PnP Transformation Framework a custom data source of your own. - -## Transforming SharePoint pages with custom .NET code - -If you want to use the PnP Transformation Framework in your own custom developed solutions, or eventually leveraging a custom data source that you implemented, you can reference the [PnP Transformation Framework NuGet package](https://www.nuget.org/packages/PnP.Core.Transformation/) in your .NET solution. - -In particular, if you like to transform classic SharePoint content pages to SharePoint Online, you can reference the [PnP Transformation Framework library for SharePoint](https://www.nuget.org/packages/PnP.Core.Transformation.SharePoint/), which includes a dependency on the main PnP Transformation Framework NuGet package. - -> [!Note] -> PnP Transformation Framework library for SharePoint relies on the SharePoint Client-Side Object Model (CSOM) to read the classic content pages from the source. - -The PnP Transformation Framework is based on the PnP Core SDK and relies on dependency injection, so in order to start using it in your code, you need to setup a host context. In the following code excerpt, you can see an example of a console application to transform a SharePoint classic page. - -```csharp -var host = Host.CreateDefaultBuilder() -// Configure logging -.ConfigureServices((hostingContext, services) => -{ - // Add the PnP Core SDK library - services.AddPnPCore(options => { - options.PnPContext.GraphFirst = true; - options.HttpRequests.UserAgent = "ISV|Contoso|ProductX"; - - options.Sites.Add("TargetSite", new PnPCoreSiteOptions - { - SiteUrl = "https://target-tenant.sharepoint.com/sites/TargetModernSite/" - }); - }); - - services.AddPnPCoreAuthentication( - options => { - // Configure an Authentication Provider relying on the interactive authentication - options.Credentials.Configurations.Add("interactive", - new PnPCoreAuthenticationCredentialConfigurationOptions - { - ClientId = "{your_client_id}", - TenantId = "{your_tenant_id}", - Interactive = new PnPCoreAuthenticationInteractiveOptions - { - RedirectUri = new Uri("http://localhost") - } - }); - - // Configure the default authentication provider - options.Credentials.DefaultConfiguration = "interactive"; - - // Map the site defined in AddPnPCore with the - // Authentication Provider configured in this action - options.Sites.Add("TargetSite", - new PnPCoreAuthenticationSiteOptions - { - AuthenticationProviderName = "interactive" - }); - } - ); - - // Register the transformation services for SharePoint as the data source - services.AddPnPSharePointTransformation( - pnpOptions => // Global settings - { - pnpOptions.DisableTelemetry = false; - pnpOptions.PersistenceProviderConnectionString = @"c:\temp"; - }, - pageOptions => // Target modern page creation settings - { - pageOptions.CopyPageMetadata = true; - pageOptions.KeepPageCreationModificationInformation = true; - pageOptions.PostAsNews = false; - pageOptions.PublishPage = false; - pageOptions.DisablePageComments = false; - pageOptions.KeepPageSpecificPermissions = true; - pageOptions.Overwrite = true; - pageOptions.ReplaceHomePageWithDefaultHomePage = true; - pageOptions.SetAuthorInPageHeader = true; - pageOptions.TargetPagePrefix = "Migrated_"; - pageOptions.TargetPageTakesSourcePageName = true; - }, - spOptions => // SharePoint classic source settings - { - spOptions.RemoveEmptySectionsAndColumns = true; - spOptions.ShouldMapUsers = true; - spOptions.HandleWikiImagesAndVideos = true; - spOptions.AddTableListImageAsImageWebPart = true; - spOptions.IncludeTitleBarWebPart = true; - spOptions.SkipHiddenWebParts = true; - spOptions.SkipUrlRewrite = true; - } - ); - -}) -// Let the builder know we're running in a console -.UseConsoleLifetime() -// Add services to the container -.Build(); - -// Start console host -await host.StartAsync(); - -using (var scope = host.Services.CreateScope()) -{ - // Obtain a PnP Context factory - var pnpContextFactory = scope.ServiceProvider.GetRequiredService(); - var pageTransformator = scope.ServiceProvider.GetRequiredService(); - - using (var sourceContext = new ClientContext("https://source-tenant.sharepoint.com/sites/SourceClassicSite/")) - { - var targetContext = await pnpContextFactory.CreateAsync("TargetSite"); - var sourceUri = new Uri("https://source-tenant.sharepoint.com/sites/SourceClassicSite/sitepages/source-page.aspx"); - - var result = await pageTransformator.TransformSharePointAsync(sourceContext, targetContext, sourceUri); - - Console.WriteLine(result.AbsoluteUri); - } -} -``` - -Aside from the dependency injection plumbing and from all the configuration settings for the transformation framework, the real code is all about getting a reference to the data source, which is a CSOM *ClientContext* object, and the target, which is *PnPContext* of PnP Core SDK. Then, you simply invoke the *TransformSharePointAsync* method providing the source context (*ClientContext* of CSOM), the target context (*PnPContext* of PnP Core SDK), and the URL of the page to transform (as a *Uri* type instance). Under the cover the transformation will take place for you and will return you back the URL of the transformed page. - -> [!Note] -> Supporting dependency injection and the service-oriented model makes really simple to use the PnP Transformation Framework in .NET Core and modern cloud-hosted solutions like Azure Functions, Containers, etc. diff --git a/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.csproj b/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.csproj deleted file mode 100644 index 14f2a64507..0000000000 --- a/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - - - - - - - ..\..\src\sdk\PnP.Core\bin\Debug\netstandard2.0\PnP.Core.dll - - - ..\..\src\sdk\PnP.Core.Transformation\bin\Debug\netstandard2.0\PnP.Core.Transformation.dll - - - ..\..\src\sdk\PnP.Core.Transformation.SharePoint\bin\Debug\netstandard2.0\PnP.Core.Transformation.SharePoint.dll - - - - - - Always - - - - diff --git a/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.sln b/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.sln deleted file mode 100644 index ed85973463..0000000000 --- a/samples/PnP.Core.Transformation.ConsoleSample/PnP.Core.Transformation.ConsoleSample.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31424.327 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PnP.Core.Transformation.ConsoleSample", "PnP.Core.Transformation.ConsoleSample.csproj", "{95F081CD-5655-4EF7-BC09-FF32D9453167}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {95F081CD-5655-4EF7-BC09-FF32D9453167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95F081CD-5655-4EF7-BC09-FF32D9453167}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95F081CD-5655-4EF7-BC09-FF32D9453167}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95F081CD-5655-4EF7-BC09-FF32D9453167}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1CFE6AEA-A79F-455F-9A61-1D4497D1F04F} - EndGlobalSection -EndGlobal diff --git a/samples/PnP.Core.Transformation.ConsoleSample/Program.cs b/samples/PnP.Core.Transformation.ConsoleSample/Program.cs deleted file mode 100644 index 45d3c5727c..0000000000 --- a/samples/PnP.Core.Transformation.ConsoleSample/Program.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.SharePoint.Client; -using PnP.Core.Auth; -using PnP.Core.Auth.Services.Builder.Configuration; -using PnP.Core.Services; -using PnP.Core.Services.Builder.Configuration; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint; - -namespace PnP.Core.Transformation.ConsoleSample -{ - class Program - { - static async Task Main(string[] args) - { - #region Dependency Injection plumbing - - var host = Host.CreateDefaultBuilder() - .ConfigureLogging((hostingContext, logging) => - { - logging.AddEventSourceLogger(); - logging.AddConsole(); - }) - .ConfigureServices((hostingContext, services) => - { - // Register the PnP Core services - services.AddPnPCore(); - services.Configure(hostingContext.Configuration.GetSection("PnPCore")); - - // Register the PnP Core authentication services - services.AddPnPCoreAuthentication(); - services.Configure(hostingContext.Configuration.GetSection("PnPCore")); - - // Register the PnP Transformation Framework services with SharePoint as a data source - services.AddPnPSharePointTransformation(null, spOptions => - { - //spOptions.WebPartMappingFile = @"C:\github\pnpcore\src\sdk\PnP.Core.Transformation.SharePoint\MappingFiles\webpartmapping.xml"; - //spOptions.PageLayoutMappingFile = @"C:\github\pnpcore\src\sdk\PnP.Core.Transformation.SharePoint\MappingFiles\pagelayoutmapping.xml"; - spOptions.CopyPageMetadata = true; - spOptions.KeepPageSpecificPermissions = true; - spOptions.RemoveEmptySectionsAndColumns = true; - spOptions.ShouldMapUsers = true; - spOptions.TargetPageTakesSourcePageName = true; - }); - - // Register the CSOM ClientContext for the data source - services.AddTransient(p => { - var clientContext = new ClientContext(hostingContext.Configuration["SourceSite"]); - clientContext.ExecutingWebRequest += (sender, args) => - { - var resource = $"https://{new Uri(hostingContext.Configuration["SourceSite"]).Authority}"; - - var clientId = hostingContext.Configuration.GetSection("PnPCore:Credentials:Configurations:CredentialManager:ClientId")?.Value; - var tenantId = hostingContext.Configuration.GetSection("PnPCore:Credentials:Configurations:CredentialManager:TenantId")?.Value; - var credentialManager = hostingContext.Configuration.GetSection("PnPCore:Credentials:Configurations:CredentialManager:CredentialManager:CredentialManagerName")?.Value; - - var cmap = new CredentialManagerAuthenticationProvider(clientId, tenantId, credentialManager); - if (cmap != null) - { - args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + cmap.GetAccessTokenAsync(new Uri(resource)).GetAwaiter().GetResult(); - } - }; - return clientContext; - }); - - }) - // Let the builder know we're running in a console - .UseConsoleLifetime() - // Add services to the container - .Build(); - - await host.StartAsync(); - - #endregion - - using (var scope = host.Services.CreateScope()) - { - var configuration = scope.ServiceProvider.GetRequiredService(); - var pnpContextFactory = scope.ServiceProvider.GetRequiredService(); - var pageTransformator = scope.ServiceProvider.GetRequiredService(); - var sourceContext = scope.ServiceProvider.GetRequiredService(); - - using (var targetContext = await pnpContextFactory.CreateAsync("TargetSite")) - { - var sourcePageUri = configuration.GetValue("SourcePageUri"); - var sourceUri = new Uri(sourcePageUri); - var result = await pageTransformator.TransformSharePointAsync(sourceContext, targetContext, sourceUri); - - Console.WriteLine($"Here is the URL of the transformed page: {result}"); - } - } - } - } -} diff --git a/samples/PnP.Core.Transformation.ConsoleSample/appsettings.json b/samples/PnP.Core.Transformation.ConsoleSample/appsettings.json deleted file mode 100644 index f5891ebad6..0000000000 --- a/samples/PnP.Core.Transformation.ConsoleSample/appsettings.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "SourceSite": "https://piasysdev.sharepoint.com/sites/ClassicTest01/", - "SourcePageUri": "https://piasysdev.sharepoint.com/sites/ClassicTest01/SitePages/tourdefrance2018.aspx", - "PnPCore": { - "DisableTelemetry": "false", - "HttpRequests": { - "UserAgent": "NONISV|SharePointPnP|PnPCoreSDK", - "SharePointRest": { - "UseRetryAfterHeader": "false", - "MaxRetries": "10", - "DelayInSeconds": "3", - "UseIncrementalDelay": "true", - "DefaultPageSize": 100 - }, - "MicrosoftGraph": { - "UseRetryAfterHeader": "true", - "MaxRetries": "10", - "DelayInSeconds": "3", - "UseIncrementalDelay": "true" - } - }, - "PnPContext": { - "GraphFirst": "true", - "GraphCanUseBeta": "true", - "GraphAlwaysUseBeta": "false" - }, - "Credentials": { - "DefaultConfiguration": "CredentialManager", - "Configurations": { - "CredentialManager": { - "ClientId": "6d6aab4b-1717-4c70-8b3e-0e309c854851", - "TenantId": "6c94075a-da0a-4c6a-8411-badf652e8b53", - "CredentialManager": { - "CredentialManagerName": "PiaSysDev-Paolo" - } - }, - "X509Certificate": { - "ClientId": "6d6aab4b-1717-4c70-8b3e-0e309c854851", - "TenantId": "6c94075a-da0a-4c6a-8411-badf652e8b53", - "X509Certificate": { - "StoreName": "My", - "StoreLocation": "CurrentUser", - "Thumbprint": "45AAA6198EDCE3CAC73178CEDC0D93120F833A1D" - } - } - } - }, - "Sites": { - "TargetSite": { - "SiteUrl": "https://piasysdev.sharepoint.com/sites/ModernTargetSite01", - "AuthenticationProviderName": "CredentialManager" - } - } - }, - "Logging": { - "LogLevel": { - "Default": "Debug" - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/.gitignore b/samples/PnP.Core.Transformation.Poc/.gitignore deleted file mode 100644 index ff5b00c506..0000000000 --- a/samples/PnP.Core.Transformation.Poc/.gitignore +++ /dev/null @@ -1,264 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# Azure Functions localsettings file -local.settings.json - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -#*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/DashboardFunction.cs b/samples/PnP.Core.Transformation.Poc/DashboardFunction.cs deleted file mode 100644 index aa1c2ab72d..0000000000 --- a/samples/PnP.Core.Transformation.Poc/DashboardFunction.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - -namespace PnP.Core.Transformation.Poc -{ - public class DashboardFunction - { - private readonly IConfiguration configuration; - - public DashboardFunction(IConfiguration configuration) - { - this.configuration = configuration; - } - - [FunctionName("DashboardFunction")] - public IActionResult Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "dashboard")] HttpRequest req, - ExecutionContext context, - ILogger log) - { - - return new PhysicalFileResult(Path.Combine(context.FunctionAppDirectory, "default.html"), "text/html"); - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/Implementations/AzureQueueTransformationExecutor.cs b/samples/PnP.Core.Transformation.Poc/Implementations/AzureQueueTransformationExecutor.cs deleted file mode 100644 index 2c8349eafb..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Implementations/AzureQueueTransformationExecutor.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.WindowsAzure.Storage.Queue; -using Newtonsoft.Json; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint; - -namespace PnP.Core.Transformation.Poc.Implementations -{ - /// - /// Executor that relies on an Azure Storage Queue to process the transformations - /// - public class AzureQueueTransformationExecutor : LongRunningTransformationExecutorBase - { - public AzureQueueTransformationExecutor(IServiceProvider serviceProvider) : base(serviceProvider) - { - } - - protected override LongRunningTransformationProcessBase CreateProcess(Guid id) - { - return new AzureQueueTransformationProcessBase(id, ServiceProvider); - } - } - - /// - /// Transformation Process that relies on an Azure Storage Queue - /// - public class AzureQueueTransformationProcessBase : LongRunningTransformationProcessBase - { - private readonly CloudQueue queueReference; - - public AzureQueueTransformationProcessBase(Guid id, IServiceProvider serviceProvider) : base(id, serviceProvider) - { - var tasksQueueName = Environment.GetEnvironmentVariable("TasksQueueName"); - queueReference = serviceProvider.GetRequiredService().GetQueueReference(tasksQueueName); - } - - public override async Task StartProcessAsync(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token = default) - { - await queueReference.CreateIfNotExistsAsync(); - - await base.StartProcessAsync(sourceProvider, targetContext, token).ConfigureAwait(false); - } - - /// - /// Protected method that simply enqueues a task into an Azure Storage Queue - /// - /// The task to enqueue - /// The cancellation token, if any - /// - protected override async Task EnqueueTaskAsync(PageTransformationTask task, CancellationToken token = default) - { - var spItemId = (SharePointSourceItemId) task.SourceItemId; - - var message = new TaskQueueItem - { - ProcessId = Id, - SourcePageUri = spItemId.Uri, - TaskId = task.Id - }; - string json = JsonConvert.SerializeObject(message); - await queueReference.AddMessageAsync(new CloudQueueMessage(json), - null, null, null, null, token) - .ConfigureAwait(false); - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/Implementations/AzureTableTransformationStateManager.cs b/samples/PnP.Core.Transformation.Poc/Implementations/AzureTableTransformationStateManager.cs deleted file mode 100644 index befb09a56a..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Implementations/AzureTableTransformationStateManager.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Storage; -using Microsoft.WindowsAzure.Storage.Table; -using Newtonsoft.Json; -using PnP.Core.Transformation.Services.Core; - -namespace PnP.Core.Transformation.Poc.Implementations -{ - /// - /// State manager that relies on an Azure Table Storage to store the state items - /// - public class AzureTableTransformationStateManager : ITransformationStateManager - { - private readonly CloudTable tableReference; - - private static readonly SemaphoreSlim CreationSemaphoreSlim = new SemaphoreSlim(1, 1); - private static bool TableCreated; - - public AzureTableTransformationStateManager(CloudTableClient cloudTableClient) - { - if (cloudTableClient == null) throw new ArgumentNullException(nameof(cloudTableClient)); - - var stateTableName = Environment.GetEnvironmentVariable("StateTableName"); - tableReference = cloudTableClient.GetTableReference(stateTableName); - } - - private async Task EnsureTableAsync() - { - // Table already created - if (TableCreated) return; - - // Try to acquire the lock - await CreationSemaphoreSlim.WaitAsync(); - try - { - // Table already created - if (TableCreated) return; - - await tableReference.CreateIfNotExistsAsync(); - TableCreated = true; - } - finally - { - CreationSemaphoreSlim.Release(); - } - } - - private class StateEntity : TableEntity - { - public StateEntity() - { - - } - - public StateEntity(TransformationProcessStatus status) - { - PartitionKey = status.ProcessId.ToString(); - RowKey = status.ProcessId.ToString(); - Data = JsonConvert.SerializeObject(status); - State = status.State.ToString(); - } - - public StateEntity(TransformationProcessTaskStatus status) - { - PartitionKey = status.ProcessId.ToString(); - RowKey = status.Id.ToString(); - Data = JsonConvert.SerializeObject(status); - State = status.State.ToString(); - } - - public string Data { get; set; } - - public string State { get; set; } - - public TransformationProcessStatus GetProcessStatus() - { - return JsonConvert.DeserializeObject(Data); - } - - public TransformationProcessTaskStatus GetTaskStatus() - { - return JsonConvert.DeserializeObject(Data); - } - } - - public async Task WriteProcessStatusAsync(TransformationProcessStatus status, CancellationToken token = default) - { - await EnsureTableAsync(); - - var entity = new StateEntity(status); - - await tableReference.ExecuteAsync(TableOperation.InsertOrReplace(entity)); - } - - public async Task WriteTaskStatusAsync(TransformationProcessTaskStatus status, CancellationToken token = default) - { - await EnsureTableAsync(); - - var entity = new StateEntity(status); - - await tableReference.ExecuteAsync(TableOperation.InsertOrReplace(entity)); - } - - public async IAsyncEnumerable GetProcessTasksStatus(Guid processId, TasksStatusQuery query, CancellationToken token = default) - { - await EnsureTableAsync(); - - string partitionKey = processId.ToString(); - // Prepend the type - - var tableQuery = new TableQuery(); - - if (query.State.HasValue) - { - // Apply filter - tableQuery = tableQuery.Where($"{nameof(StateEntity.PartitionKey)} eq '{partitionKey}' and {nameof(StateEntity.State)} eq '{query.State}'"); - } - else - { - tableQuery = tableQuery.Where($"{nameof(StateEntity.PartitionKey)} eq '{partitionKey}'"); - } - - TableContinuationToken tableToken = null; - do - { - // Try to load next segment - var segment = await tableReference.ExecuteQuerySegmentedAsync(tableQuery, tableToken); - token.ThrowIfCancellationRequested(); - - foreach (var stateEntity in segment) - { - // Remove the type from the beginning of the row key - yield return stateEntity.GetTaskStatus(); - token.ThrowIfCancellationRequested(); - } - - tableToken = segment.ContinuationToken; - } while (tableToken != null); - } - - public async Task ReadProcessStatusAsync(Guid processId, CancellationToken token = default) - { - await EnsureTableAsync(); - - string rowKey = processId.ToString(); - string partitionKey = processId.ToString(); - - var result = await tableReference.ExecuteAsync(TableOperation.Retrieve(partitionKey, rowKey), null, null, token); - // Entity is available only if status code is 200 - if (result.HttpStatusCode == 200) - { - return ((StateEntity)result.Result).GetProcessStatus(); - } - - return default; - } - - public async Task ReadTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default) - { - await EnsureTableAsync(); - - string rowKey = taskId.ToString(); - string partitionKey = processId.ToString(); - - var result = await tableReference.ExecuteAsync(TableOperation.Retrieve(partitionKey, rowKey), null, null, token); - // Entity is available only if status code is 200 - if (result.HttpStatusCode == 200) - { - return ((StateEntity)result.Result).GetTaskStatus(); - } - - return default; - } - - public async Task RemoveProcessStatusAsync(Guid processId, CancellationToken token = default) - { - await EnsureTableAsync(); - - string rowKey = processId.ToString(); - string partitionKey = processId.ToString(); - var entity = new TableEntity(partitionKey, rowKey) { ETag = "*" }; - try - { - var result = await tableReference.ExecuteAsync(TableOperation.Delete(entity), null, null, token); - - return result.HttpStatusCode == 200; - } - catch (StorageException se) when (se.RequestInformation.HttpStatusCode == 404) - { - return false; - } - } - public async Task RemoveTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default) - { - await EnsureTableAsync(); - - string rowKey = taskId.ToString(); - string partitionKey = processId.ToString(); - var entity = new TableEntity(partitionKey, rowKey) { ETag = "*" }; - try - { - var result = await tableReference.ExecuteAsync(TableOperation.Delete(entity), null, null, token); - - return result.HttpStatusCode == 200; - } - catch (StorageException se) when (se.RequestInformation.HttpStatusCode == 404) - { - return false; - } - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/Implementations/SharePointConfig.cs b/samples/PnP.Core.Transformation.Poc/Implementations/SharePointConfig.cs deleted file mode 100644 index 3db5d3453f..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Implementations/SharePointConfig.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace PnP.Core.Transformation.Poc.Implementations -{ - public class SharePointConfig - { - public string Source { get; set; } - - public string Target { get; set; } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/Implementations/TaskQueueItem.cs b/samples/PnP.Core.Transformation.Poc/Implementations/TaskQueueItem.cs deleted file mode 100644 index 4626088a71..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Implementations/TaskQueueItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace PnP.Core.Transformation.Poc.Implementations -{ - public class TaskQueueItem - { - public Uri SourcePageUri { get; set; } - - public Guid TaskId { get; set; } - - public Guid ProcessId { get; set; } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.csproj b/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.csproj deleted file mode 100644 index 50e69911f7..0000000000 --- a/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.csproj +++ /dev/null @@ -1,48 +0,0 @@ - - - netcoreapp3.1 - v3 - 234de3f0-082f-42df-b3a4-bb7bcddc3909 - - - - - - - PreserveNewest - - - - - - - - - - - - - - - ..\..\src\sdk\PnP.Core\bin\Debug\netstandard2.0\PnP.Core.dll - - - ..\..\src\sdk\PnP.Core.Auth\bin\Debug\netstandard2.0\PnP.Core.Auth.dll - - - ..\..\src\sdk\PnP.Core.Transformation\bin\Debug\netstandard2.0\PnP.Core.Transformation.dll - - - ..\..\src\sdk\PnP.Core.Transformation.SharePoint\bin\Debug\netstandard2.0\PnP.Core.Transformation.SharePoint.dll - - - - - PreserveNewest - - - PreserveNewest - Never - - - diff --git a/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.sln b/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.sln deleted file mode 100644 index 0672f0cb46..0000000000 --- a/samples/PnP.Core.Transformation.Poc/PnP.Core.Transformation.Poc.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30907.101 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Transformation.Poc", "PnP.Core.Transformation.Poc.csproj", "{F3C932FB-FBAD-4352-867A-6D60942FF932}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F3C932FB-FBAD-4352-867A-6D60942FF932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3C932FB-FBAD-4352-867A-6D60942FF932}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3C932FB-FBAD-4352-867A-6D60942FF932}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3C932FB-FBAD-4352-867A-6D60942FF932}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7113DFC3-70C4-4216-8DE0-A7BBEE1B509D} - EndGlobalSection -EndGlobal diff --git a/samples/PnP.Core.Transformation.Poc/Properties/ServiceDependencies/pnpcoretransformation - Zip Deploy/profile.arm.json b/samples/PnP.Core.Transformation.Poc/Properties/ServiceDependencies/pnpcoretransformation - Zip Deploy/profile.arm.json deleted file mode 100644 index 8a239c468c..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Properties/ServiceDependencies/pnpcoretransformation - Zip Deploy/profile.arm.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_dependencyType": "function.windows.consumption" - }, - "parameters": { - "resourceGroupName": { - "type": "string", - "defaultValue": "pnpcoretransformation", - "metadata": { - "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." - } - }, - "resourceGroupLocation": { - "type": "string", - "defaultValue": "westeurope", - "metadata": { - "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." - } - }, - "resourceName": { - "type": "string", - "defaultValue": "pnpcoretransformation", - "metadata": { - "description": "Name of the main resource to be created by this template." - } - }, - "resourceLocation": { - "type": "string", - "defaultValue": "[parameters('resourceGroupLocation')]", - "metadata": { - "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." - } - } - }, - "resources": [ - { - "type": "Microsoft.Resources/resourceGroups", - "name": "[parameters('resourceGroupName')]", - "location": "[parameters('resourceGroupLocation')]", - "apiVersion": "2019-10-01" - }, - { - "type": "Microsoft.Resources/deployments", - "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", - "resourceGroup": "[parameters('resourceGroupName')]", - "apiVersion": "2019-10-01", - "dependsOn": [ - "[parameters('resourceGroupName')]" - ], - "properties": { - "mode": "Incremental", - "expressionEvaluationOptions": { - "scope": "inner" - }, - "parameters": { - "resourceGroupName": { - "value": "[parameters('resourceGroupName')]" - }, - "resourceGroupLocation": { - "value": "[parameters('resourceGroupLocation')]" - }, - "resourceName": { - "value": "[parameters('resourceName')]" - }, - "resourceLocation": { - "value": "[parameters('resourceLocation')]" - } - }, - "template": { - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceGroupName": { - "type": "string" - }, - "resourceGroupLocation": { - "type": "string" - }, - "resourceName": { - "type": "string" - }, - "resourceLocation": { - "type": "string" - } - }, - "variables": { - "storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", - "storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", - "function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" - }, - "resources": [ - { - "location": "[parameters('resourceGroupLocation')]", - "name": "[variables('storage_name')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2017-10-01", - "tags": { - "[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" - }, - "properties": { - "supportsHttpsTrafficOnly": true - }, - "sku": { - "name": "Standard_LRS" - }, - "kind": "Storage" - }, - { - "location": "[parameters('resourceLocation')]", - "name": "[parameters('resourceName')]", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "dependsOn": [ - "[variables('storage_ResourceId')]" - ], - "kind": "functionapp", - "properties": { - "name": "[parameters('resourceName')]", - "kind": "functionapp", - "httpsOnly": true, - "reserved": false - }, - "identity": { - "type": "SystemAssigned" - }, - "resources": [ - { - "name": "appsettings", - "type": "config", - "apiVersion": "2015-08-01", - "dependsOn": [ - "[variables('function_ResourceId')]" - ], - "properties": { - "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", - "WEBSITE_CONTENTSHARE": "[toLower(parameters('resourceName'))]", - "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", - "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", - "FUNCTIONS_EXTENSION_VERSION": "~3", - "FUNCTIONS_WORKER_RUNTIME": "dotnet" - } - } - ] - } - ] - } - } - } - ] -} \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.json b/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.json deleted file mode 100644 index 33703d5822..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dependencies": {} -} \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.local.json b/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.local.json deleted file mode 100644 index 33703d5822..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dependencies": {} -} \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/Startup.cs b/samples/PnP.Core.Transformation.Poc/Startup.cs deleted file mode 100644 index 4478c194fc..0000000000 --- a/samples/PnP.Core.Transformation.Poc/Startup.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Microsoft.Azure.Functions.Extensions.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ObjectPool; -using Microsoft.SharePoint.Client; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Table; -using PnP.Core.Auth; -using PnP.Core.Auth.Services.Builder.Configuration; -using PnP.Core.Services.Builder.Configuration; -using PnP.Core.Transformation.Poc; -using PnP.Core.Transformation.Poc.Implementations; - -[assembly: FunctionsStartup(typeof(Startup))] - -namespace PnP.Core.Transformation.Poc -{ - public class Startup : FunctionsStartup - { - public override void Configure(IFunctionsHostBuilder builder) - { - builder.Services.AddSingleton(p => CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage"))); - builder.Services.AddSingleton(p => - { - var account = p.GetRequiredService(); - return account.CreateCloudTableClient(); - }); - builder.Services.AddSingleton(p => - { - var account = p.GetRequiredService(); - return account.CreateCloudQueueClient(); - }); - - // Configure PnP Core and PnP Core Auth options - builder.Services.AddOptions() - .Configure((options, configuration) => configuration.GetSection("PnPCore").Bind(options)); - builder.Services.AddOptions() - .Configure((options, configuration) => configuration.GetSection("PnPCore").Bind(options)); - - // Register PnP Core and PnP Core Auth services - builder.Services.AddPnPCoreAuthentication(); - builder.Services.AddPnPCore(); - - // Register the PnP Core Auth providers - // Workaround: DryIoc (used by functions choose wrong ctor) - builder.Services.RemoveAll(); - builder.Services.RemoveAll(); - builder.Services.AddTransient(p => - new CredentialManagerAuthenticationProvider( - p.GetRequiredService>())); - builder.Services.AddTransient(p => - new X509CertificateAuthenticationProvider( - p.GetRequiredService>())); - - // Register the PnP Transformation Framework services - // for a SharePoint to SharePoint transformation - builder.Services.AddPnPSharePointTransformation() - .WithTransformationStateManager() - .WithTransformationExecutor(); - - // Register the CSOM ClientContext for the data source - builder.Services.AddTransient(p => { - - var clientContext = new ClientContext(Environment.GetEnvironmentVariable("SourceSite")); - clientContext.ExecutingWebRequest += (sender, args) => - { - var resource = $"https://{new Uri(Environment.GetEnvironmentVariable("SourceSite")).Authority}"; - - var clientId = Environment.GetEnvironmentVariable("PnPCore:Credentials:Configurations:CredentialManager:ClientId"); - var tenantId = Environment.GetEnvironmentVariable("PnPCore:Credentials:Configurations:CredentialManager:TenantId"); - var credentialManager = Environment.GetEnvironmentVariable("PnPCore:Credentials:Configurations:CredentialManager:CredentialManager:CredentialManagerName"); - - var cmap = new CredentialManagerAuthenticationProvider(clientId, tenantId, credentialManager); - if (cmap != null) - { - args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + cmap.GetAccessTokenAsync(new Uri(resource)).GetAwaiter().GetResult(); - } - }; - return clientContext; - }); - } - - - } -} \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/TransformSiteFunction.cs b/samples/PnP.Core.Transformation.Poc/TransformSiteFunction.cs deleted file mode 100644 index b7bcd88b5f..0000000000 --- a/samples/PnP.Core.Transformation.Poc/TransformSiteFunction.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using PnP.Core.Services; -using PnP.Core.Transformation.Poc.Implementations; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint; -using Microsoft.SharePoint.Client; - -namespace PnP.Core.Transformation.Poc -{ - /// - /// Function to magage the transformation of a whole site - /// - public class TransformSiteFunction - { - private readonly IPnPContextFactory pnpContextFactory; - private readonly ITransformationExecutor transformationExecutor; - private readonly ITransformationStateManager transformationStateManager; - private readonly ClientContext sourceContext; - - public TransformSiteFunction( - IPnPContextFactory pnpContextFactory, - ITransformationExecutor transformationExecutor, - ITransformationStateManager transformationStateManager, - ClientContext sourceContext) - { - this.pnpContextFactory = pnpContextFactory; - this.transformationExecutor = transformationExecutor; - this.transformationStateManager = transformationStateManager; - this.sourceContext = sourceContext; - } - - [FunctionName("TransformSite")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, - ILogger log, - CancellationToken token) - { - ITransformationProcess process; - string id; - Guid processId; - switch (req.Method) - { - // Trigger the transformation of a whole site - case "POST": - string target = "TargetSite"; - - // Create the process - process = await transformationExecutor.CreateTransformationProcessAsync(token); - - // Start to enqueue items - await process.StartProcessAsync( - pnpContextFactory, - sourceContext, - target, - token); - - return new OkObjectResult(new { process.Id }); - - // Get the status of a running transformation - case "GET": - id = req.Query["id"]; - if (!Guid.TryParse(id, out processId)) return new BadRequestResult(); - - process = await transformationExecutor.LoadTransformationProcessAsync(processId, token); - var status = await process.GetStatusAsync(token); - - return new OkObjectResult(status); - - // Cancel a running transformation - case "DELETE": - id = req.Query["id"]; - if (!Guid.TryParse(id, out processId)) return new BadRequestResult(); - - process = await transformationExecutor.LoadTransformationProcessAsync(processId, token); - await process.StopProcessAsync(token); - - return new OkResult(); - } - - return new BadRequestResult(); - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/TransformTaskFunction.cs b/samples/PnP.Core.Transformation.Poc/TransformTaskFunction.cs deleted file mode 100644 index cca2a8691f..0000000000 --- a/samples/PnP.Core.Transformation.Poc/TransformTaskFunction.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Host; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ObjectPool; -using Microsoft.SharePoint.Client; -using PnP.Core.Services; -using PnP.Core.Transformation.Poc.Implementations; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint; - -namespace PnP.Core.Transformation.Poc -{ - /// - /// Function to process a single page transformation request - /// - public class TransformTaskFunction - { - private readonly ITransformationExecutor executor; - private readonly IPnPContextFactory pnpContextFactory; - private readonly ITransformationStateManager transformationStateManager; - private readonly ClientContext sourceContext; - - public TransformTaskFunction( - ITransformationExecutor executor, - IPnPContextFactory pnpContextFactory, - ITransformationStateManager transformationStateManager, - ClientContext sourceContext) - { - this.executor = executor; - this.pnpContextFactory = pnpContextFactory; - this.transformationStateManager = transformationStateManager; - this.sourceContext = sourceContext; - } - - [FunctionName("TransformTaskFunction")] - public async Task Run([QueueTrigger("%TasksQueueName%", Connection = "AzureWebJobsStorage")] TaskQueueItem item, - ILogger log, - CancellationToken token) - { - log.LogInformation($"Processing: {item.TaskId}"); - - // Restore process info - var process = await executor.LoadTransformationProcessAsync(item.ProcessId, token); - if (!(process is LongRunningTransformationProcessBase p)) throw new NotSupportedException(); - - string target = "TargetSite"; - - // Create SharePoint target context - PnPContext targetContext = await pnpContextFactory.CreateAsync(target); - - // Configure the source item id and the data source provider - var sourceItemId = new SharePointSourceItemId(item.SourcePageUri); - var sourceProvider = new SharePointSourceProvider(sourceContext); - - // Execute the actual transformatio task - var task = new PageTransformationTask(item.TaskId, sourceProvider, sourceItemId, targetContext); - await p.ProcessTaskAsync(task, token); - } - } -} diff --git a/samples/PnP.Core.Transformation.Poc/default.html b/samples/PnP.Core.Transformation.Poc/default.html deleted file mode 100644 index 9ce3c71246..0000000000 --- a/samples/PnP.Core.Transformation.Poc/default.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - PnP.Core.Transformation.Poc - Dashboard - - -

Under construction ...

- - \ No newline at end of file diff --git a/samples/PnP.Core.Transformation.Poc/host.json b/samples/PnP.Core.Transformation.Poc/host.json deleted file mode 100644 index d172bc843c..0000000000 --- a/samples/PnP.Core.Transformation.Poc/host.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - }, - "logLevel": { - "PnP": "Information" - } - }, - "extensions": { - "queues": { - "batchSize": 1, - "maxDequeueCount": 3 - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/.editorconfig b/src/sdk/PnP.Core.Transformation.SharePoint.Test/.editorconfig deleted file mode 100644 index e5acfbb32f..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/.editorconfig +++ /dev/null @@ -1,3 +0,0 @@ - -[*.{cs,vb}] -dotnet_diagnostic.CA2007.severity=none \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/.gitignore b/src/sdk/PnP.Core.Transformation.SharePoint.Test/.gitignore deleted file mode 100644 index 4c9a757a25..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore file that specifies which test environment to load -**/env.txt - -# Ignore user specific appsettings -**/appsettings.*.json \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/HtmlMappingTests.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/HtmlMappingTests.cs deleted file mode 100644 index 729d88ac9e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/HtmlMappingTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.SharePoint.Client; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingProviders; -using PnP.Core.Transformation.Test.Utilities; - -namespace PnP.Core.Transformation.SharePoint.Test -{ - [TestClass] - public class HtmlMappingTests - { - [TestMethod] - public async Task MapHtmlAsync() - { - var services = new ServiceCollection(); - services.AddTestPnPCore(); - services.AddPnPSharePointTransformation(); - - var provider = services.BuildServiceProvider(); - - var pnpContextFactory = provider.GetRequiredService(); - var mappingProvider = provider.GetRequiredService(); - - // Prepare contexts - var sourceContext = provider.GetRequiredService(); - var targetContext = await pnpContextFactory.CreateAsync(TestCommon.TargetTestSite); - - var sourceUri = new Uri("http://site/item"); - var targetPageUri = new Uri("http://site/item"); - - SharePointSourceItem sourceItem = new SharePointSourceItem(sourceUri, sourceContext); - - // Prepare task - var task = new PageTransformationTask(new SharePointSourceProvider(sourceContext), sourceItem.Id, targetContext); - var context = new PageTransformationContext(task, sourceItem, targetPageUri); - var htmlContent = "

Norm​al

Hea​​ding1

hea​​​ding2

Hea​​ding3

​heading4​​​​

Quote

Text in bold, in italic, in underline, in red with yellow highlight

with striked, with superscript with lowerscript and with a different size

left centered

Middle centered

Right centered

spread

Indent1

Indent2

  • Bullet 1
  • Bullet2
    • Bullet 2.1
  1. Numbered1
  2. Numbered2

with a link to microsoft.com​

table centered

​​H1
​H2
​H3
​v1
​v2
​v3
​v12
​v22
​v32
​v13
​​v23
​v33




"; - - // Map html - var input = new HtmlMappingProviderInput(context, htmlContent); - var result = await mappingProvider.MapHtmlAsync(input); - - Assert.AreEqual( - "

Normal

Heading1

heading2

Heading3

heading4

Quote

Text in bold, in italic, in underline, in red with yellow highlight

with striked, with superscript with lowerscript and with a different size

left centered

Middle centered

Right centered

spread

Indent1

Indent2

  • Bullet 1
  • Bullet2
    • Bullet 2.1
  1. Numbered1
  2. Numbered2

with a link to microsoft.com

table centered

H1H2H3
v1
v2
v3
v12
v22
v32
v13
v23
v33




", - result.HtmlContent); - } - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/PnP.Core.Transformation.SharePoint.Test.csproj b/src/sdk/PnP.Core.Transformation.SharePoint.Test/PnP.Core.Transformation.SharePoint.Test.csproj deleted file mode 100644 index 642e1b98c9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/PnP.Core.Transformation.SharePoint.Test.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - net10.0 - - false - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/SetupTests.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/SetupTests.cs deleted file mode 100644 index 98520eea1d..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/SetupTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingProviders; - -namespace PnP.Core.Transformation.SharePoint.Test -{ - [TestClass] - public class SetupTests - { - - [TestMethod] - public void DefaultSharePointServices() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddPnPSharePointTransformation(); - - var provider = services.BuildServiceProvider(); - - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointTransformationDistiller)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointTargetPageUriResolver)); - - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointMetadataMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointHtmlMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointPageLayoutMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointTaxonomyMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointUserMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointUrlMappingProvider)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(SharePointWebPartMappingProvider)); - } - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/TransformationTests.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/TransformationTests.cs deleted file mode 100644 index 03c3cd1b66..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/TransformationTests.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.SharePoint.Client; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Test.Utilities; -using PnP.Core.Auth; -using PnP.Core.Transformation.SharePoint.Test.Utilities; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Test -{ - [TestClass] - public class TransformationTests - { - - [TestMethod] - public async Task SharePointTransformAsync() - { - var config = TestCommon.GetConfigurationSettings(); - - var services = new ServiceCollection(); - services.AddTestPnPCore(); - - // You can use the default settings - // services.AddPnPSharePointTransformation(); - - // Or you can provide a set of custom settings - services.AddPnPSharePointTransformation( - pnpOptions => // Global settings - { - pnpOptions.DisableTelemetry = false; - pnpOptions.PersistenceProviderConnectionString = config["PersistenceProviderConnectionString"]; - }, - pageOptions => // Target modern page creation settings - { - pageOptions.CopyPageMetadata = true; - pageOptions.KeepPageCreationModificationInformation = true; - pageOptions.PostAsNews = false; - pageOptions.PublishPage = false; - pageOptions.DisablePageComments = false; - pageOptions.KeepPageSpecificPermissions = true; - pageOptions.Overwrite = true; - pageOptions.ReplaceHomePageWithDefaultHomePage = true; - pageOptions.SetAuthorInPageHeader = true; - pageOptions.TargetPageFolder = ""; - pageOptions.TargetPageName = ""; - pageOptions.TargetPagePrefix = "Migrated_"; - pageOptions.TargetPageTakesSourcePageName = true; - }, - spOptions => // SharePoint classic source settings - { - // spOptions.WebPartMappingFile = config["WebPartMappingFile"]; - // spOptions.PageLayoutMappingFile = config["PageLayoutMappingFile"]; - spOptions.RemoveEmptySectionsAndColumns = true; - spOptions.ShouldMapUsers = true; - spOptions.HandleWikiImagesAndVideos = true; - spOptions.AddTableListImageAsImageWebPart = true; - spOptions.IncludeTitleBarWebPart = false; //Temp - there is another bug here - spOptions.MappingProperties = null; - spOptions.SkipHiddenWebParts = true; - spOptions.SkipUrlRewrite = true; - spOptions.UrlMappings = null; - spOptions.UserMappings = null; - spOptions.MappingProperties = new Dictionary() - { - { "UseCommunityScriptEditor", "true" } - }; // This creates a bug later down the line. PnP PowerShell initialises this in that usage. - } - ); - - var provider = services.BuildServiceProvider(); - - var pnpContextFactory = provider.GetRequiredService(); - var pageTransformator = provider.GetRequiredService(); - - var sourceContext = provider.GetRequiredService(); - var targetContext = await pnpContextFactory.CreateAsync(TestCommon.TargetTestSite); - var sourceUri = new Uri(config["SourceUri"]); - - var result = await pageTransformator.TransformSharePointAsync(sourceContext, targetContext, sourceUri); - - Assert.IsNotNull(result); - var expectedUri = new Uri($"{targetContext.Web.Url}/SitePages/Migrated_{sourceUri.Segments[sourceUri.Segments.Length - 1]}"); - Assert.AreEqual(expectedUri.AbsoluteUri, result.AbsoluteUri, ignoreCase: true); - } - - [TestMethod] - public async Task SharePointTransformOnPremAsync() - { - var config = TestCommon.GetConfigurationSettings(); - - var services = new ServiceCollection(); - services.AddTargetTestPnPCore(); - - // You can use the default settings - services.AddPnPSharePointTransformation(); - - // Or you can provide a set of custom settings - services.AddPnPSharePointTransformation( - pnpOptions => // Global settings - { - pnpOptions.DisableTelemetry = false; - pnpOptions.PersistenceProviderConnectionString = config["PersistenceProviderConnectionString"]; - }, - pageOptions => // Target modern page creation settings - { - pageOptions.CopyPageMetadata = true; - pageOptions.KeepPageCreationModificationInformation = true; - pageOptions.PostAsNews = false; - pageOptions.PublishPage = false; - pageOptions.DisablePageComments = false; - pageOptions.KeepPageSpecificPermissions = true; - pageOptions.Overwrite = true; - pageOptions.ReplaceHomePageWithDefaultHomePage = true; - pageOptions.SetAuthorInPageHeader = true; - pageOptions.TargetPageFolder = ""; - pageOptions.TargetPageName = ""; - pageOptions.TargetPagePrefix = "OnPrem_"; - pageOptions.TargetPageTakesSourcePageName = true; - }, - spOptions => // SharePoint classic source settings - { - // spOptions.WebPartMappingFile = config["WebPartMappingFile"]; - // spOptions.PageLayoutMappingFile = config["PageLayoutMappingFile"]; - spOptions.RemoveEmptySectionsAndColumns = true; - spOptions.ShouldMapUsers = true; - spOptions.HandleWikiImagesAndVideos = true; - spOptions.AddTableListImageAsImageWebPart = true; - spOptions.IncludeTitleBarWebPart = false; - spOptions.SkipHiddenWebParts = true; - spOptions.SkipUrlRewrite = true; - spOptions.UrlMappings = null; - spOptions.UserMappings = null; - //spOptions.MappingProperties = new Dictionary() - //{ - // { "UseCommunityScriptEditor", "true" } - //}; - } - ); - - var provider = services.BuildServiceProvider(); - - var pnpContextFactory = provider.GetRequiredService(); - var pageTransformator = provider.GetRequiredService(); - - var targetContext = await pnpContextFactory.CreateAsync(TestCommon.TargetTestSite); - var sourceUri = new Uri(config["OnPremSourceUri"]); - - var onPremCreds = TestCommon.ReadWindowsCredentialManagerEntry("OnPrem"); - var onPremAuth = new OnPremisesAuth(); - - using (var sourceContext = onPremAuth.GetOnPremisesContext(config["OnPremSourceTestSite"], onPremCreds)) - { - - var result = await pageTransformator.TransformSharePointAsync(sourceContext, targetContext, sourceUri); - Console.WriteLine(result.AbsoluteUri); - - Assert.IsNotNull(result); - var expectedUri = new Uri($"{targetContext.Web.Url}/SitePages/OnPrem_{sourceUri.Segments[sourceUri.Segments.Length - 1]}"); - Assert.AreEqual(expectedUri.AbsoluteUri, result.AbsoluteUri, ignoreCase: true); - - } - } - - [TestMethod] - public async Task InMemoryExecutorSharePointTransformAsync() - { - var services = new ServiceCollection(); - services.AddTestPnPCore(); - services.AddPnPSharePointTransformation(); - - var provider = services.BuildServiceProvider(); - - var transformationExecutor = provider.GetRequiredService(); - var pnpContextFactory = provider.GetRequiredService(); - - var sourceContext = provider.GetRequiredService(); - - var result = await transformationExecutor.TransformSharePointAsync( - pnpContextFactory, - sourceContext, - TestCommon.TargetTestSite); - - Assert.IsNotNull(result); - Assert.AreEqual(TransformationExecutionState.Completed, result.State); - } - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/UrlMappingTests.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/UrlMappingTests.cs deleted file mode 100644 index 5655a34483..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/UrlMappingTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.SharePoint.Client; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingProviders; -using PnP.Core.Transformation.Test.Utilities; - -namespace PnP.Core.Transformation.SharePoint.Test -{ - [TestClass] - public class UrlMappingTests - { - [TestMethod] - public async Task MapUrlAsync() - { - var services = new ServiceCollection(); - services.AddTestPnPCore(); - services.AddPnPSharePointTransformation(); - - var provider = services.BuildServiceProvider(); - - var pnpContextFactory = provider.GetRequiredService(); - var mappingProvider = provider.GetRequiredService(); - - // Prepare contexts - var sourceContext = new ClientContext("https://capadevtest.sharepoint.com/sites/PnPSauce"); - var targetContext = await pnpContextFactory.CreateAsync(TestCommon.TargetTestSite); - - var sourceUri = new Uri("https://capadevtest.sharepoint.com/Documents/Folder/Employee-Handbook.docx"); - var targetPageUri = new Uri("http://site/item"); - - var sourceItem = new SharePointSourceItem(sourceUri, sourceContext); - - // Prepare task - var task = new PageTransformationTask(new SharePointSourceProvider(sourceContext), sourceItem.Id, targetContext); - var context = new PageTransformationContext(task, sourceItem, targetPageUri); - - // Map url - var input = new UrlMappingProviderInput(context, sourceUri.ToString()); - var result = await mappingProvider.MapUrlAsync(input); - - Assert.AreEqual("https://capadevtest.sharepoint.com/sites/PnPSauceModern/Documents/Folder/Employee-Handbook.docx", result.Text); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/OnPremisesAuth.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/OnPremisesAuth.cs deleted file mode 100644 index db37c4caa9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/OnPremisesAuth.cs +++ /dev/null @@ -1,800 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Test.Utilities; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; - -namespace PnP.Core.Transformation.SharePoint.Test.Utilities -{ - internal class OnPremisesAuth : IDisposable - { - - /// - /// Gets an On-Premises connection - /// - /// - /// Connection credentials - /// - public ClientContext GetOnPremisesContext(string siteUrl, ICredentials credentials) - { - ClientContext clientContext = new ClientContext(siteUrl) - { - DisableReturnValueCache = true, - Credentials = credentials - }; - - ConfigureOnPremisesContext(siteUrl, clientContext); - - return clientContext; - } - - /// - /// Gets an On-Premises connection - /// - /// - /// - public ClientContext GetOnPremisesContext(string siteUrl) - { - ClientContext clientContext = new ClientContext(siteUrl) - { - DisableReturnValueCache = true, - Credentials = CredentialCache.DefaultNetworkCredentials - }; - - ConfigureOnPremisesContext(siteUrl, clientContext); - - return clientContext; - } - - - /* - * This code is taken from PnP.Framework.AuthenticationManager to allow establishing connections to - * on-premises servers. - */ - - internal void ConfigureOnPremisesContext(string siteUrl, ClientContext clientContext) - { - clientContext.ExecutingWebRequest += (sender, webRequestEventArgs) => - { - // CSOM for .NET Standard 2.0 is not sending along credentials for an on-premises request, so ensure - // credentials and request digest are in place. This will make CSOM for .NET Standard work for - // SharePoint 2013, 2016 and 2019. For SharePoint 2010 this does not work as the generated CSOM request - // contains references to version 15 while 2010 expects version 14. - // - // Note: the "onpremises" part of AuthenticationManager internal by design as it's only intended to be - // used by transformation tech that needs to get data from on-premises. PnP Framework, nor PnP - // PowerShell do support SharePoint on-premises. - webRequestEventArgs.WebRequestExecutor.WebRequest.Credentials = (sender as ClientContext).Credentials; - // CSOM for .NET Standard does not handle request digest management, a POST to client.svc requires a digest, so ensuring that - webRequestEventArgs.WebRequestExecutor.WebRequest.Headers.Add("X-RequestDigest", (sender as ClientContext).GetOnPremisesRequestDigestAsync().GetAwaiter().GetResult()); - // Add Request Header to force Windows Authentication which avoids an issue if multiple authentication providers are enabled on a webapplication - webRequestEventArgs.WebRequestExecutor.RequestHeaders["X-FORMS_BASED_AUTH_ACCEPTED"] = "f"; - }; - - ClientContextSettings clientContextSettings = new ClientContextSettings() - { - Type = ClientContextType.OnPremises, - SiteUrl = siteUrl, - AuthenticationManager = null - }; - - clientContext.AddContextSettings(clientContextSettings); - } - - - ///// - ///// Returns the request digest from the current session/site - ///// - ///// - ///// - //private static async Task<(string digestToken, DateTime expiresOn)> GetRequestDigestInfoAsync(ClientContext context) - //{ - // await new SynchronizationContextRemover(); - - // string responseString = string.Empty; - // var accessToken = context.GetAccessToken(); - - // context.Web.EnsureProperty(w => w.Url); - - // var httpClient = PnPHttpClient.Instance.GetHttpClient(); - - // string requestUrl = String.Format("{0}/_api/contextinfo", context.Url); - // using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) - // { - // request.Headers.Add("accept", "application/json;odata=nometadata"); - // if (!string.IsNullOrEmpty(accessToken)) - // { - // request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); - // } - - // HttpResponseMessage response = await httpClient.SendAsync(request); - - // if (response.IsSuccessStatusCode) - // { - // responseString = await response.Content.ReadAsStringAsync(); - // } - // else - // { - // var errorSb = new System.Text.StringBuilder(); - - // errorSb.AppendLine(await response.Content.ReadAsStringAsync()); - // if (response.Headers.Contains("SPRequestGuid")) - // { - // var values = response.Headers.GetValues("SPRequestGuid"); - // if (values != null) - // { - // var spRequestGuid = values.FirstOrDefault(); - // errorSb.AppendLine($"ServerErrorTraceCorrelationId: {spRequestGuid}"); - // } - // } - - // throw new Exception(errorSb.ToString()); - // } - // } - // var contextInformation = JsonSerializer.Deserialize(responseString); - - // string formDigestValue = contextInformation.GetProperty("FormDigestValue").GetString(); - // int expiresIn = contextInformation.GetProperty("FormDigestTimeoutSeconds").GetInt32(); - // return (formDigestValue, DateTime.Now.AddSeconds(expiresIn - 30)); - //} - - - /// - /// called when disposing the object - /// - /// - protected virtual void Dispose(bool disposing) - { - // For backwards compatibility - } - - /// - /// Dispose the object - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } - - internal static class OnPremisesAuthExtensions - { - private static ConcurrentDictionary requestDigestInfos = new ConcurrentDictionary(); - - internal static async Task GetOnPremisesRequestDigestAsync(this ClientContext context) - { - var hostUrl = context.Url; - if (requestDigestInfos.TryGetValue(hostUrl, out (string digestToken, DateTime expiresOn) requestDigestInfo)) - { - // We only have to add a request digest when running in dotnet core - if (DateTime.Now > requestDigestInfo.expiresOn) - { - requestDigestInfo = await GetOnPremisesRequestDigestInfoAsync(context); - requestDigestInfos.AddOrUpdate(hostUrl, requestDigestInfo, (key, oldValue) => requestDigestInfo); - } - } - else - { - // admin url maybe? - requestDigestInfo = await GetOnPremisesRequestDigestInfoAsync(context); - requestDigestInfos.AddOrUpdate(hostUrl, requestDigestInfo, (key, oldValue) => requestDigestInfo); - } - return requestDigestInfo.digestToken; - } - - private static async Task<(string digestToken, DateTime expiresOn)> GetOnPremisesRequestDigestInfoAsync(ClientContext context) - { - await new SynchronizationContextRemover(); - - string responseString = string.Empty; - - string requestUrl = $"{context.Url}/_vti_bin/sites.asmx"; - - StringContent content = new StringContent(""); - // Remove the default Content-Type content header - if (content.Headers.Contains("Content-Type")) - { - content.Headers.Remove("Content-Type"); - } - // Add the batch Content-Type header - content.Headers.Add($"Content-Type", "text/xml"); - content.Headers.Add("SOAPAction", "http://schemas.microsoft.com/sharepoint/soap/GetUpdatedFormDigestInformation"); - content.Headers.Add("X-RequestForceAuthentication", "true"); - - using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) - { - request.Content = content; - -#pragma warning disable CA2000 // Dispose objects before losing scope - var httpClient = PnPHttpClient.Instance.GetHttpClient(context); -#pragma warning restore CA2000 // Dispose objects before losing scope - - //Note: no credentials are passed here because the returned http context uses an already correctly configured handler - - HttpResponseMessage response = await httpClient.SendAsync(request); - - if (response.IsSuccessStatusCode) - { - responseString = await response.Content.ReadAsStringAsync(); - } - else - { - var errorSb = new System.Text.StringBuilder(); - - errorSb.AppendLine(await response.Content.ReadAsStringAsync()); - if (response.Headers.Contains("SPRequestGuid")) - { - var values = response.Headers.GetValues("SPRequestGuid"); - if (values != null) - { - var spRequestGuid = values.FirstOrDefault(); - errorSb.AppendLine($"ServerErrorTraceCorrelationId: {spRequestGuid}"); - } - } - - throw new Exception(errorSb.ToString()); - } - } - - XmlDocument xd = new XmlDocument(); - xd.LoadXml(responseString); - - XmlNamespaceManager nsmgr = new XmlNamespaceManager(xd.NameTable); - nsmgr.AddNamespace("soap", "http://schemas.microsoft.com/sharepoint/soap/"); - XmlNode digestNode = xd.SelectSingleNode("//soap:DigestValue", nsmgr); - if (digestNode != null) - { - XmlNode timeOutNode = xd.SelectSingleNode("//soap:TimeoutSeconds", nsmgr); - int expiresIn = int.Parse(timeOutNode.InnerText); - return (digestNode.InnerText, DateTime.Now.AddSeconds(expiresIn - 30)); - } - else - { - throw new Exception("No digest found!"); - } - } - - - /// - /// Create a new HTTP request by copying previous HTTP request's headers and properties from response's request message. - /// Copied from: https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/blob/dev/src/Microsoft.Graph.Core/Extensions/HttpRequestMessageExtensions.cs - /// - /// The previous needs to be copy. - /// The . - /// - /// Re-issue a new HTTP request with the previous request's headers and properities - /// - internal static async Task CloneAsync(this HttpRequestMessage originalRequest) - { - var newRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri); - - // Copy request headers. - foreach (var header in originalRequest.Headers) - newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); - - // Copy request properties. -#pragma warning disable CS0618 // Type or member is obsolete - foreach (var property in originalRequest.Properties) - { - newRequest.Properties.Add(property); - } -#pragma warning restore CS0618 // Type or member is obsolete - - // Set Content if previous request had one. - if (originalRequest.Content != null) - { - // HttpClient doesn't rewind streams and we have to explicitly do so. -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler - await originalRequest.Content.ReadAsStreamAsync().ContinueWith(t => - { - if (t.Result.CanSeek) - { - t.Result.Seek(0, SeekOrigin.Begin); - } - - newRequest.Content = new StreamContent(t.Result); - }).ConfigureAwait(false); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler - - // Copy content headers. - if (originalRequest.Content.Headers != null) - foreach (var contentHeader in originalRequest.Content.Headers) - newRequest.Content.Headers.TryAddWithoutValidation(contentHeader.Key, contentHeader.Value); - } - - return newRequest; - } - } - - public class ClientContextSettings - { - #region properties - // Generic - public ClientContextType Type { get; set; } - public string SiteUrl { get; set; } - public AuthenticationManager AuthenticationManager { get; set; } - - // User name + password flows - internal string UserName { get; set; } - internal string Password { get; set; } - - // App Only flows - internal string ClientId { get; set; } - internal string ClientSecret { get; set; } - internal string Realm { get; set; } - internal string AcsHostUrl { get; set; } - internal string GlobalEndPointPrefix { get; set; } - internal string Tenant { get; set; } - #endregion - - #region methods - internal bool UsesDifferentAudience(string newSiteUrl) - { - Uri newAudience = new Uri(newSiteUrl); - Uri currentAudience = new Uri(this.SiteUrl); - - if (newAudience.Host != currentAudience.Host) - { - return true; - } - else - { - return false; - } - } - #endregion - - - } - - public class PnPHttpClient - { - //private static Configuration configuration; - private const string PnPHttpClientName = "PnPHttpClient"; - private static readonly Lazy _lazyInstance = new Lazy(() => new PnPHttpClient(), true); - private ServiceProvider serviceProvider; - private static readonly ConcurrentDictionary credentialsHttpClients = new ConcurrentDictionary(); - - private PnPHttpClient() - { - BuildServiceFactory(); - } - - public static PnPHttpClient Instance - { - get - { - return _lazyInstance.Value; - } - } - - public HttpClient GetHttpClient(ClientContext context) - { - var factory = serviceProvider.GetRequiredService(); - - if (context.Credentials is NetworkCredential networkCredential) - { - string cacheKey = networkCredential.UserName; - - if (string.IsNullOrEmpty(cacheKey)) - { - cacheKey = CredentialCache.DefaultNetworkCredentials.UserName; - } - - // The HttpClientHandler is the one managing the network connections and holds the resources and as - // such we're caching this one for on-prem usage scenarions (for page transformation) - if (credentialsHttpClients.TryGetValue(cacheKey, out HttpClientHandler cachedHttpHandler)) - { - // No need to dispose HttpClient, the IDisposable is purely there to trigger the - // dispose of the created HttpClientHandler - return new HttpClient(cachedHttpHandler); - } - else - { - // Create a new handler, do not dispose it since we're caching it - var handler = new HttpClientHandler - { - Credentials = context.Credentials - }; - - credentialsHttpClients.TryAdd(cacheKey, handler); - - // No need to dispose HttpClient, the IDisposable is purely there to trigger the - // dispose of the created HttpClientHandler - return new HttpClient(handler); - } - } - else - { - // Let the HttpClientFactory handle things - return factory.CreateClient(PnPHttpClientName); - } - } - - public HttpClient GetHttpClient() - { - var factory = serviceProvider.GetRequiredService(); - return factory.CreateClient(PnPHttpClientName); - } - - //public static async Task AuthenticateRequestAsync(HttpRequestMessage request, ClientContext context) - //{ - // var accessToken = context.GetAccessToken(); - - // if (!string.IsNullOrEmpty(accessToken)) - // { - // request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); - // } - // else - // { - // var cookieContainer = context.GetAuthenticationCookies(); - // if (cookieContainer != null) - // { - // request.Headers.Add("Cookie", cookieContainer.GetCookieHeader(new Uri(context.Url))); - // if (request.Method != HttpMethod.Get) - // { - // request.Headers.Add("X-RequestDigest", await context.GetRequestDigestAsync(cookieContainer).ConfigureAwait(false)); - // } - // } - // else if (context.Credentials is NetworkCredential) - // { - // // No need to add credentials as these are already provided via the selected HttpClient/HttpClientHandler - // if (request.Method != HttpMethod.Get) - // { - // request.Headers.Add("X-RequestDigest", await context.GetOnPremisesRequestDigestAsync().ConfigureAwait(false)); - // } - // } - // } - //} - - public static void AuthenticateRequest(HttpRequestMessage request, string accessToken) - { - if (!string.IsNullOrEmpty(accessToken)) - { - request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); - } - } - - private void BuildServiceFactory() - { - // Use TLS 1.2 as default connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - - // Create container - var serviceCollection = new ServiceCollection(); - - // Add http handlers - AddHttpHandlers(serviceCollection); - - // get User Agent String - string userAgentFromConfig = null; - try - { - userAgentFromConfig = "PnP Testing Transformation"; - } - catch // throws exception if being called from a .NET Standard 2.0 application - { - - } - if (string.IsNullOrWhiteSpace(userAgentFromConfig)) - { - userAgentFromConfig = Environment.GetEnvironmentVariable("SharePointPnPUserAgent", EnvironmentVariableTarget.Process); - } - - // Add http clients - AddHttpClients(serviceCollection, userAgentFromConfig); - - // Build the container - serviceProvider = serviceCollection.BuildServiceProvider(); - } - - private static TimeSpan GetHttpTimeout() - { - // get User Agent String - string httpTimeOutValue = null; - - if (string.IsNullOrWhiteSpace(httpTimeOutValue)) - { - httpTimeOutValue = Environment.GetEnvironmentVariable("SharePointPnPHttpTimeout", EnvironmentVariableTarget.Process); - } - - if (int.TryParse(httpTimeOutValue, out int httpTimeout)) - { - if (httpTimeout == -1) - { - return Timeout.InfiniteTimeSpan; - } - else - { - return new TimeSpan(0, 0, httpTimeout); - } - } - - // Return default value of 100 seconds - return new TimeSpan(0, 0, 100); - } - - private static IServiceCollection AddHttpClients(IServiceCollection collection, string UserAgent = null) - { - collection.AddHttpClient(PnPHttpClientName, config => - { - config.Timeout = GetHttpTimeout(); - - if (string.IsNullOrWhiteSpace(UserAgent)) - { - config.DefaultRequestHeaders.UserAgent.TryParseAdd("PnP Testing Transformation"); - } - else - { - config.DefaultRequestHeaders.UserAgent.TryParseAdd(UserAgent); - } - }) - .AddHttpMessageHandler() - // We use cookies by adding them to the header which works great when used from Core framework, - // however when running the .NET Standard 2.0 version from .NET Framework we explicetely have to - // tell the http client to not use the default (empty) cookie container - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() - { - UseCookies = false - }); - - return collection; - } - - private static IServiceCollection AddHttpHandlers(IServiceCollection collection) - { - // Use transient for the DelegatingHandlers - // https://stackoverflow.com/questions/53223411/httpclient-delegatinghandler-unexpected-life-cycle - collection.AddTransient(); - - return collection; - } - } - - public enum ClientContextType - { - SharePointACSAppOnly = 0, - AzureADCredentials = 1, - AzureADCertificate = 2, - Cookie = 3, - AzureADInteractive = 4, - AzureOnBehalfOf = 5, - DeviceLogin = 6, - OnPremises = 7, - AccessToken = 8, - PnPCoreSdk = 9 - } - - /// - /// Retry handler for http requests - /// Based upon: https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/blob/dev/src/Microsoft.Graph.Core/Extensions/HttpRequestMessageExtensions.cs - /// - internal class RetryHandler : DelegatingHandler - { - private const string RETRY_AFTER = "Retry-After"; - private const string RETRY_ATTEMPT = "Retry-Attempt"; - internal const int MAXDELAY = 300; - - #region Construction - public RetryHandler() - { - } - #endregion - - internal bool UseRetryAfterHeader { get; set; } = true; - internal int MaxRetries { get; set; } = 10; - internal int DelayInSeconds { get; set; } = 1; - internal bool IncrementalDelay { get; set; } = true; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposing will prevent cloning of the request needed for the retry")] - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - int retryCount = 0; - - while (true) - { - HttpResponseMessage response = null; - - try - { - response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - - if (!ShouldRetry(response.StatusCode)) - { - return response; - } - - if (retryCount >= MaxRetries) - { - // Drain response content to free connections. Need to perform this - // before retry attempt and before the TooManyRetries ServiceException. - if (response.Content != null) - { -#if NET5_0_OR_GREATER - await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); -#else - await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); -#endif - } - - throw new Exception($"Too many http request retries: {retryCount}"); - } - } - catch (Exception ex) - { - // Find innermost exception and check if it is a SocketException - Exception innermostEx = ex; - - while (innermostEx.InnerException != null) innermostEx = innermostEx.InnerException; - if (!(innermostEx is SocketException)) - { - throw; - } - - if (retryCount >= MaxRetries) - { - throw; - } - } - - // Drain response content to free connections. Need to perform this - // before retry attempt and before the TooManyRetries ServiceException. - if (response?.Content != null) - { -#if NET5_0_OR_GREATER - await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); -#else - await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); -#endif - } - - // Call Delay method to get delay time from response's Retry-After header or by exponential backoff - Task delay = Delay(response, retryCount, DelayInSeconds, cancellationToken); - - // general clone request with internal CloneAsync (see CloneAsync for details) extension method - // do not dispose this request as that breaks the request cloning - request = await request.CloneAsync().ConfigureAwait(false); - // Increase retryCount and then update Retry-Attempt in request header if needed - retryCount++; - AddOrUpdateRetryAttempt(request, retryCount); - - // Delay time - await delay.ConfigureAwait(false); - } - } - - /// - /// Update Retry-Attempt header in the HTTP request - /// - /// The needs to be sent. - /// Retry times - private void AddOrUpdateRetryAttempt(HttpRequestMessage request, int retryCount) - { - if (UseRetryAfterHeader) - { - if (request.Headers.Contains(RETRY_ATTEMPT)) - { - request.Headers.Remove(RETRY_ATTEMPT); - } - request.Headers.Add(RETRY_ATTEMPT, retryCount.ToString()); - } - } - - private Task Delay(HttpResponseMessage response, int retryCount, int delay, CancellationToken cancellationToken) - { - double delayInSeconds = delay; - - if (UseRetryAfterHeader && response != null && response.Headers.TryGetValues(RETRY_AFTER, out IEnumerable values)) - { - // Can we use the provided retry-after header? - string retryAfter = values.First(); - if (Int32.TryParse(retryAfter, out int delaySeconds)) - { - delayInSeconds = delaySeconds; - } - } - else - { - // Custom delay - if (IncrementalDelay) - { - // Incremental delay, the wait time between each delay exponentially gets bigger - double power = Math.Pow(2, retryCount); - delayInSeconds = power * delay; - } - else - { - // Linear delay - delayInSeconds = delay; - } - } - - // If the delay goes beyond our max wait time for a delay then cap it - TimeSpan delayTimeSpan = TimeSpan.FromSeconds(Math.Min(delayInSeconds, RetryHandler.MAXDELAY)); - - return Task.Delay(delayTimeSpan, cancellationToken); - } - - internal static bool ShouldRetry(HttpStatusCode statusCode) - { - return (statusCode == HttpStatusCode.ServiceUnavailable || - statusCode == HttpStatusCode.GatewayTimeout || - statusCode == (HttpStatusCode)429); - } - } - - /// - /// Based upon https://blogs.msdn.microsoft.com/benwilli/2017/02/09/an-alternative-to-configureawaitfalse-everywhere/ - /// - public struct SynchronizationContextRemover : INotifyCompletion - { - public bool IsCompleted - { - get { return SynchronizationContext.Current == null; } - } - - public void OnCompleted(Action continuation) - { - var prevContext = SynchronizationContext.Current; - try - { - SynchronizationContext.SetSynchronizationContext(null); - continuation(); - } - finally - { - SynchronizationContext.SetSynchronizationContext(prevContext); - } - } - - public SynchronizationContextRemover GetAwaiter() - { - return this; - } - - public void GetResult() - { - } - } -} - - -namespace Microsoft.SharePoint.Client -{ - /// - /// Class that holds the extension methods used to "tag" a client context for cloning support - /// - public static partial class InternalClientContextExtensions - { - private const string PnPSettingsKey = "SharePointPnP$Settings$ContextCloning"; - - public static void AddContextSettings(this ClientRuntimeContext clientContext, ClientContextSettings contextData) - { - clientContext.StaticObjects[PnPSettingsKey] = contextData; - } - - public static ClientContextSettings GetContextSettings(this ClientRuntimeContext clientContext) - { - if (!clientContext.StaticObjects.TryGetValue(PnPSettingsKey, out object settingsObject)) - { - return null; - } - - return (ClientContextSettings)settingsObject; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/TestCommon.cs b/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/TestCommon.cs deleted file mode 100644 index ada4b783fd..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/Utilities/TestCommon.cs +++ /dev/null @@ -1,339 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using System.Security; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.SharePoint.Client; -using Microsoft.Win32.SafeHandles; -using PnP.Core.Auth; -using PnP.Core.Auth.Services.Builder.Configuration; -using PnP.Core.Services.Builder.Configuration; -using File = System.IO.File; - -namespace PnP.Core.Transformation.Test.Utilities -{ - public static class TestCommon - { - - /// - /// Name of the default test target site configuration - /// - internal static string TargetTestSite => "TargetTestSite"; - - public static IConfigurationRoot GetConfigurationSettings() - { - // Define the test environment by: - // - Copying env.sample to env.txt - // - Putting the test environment name in env.txt ==> this should be same name as used in your settings file: - // When using appsettings.mine.json then you need to put mine as content in env.txt - var environmentName = LoadTestEnvironment(); - - if (string.IsNullOrEmpty(environmentName)) - { - throw new Exception("Please ensure you've a env.txt file in the root of the test project. This file should contain the name of the test environment you want to use."); - } - - // The settings file is stored in the root of the test project, no need to configure the file to be copied over the bin folder - var jsonSettingsFile = Path.GetFullPath($"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}appsettings.{environmentName}.json"); - - var configuration = new ConfigurationBuilder() - .AddJsonFile(jsonSettingsFile, optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .Build(); - - return configuration; - } - - public static IServiceProvider AddTestPnPCore(this IServiceCollection services) - { - var configuration = GetConfigurationSettings(); - - services - // Configuration - .AddScoped(_ => configuration) - // Logging service, get config from appsettings + add debug output handler - .AddLogging(configure => - { - configure.AddConfiguration(configuration.GetSection("Logging")); - configure.AddDebug(); - }) - // Add the PnP Core SDK library services configuration from the appsettings.json file - .Configure(configuration.GetSection("PnPCore")) - .Configure(configuration.GetSection("PnPCore")) - .AddPnPCoreAuthentication() - // Add the PnP Core SDK Authentication Providers - .AddPnPCore(); - - // The default configuration has to use credential manager for auth - - var defaultConfiguration = configuration.GetSection("PnPCore:Credentials:DefaultConfiguration")?.Value; - - var clientId = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:ClientId")?.Value; - var tenantId = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:TenantId")?.Value; - var credentialManager = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:CredentialManager:CredentialManagerName")?.Value; - - var resource = $"https://{new Uri(configuration["SourceTestSite"]).Authority}"; - - var cmap = new CredentialManagerAuthenticationProvider(clientId, tenantId, credentialManager); - var accessToken = cmap.GetAccessTokenAsync(new Uri(resource)).GetAwaiter().GetResult(); - - services.AddTransient(p => { - var clientContext = new ClientContext(configuration["SourceTestSite"]); - clientContext.ExecutingWebRequest += (sender, args) => - { - - if (cmap != null) - { - args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken; - } - }; - return clientContext; - }); - - return services.BuildServiceProvider(); - } - - public static IServiceProvider AddTargetTestPnPCore(this IServiceCollection services) - { - var configuration = GetConfigurationSettings(); - - services - // Configuration - .AddScoped(_ => configuration) - // Logging service, get config from appsettings + add debug output handler - .AddLogging(configure => - { - configure.AddConfiguration(configuration.GetSection("Logging")); - configure.AddDebug(); - }) - // Add the PnP Core SDK library services configuration from the appsettings.json file - .Configure(configuration.GetSection("PnPCore")) - .Configure(configuration.GetSection("PnPCore")) - .AddPnPCoreAuthentication() - // Add the PnP Core SDK Authentication Providers - .AddPnPCore(); - - // The default configuration has to use credential manager for auth - - var defaultConfiguration = configuration.GetSection("PnPCore:Credentials:DefaultConfiguration")?.Value; - - var clientId = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:ClientId")?.Value; - var tenantId = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:TenantId")?.Value; - var credentialManager = configuration.GetSection($"PnPCore:Credentials:Configurations:{defaultConfiguration}:CredentialManager:CredentialManagerName")?.Value; - - var resource = $"https://{new Uri(configuration["TargetTestSite"]).Authority}"; - - var cmap = new CredentialManagerAuthenticationProvider(clientId, tenantId, credentialManager); - var accessToken = cmap.GetAccessTokenAsync(new Uri(resource)).GetAwaiter().GetResult(); - - services.AddTransient(p => { - var clientContext = new ClientContext(configuration["TargetTestSite"]); - clientContext.ExecutingWebRequest += (sender, args) => - { - - if (cmap != null) - { - args.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken; - } - }; - return clientContext; - }); - - return services.BuildServiceProvider(); - } - - private static string LoadTestEnvironment() - { - // Detect if we're running in a github workflow - if (RunningInGitHubWorkflow()) - { - return "ci"; - } - else - { - string testEnvironmentFile = "..\\..\\..\\env.txt"; - if (File.Exists(testEnvironmentFile)) - { - string content = File.ReadAllText(testEnvironmentFile); - if (!string.IsNullOrEmpty(content)) - { - return content.Trim(); - } - } - - return null; - } - } - - - internal static bool RunningInGitHubWorkflow() - { - var runningInCI = Environment.GetEnvironmentVariable("CI"); - if (!string.IsNullOrEmpty(runningInCI)) - { - return true; - } - else - { - return false; - } - } - - internal const string PnPCoreSDKTestPrefix = "PNP_SDK_TEST_"; - internal static string GetPnPSdkTestAssetName(string name) - { - return name.StartsWith(PnPCoreSDKTestPrefix) ? name : $"{PnPCoreSDKTestPrefix}{name}"; - } - - - - - /* - * Temporary copy of the credential read from the auth assembly. - * This project needs to be signed inorder to allow this project to - * acccess internal classes. - * - */ - - - internal static NetworkCredential ReadWindowsCredentialManagerEntry(string applicationName) - { - - bool success = CredRead(applicationName, CRED_TYPE.GENERIC, 0, out IntPtr credPtr); - if (success) - { -#pragma warning disable CA2000 // Dispose objects before losing scope - var critCred = new CriticalCredentialHandle(credPtr); -#pragma warning restore CA2000 // Dispose objects before losing scope - var cred = critCred.GetCredential(); - var username = cred.UserName; - var securePassword = cred.CredentialBlob.ToSecureString(); - return new NetworkCredential(username, securePassword); - } - return null; - } - - - // TEMP - #region UNMANAGED - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct NativeCredential - { - public uint Flags; - public CRED_TYPE Type; - public IntPtr TargetName; - public IntPtr Comment; - public FILETIME LastWritten; - public uint CredentialBlobSize; - public IntPtr CredentialBlob; - public uint Persist; - public uint AttributeCount; - public IntPtr Attributes; - public IntPtr TargetAlias; - public IntPtr UserName; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct Credential - { - public uint Flags; - public CRED_TYPE Type; - public string TargetName; - public string Comment; - public FILETIME LastWritten; - public uint CredentialBlobSize; - public string CredentialBlob; - public uint Persist; - public uint AttributeCount; - public IntPtr Attributes; - public string TargetAlias; - public string UserName; - } - - internal enum CRED_TYPE : uint - { - GENERIC = 1, - DOMAIN_PASSWORD = 2, - DOMAIN_CERTIFICATE = 3, - DOMAIN_VISIBLE_PASSWORD = 4, - GENERIC_CERTIFICATE = 5, - DOMAIN_EXTENDED = 6, - MAXIMUM = 7, // Maximum supported cred type - MAXIMUM_EX = (MAXIMUM + 1000), // Allow new applications to run on old OSes - } - - internal class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid - { - public CriticalCredentialHandle(IntPtr preexistingHandle) - { - SetHandle(preexistingHandle); - } - - public Credential GetCredential() - { - if (!IsInvalid) - { - NativeCredential ncred = (NativeCredential)Marshal.PtrToStructure(handle, - typeof(NativeCredential)); - Credential cred = new Credential - { - CredentialBlobSize = ncred.CredentialBlobSize, - CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, - (int)ncred.CredentialBlobSize / 2), - UserName = Marshal.PtrToStringUni(ncred.UserName), - TargetName = Marshal.PtrToStringUni(ncred.TargetName), - TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias), - Type = ncred.Type, - Flags = ncred.Flags, - Persist = ncred.Persist - }; - return cred; - } - else - { - throw new InvalidOperationException("Invalid CriticalHandle!"); - } - } - - override protected bool ReleaseHandle() - { - if (!IsInvalid) - { - CredFree(handle); - SetHandleAsInvalid(); - return true; - } - return false; - } - } - - - - [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)] - private static extern bool CredWrite([In] ref NativeCredential userCredential, [In] uint flags); - - [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr CredentialPtr); - - [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] - private static extern bool CredFree([In] IntPtr cred); - - [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern bool CredDelete(string target, CRED_TYPE type, int reservedFlag); - #endregion - - /// - /// Converts a string to a SecureString - /// - /// String to convert - /// SecureString representation of the passed in string - internal static SecureString ToSecureString(this string input) - { - return new NetworkCredential("", input).SecurePassword; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/appsettings.copyme.json b/src/sdk/PnP.Core.Transformation.SharePoint.Test/appsettings.copyme.json deleted file mode 100644 index 2604b62b4a..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/appsettings.copyme.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "SourceTestSite": "https://contoso.sharepoint.com/sites/ClassicSite/", - "SourceUri": "https://contoso.sharepoint.com/sites/ClassicSite/SitePages/ClassicPage.aspx", - - // On-Premises - "OnPremSourceTestSite": "http://contoso/sites/teamsite", - "OnPremSourceUri": "http://contoso/sites/teamsite/SitePages/WebPartPage.aspx", - - "PersistenceProviderConnectionString": "c:\\temp", - "WebPartMappingFile": "c:\\github\\pnpcore\\src\\sdk\\PnP.Core.Transformation.SharePoint\\MappingFiles\\webpartmapping.xml", - "PageLayoutMappingFile": "C:\\github\\pnpcore\\src\\sdk\\PnP.Core.Transformation.SharePoint\\MappingFiles\\pagelayoutmapping.xml", - "PnPCore": { - "DisableTelemetry": "false", - "HttpRequests": { - "UserAgent": "NONISV|SharePointPnP|PnPCoreSDK", - "SharePointRest": { - "UseRetryAfterHeader": "false", - "MaxRetries": "10", - "DelayInSeconds": "3", - "UseIncrementalDelay": "true", - "DefaultPageSize": 100 - }, - "MicrosoftGraph": { - "UseRetryAfterHeader": "true", - "MaxRetries": "10", - "DelayInSeconds": "3", - "UseIncrementalDelay": "true" - } - }, - "PnPContext": { - "GraphFirst": "true", - "GraphCanUseBeta": "true", - "GraphAlwaysUseBeta": "false" - }, - "Credentials": { - "DefaultConfiguration": "CredentialManager", - "Configurations": { - "CredentialManager": { - "CredentialManager": { - "CredentialManagerName": "credman" - } - } - } - }, - "Sites": { - "TargetTestSite": { - "SiteUrl": "https://contoso.sharepoint.com/sites/ModernSite", - "AuthenticationProviderName": "CredentialManager" - } - } - }, - "Logging": { - "LogLevel": { - "Default": "Debug" - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint.Test/env.sample b/src/sdk/PnP.Core.Transformation.SharePoint.Test/env.sample deleted file mode 100644 index 21f5b6984c..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint.Test/env.sample +++ /dev/null @@ -1 +0,0 @@ -copyme \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/AssemblyInfo.cs b/src/sdk/PnP.Core.Transformation.SharePoint/AssemblyInfo.cs deleted file mode 100644 index 2fcad0f87c..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Resources; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("PnP.Core.Transformation.SharePoint.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] - -[assembly: NeutralResourcesLanguage("en")] \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/AngleSharpExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/AngleSharpExtensions.cs deleted file mode 100644 index 149cd36b89..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/AngleSharpExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace AngleSharp.Dom -{ - - /// - /// Extension methods for AngleSharp - /// - internal static partial class AngleSharpExtensions - { - /// - /// Performs a partial match on a list of tokens (e.g. classes on an element) - /// - /// List of tokens to search in - /// Partial token to match via an StartsWidth - /// First matching token if found, null if no match - public static string PartialMatch(this ITokenList tokenList, string filter) - { - // No tokens then bail out - if (tokenList.Length == 0) - { - return null; - } - - foreach (var token in tokenList) - { - if (token.StartsWith(filter, StringComparison.InvariantCultureIgnoreCase)) - { - return token; - } - } - - return null; - } - - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientContextExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientContextExtensions.cs deleted file mode 100644 index c346dd4503..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientContextExtensions.cs +++ /dev/null @@ -1,551 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Model; -using System; -using System.Collections.Concurrent; -using System.Configuration; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - /// - /// Class that deals with cloning client context object, getting access token and validates server version - /// - internal static partial class ClientContextExtensions - { - private const string PnPSettingsKey = "SharePointPnP$Settings$ContextCloning"; - - //private static ILoggerFactory loggerFactory; - //private static ILogger log; - private static readonly string userAgentFromConfig; - -#pragma warning disable CS0169 - private static ConcurrentDictionary requestDigestInfos = new ConcurrentDictionary(); -#pragma warning restore CS0169 - - //private static bool hasAuthCookies; - - /// - /// Static constructor, only executed once per class load - /// - static ClientContextExtensions() - { - try - { - ClientContextExtensions.userAgentFromConfig = ConfigurationManager.AppSettings["SharePointPnPUserAgent"]; - } - catch // throws exception if being called from a .NET Standard 2.0 application - { - - } - if (string.IsNullOrWhiteSpace(ClientContextExtensions.userAgentFromConfig)) - { - ClientContextExtensions.userAgentFromConfig = Environment.GetEnvironmentVariable("SharePointPnPUserAgent", EnvironmentVariableTarget.Process); - } - - //#if NET5_0_OR_GREATER - // loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - //#else - // var serviceCollection = new ServiceCollection(); - // serviceCollection.AddLogging(builder => builder.AddConsole()); - // loggerFactory = serviceCollection.BuildServiceProvider().GetService(); - //#endif - // log = new Logger(loggerFactory); - } - /// - /// Executes the current set of data retrieval queries and method invocations and retries it if needed using the Task Library. - /// - /// clientContext to operate on - /// Number of times to retry the request - /// UserAgent string value to insert for this request. You can define this value in your app's config file using key="SharePointPnPUserAgent" value="PnPRocks"> - public static Task ExecuteQueryRetryAsync(this ClientRuntimeContext clientContext, int retryCount = 10, string userAgent = null) - { - return ExecuteQueryImplementation(clientContext, retryCount, userAgent); - } - - - /// - /// Executes the current set of data retrieval queries and method invocations and retries it if needed. - /// - /// clientContext to operate on - /// Number of times to retry the request - /// UserAgent string value to insert for this request. You can define this value in your app's config file using key="SharePointPnPUserAgent" value="PnPRocks"> - public static void ExecuteQueryRetry(this ClientRuntimeContext clientContext, int retryCount = 10, string userAgent = null) - { - Task.Run(() => ExecuteQueryImplementation(clientContext, retryCount, userAgent)).GetAwaiter().GetResult(); - } - - private static async Task ExecuteQueryImplementation(ClientRuntimeContext clientContext, int retryCount = 10, string userAgent = null) - { - - // Set the TLS preference. Needed on some server os's to work when Office 365 removes support for TLS 1.0 - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - - var clientTag = string.Empty; - int backoffInterval = 500; - int retryAttempts = 0; - int retryAfterInterval = 0; - bool retry = false; - ClientRequestWrapper wrapper = null; - - if (retryCount <= 0) - throw new ArgumentException("Provide a retry count greater than zero."); - - // Do while retry attempt is less than retry count - while (retryAttempts < retryCount) - { - try - { - clientContext.ClientTag = SetClientTag(clientTag); - - // Make CSOM request more reliable by disabling the return value cache. Given we - // often clone context objects and the default value is - clientContext.DisableReturnValueCache = true; - // Add event handler to "insert" app decoration header to mark the PnP Sites Core library as a known application - EventHandler appDecorationHandler = AttachRequestUserAgent(userAgent); - - clientContext.ExecutingWebRequest += appDecorationHandler; - - // DO NOT CHANGE THIS TO EXECUTEQUERYRETRY - if (!retry) - { - await clientContext.ExecuteQueryAsync().ConfigureAwait(false); - } - else - { - if (wrapper != null && wrapper.Value != null) - { - await clientContext.RetryQueryAsync(wrapper.Value).ConfigureAwait(false); - } - } - - // Remove the app decoration event handler after the executequery - clientContext.ExecutingWebRequest -= appDecorationHandler; - - return; - } - catch (WebException wex) - { - var response = wex.Response as HttpWebResponse; - // Check if request was throttled - http status code 429 - // Check is request failed due to server unavailable - http status code 503 - if ((response != null && - (response.StatusCode == (HttpStatusCode)429 - || response.StatusCode == (HttpStatusCode)503 - // || response.StatusCode == (HttpStatusCode)500 - )) - || wex.Status == WebExceptionStatus.Timeout) - { - wrapper = (ClientRequestWrapper)wex.Data["ClientRequest"]; - retry = true; - retryAfterInterval = 0; - - //Add delay for retry, retry-after header is specified in seconds - if (response != null && response.Headers["Retry-After"] != null) - { - if (int.TryParse(response.Headers["Retry-After"], out int retryAfterHeaderValue)) - { - retryAfterInterval = retryAfterHeaderValue * 1000; - } - } - else - { - retryAfterInterval = backoffInterval; - backoffInterval *= 2; - } - - if (wex.Status == WebExceptionStatus.Timeout) - { - //log.LogWarning(string.Format(SharePointTransformationResources.Warning_CSOMRequestTimeout, retryAttempts + 1, retryAfterInterval)); - } - else - { - //log.LogWarning(string.Format(SharePointTransformationResources.Warning_CSOMRequestFrequencyExceeded, retryAttempts + 1, retryAfterInterval)); - } - - await Task.Delay(retryAfterInterval).ConfigureAwait(false); - - //Add to retry count and increase delay. - retryAttempts++; - } - else - { - var errorSb = new System.Text.StringBuilder(); - - errorSb.AppendLine(wex.ToString()); - errorSb.AppendLine($"TraceCorrelationId: {clientContext.TraceCorrelationId}"); - errorSb.AppendLine($"Url: {clientContext.Url}"); - - //find innermost Error and check if it is a SocketException - Exception innermostEx = wex; - while (innermostEx.InnerException != null) innermostEx = innermostEx.InnerException; - var socketEx = innermostEx as System.Net.Sockets.SocketException; - if (socketEx != null) - { - errorSb.AppendLine($"ErrorCode: {socketEx.ErrorCode}"); //10054 - errorSb.AppendLine($"SocketErrorCode: {socketEx.SocketErrorCode}"); //ConnectionReset - errorSb.AppendLine($"Message: {socketEx.Message}"); //An existing connection was forcibly closed by the remote host - - //log.LogError(string.Format(SharePointTransformationResources.Error_ClientContextExtensions_ExecuteQueryRetryException, errorSb)); - - //retry - wrapper = (ClientRequestWrapper)wex.Data["ClientRequest"]; - retry = true; - retryAfterInterval = 0; - - //Add delay for retry, retry-after header is specified in seconds - if (response != null && response.Headers["Retry-After"] != null) - { - if (int.TryParse(response.Headers["Retry-After"], out int retryAfterHeaderValue)) - { - retryAfterInterval = retryAfterHeaderValue * 1000; - } - } - else - { - retryAfterInterval = backoffInterval; - backoffInterval *= 2; - } - - //log.LogWarning(string.Format(SharePointTransformationResources.Error_CSOMRequestSocketException, retryAttempts + 1, retryAfterInterval)); - - await Task.Delay(retryAfterInterval).ConfigureAwait(false); - - //Add to retry count and increase delay. - retryAttempts++; - } - else - { - if (response != null) - { - //if(response.Headers["SPRequestGuid"] != null) - if (response.Headers.AllKeys.Any(k => string.Equals(k, "SPRequestGuid", StringComparison.InvariantCultureIgnoreCase))) - { - var spRequestGuid = response.Headers["SPRequestGuid"]; - errorSb.AppendLine($"ServerErrorTraceCorrelationId: {spRequestGuid}"); - } - } - - //log.LogError(string.Format(SharePointTransformationResources.Error_ClientContextExtensions_ExecuteQueryRetryException, errorSb)); - throw; - } - } - } - catch (ServerException serverEx) - { - var errorSb = new System.Text.StringBuilder(); - - errorSb.AppendLine(serverEx.ToString()); - errorSb.AppendLine($"ServerErrorCode: {serverEx.ServerErrorCode}"); - errorSb.AppendLine($"ServerErrorTypeName: {serverEx.ServerErrorTypeName}"); - errorSb.AppendLine($"ServerErrorTraceCorrelationId: {serverEx.ServerErrorTraceCorrelationId}"); - errorSb.AppendLine($"ServerErrorValue: {serverEx.ServerErrorValue}"); - errorSb.AppendLine($"ServerErrorDetails: {serverEx.ServerErrorDetails}"); - - //log.LogError(string.Format(SharePointTransformationResources.Error_ClientContextExtensions_ExecuteQueryRetryException, errorSb)); - - throw; - } - } - - throw new MaximumRetryAttemptedException($"Maximum retry attempts {retryCount}, has be attempted."); - } - - /// - /// Attaches either a passed user agent, or one defined in the App.config file, to the WebRequstExecutor UserAgent property. - /// - /// a custom user agent to override any defined in App.config - /// An EventHandler of WebRequestEventArgs. - private static EventHandler AttachRequestUserAgent(string customUserAgent) - { - return (s, e) => - { - bool overrideUserAgent = true; - var existingUserAgent = e.WebRequestExecutor.WebRequest.UserAgent; - if (!string.IsNullOrEmpty(existingUserAgent) && existingUserAgent.StartsWith("NONISV|SharePointPnP|PnPPS/")) - { - overrideUserAgent = false; - } - if (overrideUserAgent) - { - e.WebRequestExecutor.WebRequest.UserAgent = string.IsNullOrEmpty(customUserAgent) ? SharePointConstants.TransformationUserAgent : customUserAgent; - } - }; - } - - /// - /// Sets the client context client tag on outgoing CSOM requests. - /// - /// An optional client tag to set on client context requests. - /// - private static string SetClientTag(string clientTag = "") - { - // ClientTag property is limited to 32 chars - if (string.IsNullOrEmpty(clientTag)) - { - clientTag = $"{SharePointConstants.TransformationClientTag}:{GetCallingPnPMethod()}"; - } - if (clientTag.Length > 32) - { - clientTag = clientTag.Substring(0, 32); - } - - return clientTag; - } - -#pragma warning disable CA1034,CA2229,CA1032 - /// - /// Defines a Maximum Retry Attemped Exception - /// - [Serializable] - public class MaximumRetryAttemptedException : Exception - { - /// - /// Constructor - /// - /// - public MaximumRetryAttemptedException(string message) - : base(message) - { - - } - } -#pragma warning restore CA1034,CA2229,CA1032 - - /// - /// Checks the server library version of the context for a minimally required version - /// - /// clientContext to operate on - /// provide version to validate - /// True if it has minimal required version, false otherwise - public static bool HasMinimalServerLibraryVersion(this ClientRuntimeContext clientContext, string minimallyRequiredVersion) - { - return HasMinimalServerLibraryVersion(clientContext, new Version(minimallyRequiredVersion)); - } - - /// - /// Checks the server library version of the context for a minimally required version - /// - /// clientContext to operate on - /// provide version to validate - /// True if it has minimal required version, false otherwise - public static bool HasMinimalServerLibraryVersion(this ClientRuntimeContext clientContext, Version minimallyRequiredVersion) - { - bool hasMinimalVersion = false; - try - { - clientContext.ExecuteQueryRetry(); - hasMinimalVersion = clientContext.ServerLibraryVersion.CompareTo(minimallyRequiredVersion) >= 0; - } - catch (PropertyOrFieldNotInitializedException) - { - // swallow the exception. - } - - return hasMinimalVersion; - } - - /// - /// Returns the name of the method calling ExecuteQueryRetry and ExecuteQueryRetryAsync - /// - /// A string with the method name - private static string GetCallingPnPMethod() - { - StackTrace t = new StackTrace(); - - string pnpMethod = ""; - try - { - for (int i = 0; i < t.FrameCount; i++) - { - var frame = t.GetFrame(i); - var frameName = frame.GetMethod().Name; - if (frameName.Equals("ExecuteQueryRetry") || frameName.Equals("ExecuteQueryRetryAsync")) - { - var method = t.GetFrame(i + 1).GetMethod(); - - // Only return the calling method in case ExecuteQueryRetry was called from inside the PnP core library - if (method.Module.Name.Equals("PnP.Framework.dll", StringComparison.InvariantCultureIgnoreCase)) - { - pnpMethod = method.Name; - } - break; - } - } - } - catch - { - // ignored - } - - return pnpMethod; - } - - /// - /// Gets the version of SharePoint - /// - /// - /// A tuple with the SharePoint version and the SharePoint exact version - public static (SPVersion, string) GetVersions(this ClientRuntimeContext clientContext) - { - Uri urlUri = new Uri(clientContext.Url); - - // TODO: Consider adding a global caching provider via DI - var spVersionFromCache = SPVersion.Unknown; - string version = null; - if (spVersionFromCache != SPVersion.Unknown) - { - return (spVersionFromCache, version); - } - else - { - try - { - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create($"{urlUri.Scheme}://{urlUri.DnsSafeHost}:{urlUri.Port}/_vti_pvt/service.cnf"); - //request.Credentials = clientContext.Credentials; - request.AddAuthenticationData(clientContext as ClientContext); - - var response = request.GetResponse(); - - using (var dataStream = response.GetResponseStream()) - { - // Open the stream using a StreamReader for easy access. - using (System.IO.StreamReader reader = new System.IO.StreamReader(dataStream)) - { - // Read the content. Will be in this format - // SPO: - // vti_encoding: SR | utf8 - nl - // vti_extenderversion: SR | 16.0.0.8929 - // SP2019: - // vti_encoding:SR|utf8-nl - // vti_extenderversion:SR|16.0.0.10340 - // SP2016: - // vti_encoding: SR | utf8 - nl - // vti_extenderversion: SR | 16.0.0.4732 - // SP2013: - // vti_encoding:SR|utf8-nl - // vti_extenderversion: SR | 15.0.0.4505 - // Version numbers from https://buildnumbers.wordpress.com/sharepoint/ - - // Microsoft Developer Blog - - // https://developer.microsoft.com/en-us/sharepoint/blogs/updated-versions-of-the-sharepoint-on-premises-csom-nuget-packages/ - // Todd Klindt's Blog - - // http://www.toddklindt.com/sp2010builds - // http://www.toddklindt.com/sp2013builds - // http://www.toddklindt.com/sp2016builds - // http://www.toddklindt.com/sp2019builds - - - version = reader.ReadToEnd().Split('|')[2].Trim(); - - // TODO: Consider using global caching to store versions - - if (Version.TryParse(version, out Version v)) - { - if (v.Major == 14) - { - // SP2010 is not supported anymore - spVersionFromCache = SPVersion.Unsupported; - } - else if (v.Major == 15) - { - // You can change the output to SP2013 to use standard CSOM calls. - spVersionFromCache = SPVersion.SP2013Legacy; - } - else if (v.Major == 16) - { - if (v.MinorRevision < 6000) - { - spVersionFromCache = SPVersion.SP2016Legacy; - } - // Set to 12000 because some SPO reports as 12012 and SP2019 build numbers are increasing very slowly - else if (v.MinorRevision > 10300 && v.MinorRevision < 12000) - { - spVersionFromCache = SPVersion.SP2019; - } - else - { - spVersionFromCache = SPVersion.SPO; - } - } - } - } - } - } - catch (WebException) - { - // todo - } - } - - // TODO: Consider using global caching to store versions - return (spVersionFromCache, version); - } - - /// - /// Clones a ClientContext object while "taking over" the security context of the existing ClientContext instance - /// - /// ClientContext to be cloned - /// Site URL to be used for cloned ClientContext - /// A ClientContext object created for the passed site URL - public static ClientContext Clone(this ClientRuntimeContext clientContext, string siteUrl) - { - if (string.IsNullOrWhiteSpace(siteUrl)) - { - throw new ArgumentException(SharePointTransformationResources.Error_Clone_Context_Url_Required, nameof(siteUrl)); - } - - return clientContext.Clone(new Uri(siteUrl)); - } - - /// - /// Clones a ClientContext object while "taking over" the security context of the existing ClientContext instance - /// - /// ClientContext to be cloned - /// Site URL to be used for cloned ClientContext - /// A ClientContext object created for the passed site URL - public static ClientContext Clone(this ClientRuntimeContext clientContext, Uri siteUrl) - { - return Clone(clientContext, new ClientContext(siteUrl), siteUrl); - } - - /// - /// Clones a ClientContext object while "taking over" the security context of the existing ClientContext instance - /// - /// ClientContext to be cloned - /// CientContext stub to be used for cloning - /// Site URL to be used for cloned ClientContext - /// A ClientContext object created for the passed site URL - internal static ClientContext Clone(this ClientRuntimeContext clientContext, ClientContext targetContext, Uri siteUrl) - { - if (siteUrl == null) - { - throw new ArgumentException(SharePointTransformationResources.Error_Clone_Context_Url_Required, nameof(siteUrl)); - } - - ClientContext clonedClientContext = targetContext; - clonedClientContext.ClientTag = clientContext.ClientTag; - clonedClientContext.DisableReturnValueCache = clientContext.DisableReturnValueCache; - clonedClientContext.WebRequestExecutorFactory = clientContext.WebRequestExecutorFactory; - - //Take over the form digest handling setting - - var originalUri = new Uri(clientContext.Url); - - clonedClientContext.ExecutingWebRequest += (sender, webRequestEventArgs) => - { - // Call the ExecutingWebRequest delegate method from the original ClientContext object, but pass along the webRequestEventArgs of - // the new delegate method - MethodInfo methodInfo = clientContext.GetType().GetMethod("OnExecutingWebRequest", BindingFlags.Instance | BindingFlags.NonPublic); - object[] parametersArray = new object[] { webRequestEventArgs }; - methodInfo.Invoke(clientContext, parametersArray); - }; - - return clonedClientContext; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientObjectExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientObjectExtensions.cs deleted file mode 100644 index 083bcb1d71..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ClientObjectExtensions.cs +++ /dev/null @@ -1,268 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Model; -using System; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - /// - /// Class for client object extension methods - /// - internal static class ClientObjectExtensions - { - /// - /// Checks if the ClientObject is null - /// - /// Type of object to operate on - /// Object to operate on - /// True if the server object is null, otherwise false - public static bool ServerObjectIsNull(this T clientObject) where T : ClientObject - { - if (clientObject == null) - { - return true; - } - return (!(clientObject.ServerObjectIsNull != null && - clientObject.ServerObjectIsNull.HasValue && - !clientObject.ServerObjectIsNull.Value)); - } - - /// - /// Check if a property is available on a object - /// Note: this does not apply for the Values property of model classes that implement - /// - /// Type of object to operate on - /// Object to operate on - /// Lamda expression containing the properties to check (e.g. w => w.HasUniqueRoleAssignments) - /// True if the property is available, false otherwise - public static bool IsPropertyAvailable(this T clientObject, Expression> propertySelector) where T : ClientObject - { - var body = propertySelector.Body as MemberExpression ?? ((UnaryExpression)propertySelector.Body).Operand as MemberExpression; - - return clientObject.IsPropertyAvailable(body.Member.Name); - } - - /// - /// Check if a property is instantiated on a object - /// Note: this does not apply for the Values property of model classes that implement - /// - /// Type of object to operate on - /// Object to operate on - /// Lamda expression containing the properties to check (e.g. w => w.HasUniqueRoleAssignments) - /// True if the property is instantiated, false otherwise - public static bool IsObjectPropertyInstantiated(this T clientObject, Expression> propertySelector) where T : ClientObject - { - var body = propertySelector.Body as MemberExpression ?? ((UnaryExpression)propertySelector.Body).Operand as MemberExpression; - - return clientObject.IsObjectPropertyInstantiated(body.Member.Name); - } - - /// - /// Ensures that particular property is loaded on the and immediately returns this property. - /// Note: this does not apply for the Values property of model classes that implement - /// - /// type - /// Property type - /// - /// Lamda expression containing the property to ensure (e.g. w => w.HasUniqueRoleAssignments) - /// Property value - public static TResult EnsureProperty(this T clientObject, Expression> propertySelector) where T : ClientObject - { - return Task.Run(() => EnsurePropertyImplementation(clientObject, propertySelector)).GetAwaiter().GetResult(); - } - - /// - /// Ensures that particular property is loaded on the and immediately returns this property - /// Note: this does not apply for the Values property of model classes that implement - /// - /// type - /// Property type - /// - /// Lamda expression containing the property to ensure (e.g. w => w.HasUniqueRoleAssignments) - /// Property value - public static async Task EnsurePropertyAsync(this T clientObject, Expression> propertySelector) where T : ClientObject - { - return await EnsurePropertyImplementation(clientObject, propertySelector).ConfigureAwait(false); - } - - private async static Task EnsurePropertyImplementation(T clientObject, Expression> propertySelector) where T : ClientObject - { - if (propertySelector.Body.NodeType == ExpressionType.Call && propertySelector.Body is MethodCallExpression) - { - var body = (MethodCallExpression)propertySelector.Body; - if (body.Method.IsGenericMethod && - body.Method.DeclaringType == typeof(ClientObjectQueryableExtension) && - (body.Method.Name == "Include" || body.Method.Name == "IncludeWithDefaultProperties")) - { - if (body.Arguments.Count != 2) - { - throw new Exception("Invalid arguments number"); - } - - clientObject.Context.Load(clientObject, propertySelector.ToUntypedStaticMethodCallExpression()); - await clientObject.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - - var arg = (MemberExpression)(body.Arguments[0]); - var prop = (PropertyInfo)(Expression.Property(Expression.Constant(clientObject), arg.Member.Name).Member); - - return (TResult)prop.GetValue(clientObject); - } - throw new Exception("Only 'Include' and 'IncludeWithDefaultProperties' methods are supported."); - } - - var untypedExpresssion = propertySelector.ToUntypedPropertyExpression(); - if (!clientObject.IsPropertyAvailable(untypedExpresssion) && !clientObject.IsObjectPropertyInstantiated(untypedExpresssion)) - { - clientObject.Context.Load(clientObject, untypedExpresssion); - await clientObject.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - } - else if (clientObject.IsObjectPropertyInstantiated(untypedExpresssion)) - { - if (EnsureCollectionLoaded(clientObject, untypedExpresssion)) - { - await clientObject.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - } - } - - return (propertySelector.Compile())(clientObject); - } - - /// - /// Ensures that particular properties are loaded on the - /// - /// type - /// - /// Lamda expressions containing the properties to ensure (e.g. w => w.HasUniqueRoleAssignments, w => w.ServerRelativeUrl) - /// Property value - public static void EnsureProperties(this T clientObject, params Expression>[] propertySelector) where T : ClientObject - { - Task.Run(() => EnsurePropertiesImplementation(clientObject, propertySelector)).GetAwaiter().GetResult(); - } - - /// - /// Ensures that particular properties are loaded on the - /// - /// type - /// - /// Lamda expressions containing the properties to ensure (e.g. w => w.HasUniqueRoleAssignments, w => w.ServerRelativeUrl) - /// Property value - public static async Task EnsurePropertiesAsync(this T clientObject, params Expression>[] propertySelector) where T : ClientObject - { - await EnsurePropertiesImplementation(clientObject, propertySelector).ConfigureAwait(false); - } - internal static async Task EnsurePropertiesImplementation(this T clientObject, params Expression>[] propertySelector) where T : ClientObject - { - var dirty = false; - foreach (Expression> expression in propertySelector) - { - if (expression.Body.NodeType == ExpressionType.Call && expression.Body is MethodCallExpression) - { - var body = (MethodCallExpression)expression.Body; - if (body.Method.IsGenericMethod && - body.Method.DeclaringType == typeof(ClientObjectQueryableExtension) && - (body.Method.Name == "Include" || body.Method.Name == "IncludeWithDefaultProperties")) - { - if (body.Arguments.Count != 2) - { - throw new Exception("Invalid arguments number"); - } - - if (!(clientObject is ClientObjectCollection) || ((clientObject is ClientObjectCollection) && !(clientObject as ClientObjectCollection).AreItemsAvailable)) - { - clientObject.Context.Load(clientObject, expression); - dirty = true; - } - } - else - { - throw new Exception("Only 'Include' and 'IncludeWithDefaultProperties' methods are supported."); - } - } - else if (!clientObject.IsPropertyAvailable(expression) && !clientObject.IsObjectPropertyInstantiated(expression)) - { - clientObject.Context.Load(clientObject, expression); - dirty = true; - } - else if (clientObject.IsObjectPropertyInstantiated(expression)) - { - dirty = dirty || EnsureCollectionLoaded(clientObject, expression); - } - } - - if (dirty) - { - await clientObject.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - } - } - - /// - /// Converts generic > ]]> to Expression with object return type - > ]]> - /// - /// Input type - /// Returns type - /// to convert - /// New Expression where return type is object and not generic - public static Expression> ToUntypedStaticMethodCallExpression(this Expression> expression) - { - var body = (MethodCallExpression)expression.Body; - var clientObjectProperty = (MemberExpression)(body.Arguments[0]); - var newArrayExpression = body.Arguments[1] as NewArrayExpression; - var param = Expression.Parameter(typeof(TInput)); - var propertyToCall = (Expression.Property(param, clientObjectProperty.Member.Name)); - - - return Expression.Lambda>(Expression.Call(null, body.Method, propertyToCall, newArrayExpression), param); - } - - /// - /// Converts generic > ]]> to Expression with object return type - > ]]> - /// - /// Input type - /// Returns type - /// to convert - /// New Expression where return type is object and not generic - public static Expression> ToUntypedPropertyExpression(this Expression> expression) - { - - var body = expression.Body as MemberExpression ?? ((UnaryExpression)expression.Body).Operand as MemberExpression; - - var memberName = body.Member.Name; - - var param = Expression.Parameter(typeof(TInput)); - var field = Expression.Property(param, memberName); - - return Expression.Lambda>( - Expression.Convert(field, typeof(object)), - param); - } - - private static bool EnsureCollectionLoaded(T clientObject, Expression> propertySelector) where T : ClientObject - { - var body = propertySelector.Body as MemberExpression ?? ((UnaryExpression)propertySelector.Body).Operand as MemberExpression; - var propertyInfo = (PropertyInfo)body.Member; - var propertyValue = propertyInfo.GetValue(clientObject); - - if (propertyValue is ClientObjectCollection) - { - var clientCollection = propertyValue as ClientObjectCollection; - if (!clientCollection.AreItemsAvailable) - { - clientObject.Context.Load(clientObject, propertySelector); - return true; - } - } - return false; - } - - internal static void ClearObjectData(this ClientObject clientObject) - { - var info_ClientObject_ObjectData = typeof(ClientObject) - .GetProperty("ObjectData", BindingFlags.NonPublic | BindingFlags.Instance); - - var objectData = (ClientObjectData)info_ClientObject_ObjectData.GetValue(clientObject, Array.Empty()); - objectData.MethodReturnObjects.Clear(); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/FieldAndContentTypeExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/FieldAndContentTypeExtensions.cs deleted file mode 100644 index e1f7e4e321..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/FieldAndContentTypeExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.SharePoint.Client; -using System.Collections.Generic; -using System.Linq; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - /// - /// This class provides extension methods that will help you work with fields and content types. - /// - internal static partial class FieldAndContentTypeExtensions - { - /// - /// Returns the field if it exists. Null if does not exist. - /// - /// Web to be processed - /// If true, search parent sites and root site - /// If true, search across all the available fields in the site hierarchy - /// Field - public static Field GetFieldByInternalName(this Web web, string internalName, bool searchInSiteHierarchy = false) - { - IEnumerable fields = null; - - if (searchInSiteHierarchy) - { - fields = web.Context.LoadQuery(web.AvailableFields.Where(f => f.InternalName == internalName)); - } - else - { - fields = web.Context.LoadQuery(web.Fields.Where(f => f.InternalName == internalName)); - } - - web.Context.ExecuteQueryRetry(); - return fields.FirstOrDefault(); - } - - /// - /// Returns the field if it exists. Null if it does not exist. - /// - /// FieldCollection to be processed. - /// Internal name of the field - /// Field - public static Field GetFieldByInternalName(this FieldCollection fields, string internalName) - { - if (!fields.ServerObjectIsNull.HasValue || - fields.ServerObjectIsNull.Value) - { - fields.Context.Load(fields); - fields.Context.ExecuteQueryRetry(); - } - return fields.FirstOrDefault(f => f.InternalName == internalName); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/HttpWebRequestExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/HttpWebRequestExtensions.cs deleted file mode 100644 index 0a37117ea5..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/HttpWebRequestExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Utilities; - -namespace System.Net -{ - /// - /// HttpWebRequest extension methods - /// - public static class HttpWebRequestExtensions - { - /// - /// Grabs authenticaiton data from the passed client context and attaches that to the http request - /// - /// http request to update - /// ClientContext object to grab authentication data from - public static void AddAuthenticationData(this HttpWebRequest httpWebRequest, ClientContext cc) - { - if (cc.Credentials != null) - { - // Copy credentials if set - httpWebRequest.Credentials = cc.Credentials; - } - else - { - // If authentication happened via a cookie based approach (e.g. ADFS) then get the cookies that are currently linked to the context and reuse them - httpWebRequest.CookieContainer = new CookieManager().GetCookies(cc); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListExtensions.cs deleted file mode 100644 index 6b1f6b234e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListExtensions.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Microsoft.SharePoint.Client; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - internal static class ListExtensions - { - /// - /// Get list by using Title - /// - /// Site to be processed - can be root web or sub site - /// Title of the list to return - /// Loaded list instance matching to title or null - /// Thrown when listTitle is a zero-length string or contains only white space - /// listTitle is null - /// Additional list of lambda expressions of properties to load alike l => l.BaseType - public static List GetListByTitle(this Web web, string listTitle, params Expression>[] expressions) - { - var baseExpressions = new List>> { l => l.DefaultViewUrl, l => l.Id, l => l.BaseTemplate, l => l.OnQuickLaunch, l => l.DefaultViewUrl, l => l.Title, l => l.Hidden, l => l.RootFolder }; - - if (expressions != null && expressions.Any()) - { - baseExpressions.AddRange(expressions); - } - if (string.IsNullOrEmpty(listTitle)) - { - throw (listTitle == null) - ? new ArgumentNullException(nameof(listTitle)) - : new ArgumentException(SharePointTransformationResources.Error_Message_EmptyString_Arg, nameof(listTitle)); - } - var query = web.Lists.IncludeWithDefaultProperties(baseExpressions.ToArray()); - var lists = web.Context.LoadQuery(query.Where(l => l.Title == listTitle)); - web.Context.ExecuteQueryRetry(); - return lists.FirstOrDefault(); - } - - /// - /// Get List by using Id - /// - /// The web containing the list - /// The Id of the list - /// Additional list of lambda expressions of properties to load alike l => l.BaseType - /// Loaded list instance matching specified Id - /// Thrown when listId is an empty Guid - /// listId is null - public static List GetListById(this Web web, Guid listId, params Expression>[] expressions) - { - var baseExpressions = new List>> { l => l.DefaultViewUrl, l => l.Id, l => l.BaseTemplate, l => l.OnQuickLaunch, l => l.DefaultViewUrl, l => l.Title, l => l.Hidden, l => l.RootFolder }; - - if (expressions != null && expressions.Any()) - { - baseExpressions.AddRange(expressions); - } - - if (listId == Guid.Empty) - { - throw new ArgumentNullException(nameof(listId)); - } - - if (listId == Guid.Empty) - { - throw new ArgumentException(nameof(listId)); - } - - var query = web.Lists.IncludeWithDefaultProperties(baseExpressions.ToArray()); - var lists = web.Context.LoadQuery(query.Where(l => l.Id == listId)); - - web.Context.ExecuteQueryRetry(); - - return lists.FirstOrDefault(); - } - - /// - /// Get list by using Url - /// - /// Web (site) to be processed - /// Url of list relative to the web (site), e.g. lists/testlist - /// Additional list of lambda expressions of properties to load alike l => l.BaseType - /// Returns list if found, null if no list is found. - public static List GetListByUrl(this Web web, string webRelativeUrl, params Expression>[] expressions) - { - var baseExpressions = new List>> { l => l.DefaultViewUrl, l => l.Id, l => l.BaseTemplate, l => l.OnQuickLaunch, l => l.DefaultViewUrl, l => l.Title, l => l.Hidden, l => l.RootFolder }; - - if (expressions != null && expressions.Any()) - { - baseExpressions.AddRange(expressions); - } - if (string.IsNullOrEmpty(webRelativeUrl)) - throw new ArgumentNullException(nameof(webRelativeUrl)); - - if (!web.IsObjectPropertyInstantiated("ServerRelativeUrl")) - { - web.Context.Load(web, w => w.ServerRelativeUrl); - web.Context.ExecuteQueryRetry(); - } - var listServerRelativeUrl = UrlUtility.Combine(web.ServerRelativeUrl, webRelativeUrl); - - var foundList = web.GetList(listServerRelativeUrl.ToString()); - web.Context.Load(foundList, baseExpressions.ToArray()); - try - { - web.Context.ExecuteQueryRetry(); - } - catch (ServerException se) - { - if (se.ServerErrorTypeName == "System.IO.FileNotFoundException") - { - foundList = null; - } - else - { - throw; - } - } - - return foundList; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListItemExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListItemExtensions.cs deleted file mode 100644 index 81137e6ec2..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/ListItemExtensions.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Model; -using System; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - /// - /// Extension class for ListItem objects - /// - internal static class ListItemExtensions - { - /// - /// Determines the type of page - /// - /// Page list item - /// Type of page - internal static SourcePageType PageType(this ListItem item) - { - if (FieldExistsAndUsed(item, SharePointConstants.HtmlFileTypeField) && !String.IsNullOrEmpty(item[SharePointConstants.HtmlFileTypeField].ToString())) - { - if (item[SharePointConstants.HtmlFileTypeField].ToString().Equals("SharePoint.WebPartPage.Document", StringComparison.InvariantCultureIgnoreCase)) - { - return SourcePageType.WebPartPage; - } - } - - if (FieldExistsAndUsed(item, SharePointConstants.WikiField) && !String.IsNullOrEmpty(item[SharePointConstants.WikiField].ToString())) - { - return SourcePageType.WikiPage; - } - - if (FieldExistsAndUsed(item, SharePointConstants.BodyField) && !String.IsNullOrEmpty(item[SharePointConstants.BodyField].ToString())) - { - return SourcePageType.BlogPage; - } - - if (FieldExistsAndUsed(item, SharePointConstants.ClientSideApplicationIdField) && item[SharePointConstants.ClientSideApplicationIdField].ToString().Equals(SharePointConstants.FeatureId_Web_ModernPage.ToString(), StringComparison.InvariantCultureIgnoreCase)) - { - return SourcePageType.ClientSidePage; - } - - if (FieldExists(item, SharePointConstants.PublishingRollupImageField) && FieldExists(item, SharePointConstants.AudienceField)) - { - return SourcePageType.PublishingPage; - } - - if (FieldExistsAndUsed(item, SharePointConstants.WikiField)) - { - return SourcePageType.WikiPage; - } - - if (FieldExistsAndUsed(item, SharePointConstants.FileTypeField) && !String.IsNullOrEmpty(item[SharePointConstants.FileTypeField].ToString())) - { - if (item[SharePointConstants.FileTypeField].ToString().Equals("pointpub", StringComparison.InvariantCultureIgnoreCase)) - { - return SourcePageType.DelveBlogPage; - } - } - - return SourcePageType.AspxPage; - } - - /// - /// Checks if a listitem contains a field - /// - /// List item to check - /// Name of the field to check - /// - internal static bool FieldExists(this ListItem item, string fieldName) - { - return item.FieldValues.ContainsKey(fieldName); - } - - /// - /// Checks if a listitem contains a field with a value - /// - /// List item to check - /// Name of the field to check - /// - internal static bool FieldExistsAndUsed(this ListItem item, string fieldName) - { - return (item.FieldValues.ContainsKey(fieldName) && item[fieldName] != null); - } - - /// - /// Checks if a listitem contains a field and eventually retrieves its value - /// - /// List item to check - /// Name of the field to check - /// The value of the field, if any - /// Whether the field is defined in the target item - internal static bool TryGetFieldValue(this ListItem item, string fieldName, out TFieldValue fieldValue) - { - if (item.FieldValues.ContainsKey(fieldName) && item[fieldName] != null) - { - fieldValue = (TFieldValue)item[fieldName]; - return true; - } - else - { - fieldValue = default(TFieldValue); - return false; - } - } - - /// - /// Gets the field value (if the field exists and a value is set) in the given type - /// - /// Type to get the fieldValue in - /// List item to get the field from - /// Name of the field to get the value from - /// Value of the field in the requested type or null if unable to cast - public static T GetFieldValueAs(this ListItem item, string fieldName) - { - if (item.FieldExistsAndUsed(fieldName)) - { - var fieldValue = item[fieldName]; - - if (fieldValue is T returnValue) - { - return returnValue; - } - try - { - return (T)Convert.ChangeType(fieldValue, typeof(T)); - } - catch (InvalidCastException) - { - return default; - } - } - - return default; - } - - internal static string GetPageLayoutFileUrl(this ListItem pageItem) - { - FieldUrlValue fileRefField = null; - if (pageItem.TryGetFieldValue(SharePointConstants.PublishingPageLayoutField, out fileRefField)) - { - string pageLayoutUrl = fileRefField.Url; - if (string.IsNullOrEmpty(pageLayoutUrl)) - { - pageLayoutUrl = ""; - } - return pageLayoutUrl; - } - - return ""; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/StringExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/StringExtensions.cs deleted file mode 100644 index 188ce67c0d..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/StringExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using PnP.Core.Transformation.SharePoint.Model; -using System.Text; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - internal static class StringExtensions - { - /// - /// Formats a string that has the format ThisIsAClassName and formats in a friendly way - /// - /// string value - /// Friendly string value - internal static string FormatAsFriendlyTitle(this SourcePageType value) - { - var charArr = value.ToString().ToCharArray(); - var result = new StringBuilder(); - for (var i = 0; i < charArr.Length; i++) - { - if (char.IsUpper(charArr[i])) - { - result.Append($" {charArr[i]}"); - } - else - { - result.Append(charArr[i]); - } - } - - // Convert to string and remove space at start - return result.ToString().TrimStart(' '); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/UriExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/UriExtensions.cs deleted file mode 100644 index b99f536f47..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/UriExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace System -{ - /// - /// Extensions methods for uri - /// - internal static class UriExtensions - { - /// - /// Combines a uri with a relative path - /// - /// - /// - /// - internal static Uri Combine(this Uri uri, string path) - { - if (uri == null) throw new ArgumentNullException(nameof(uri)); - if (path == null) throw new ArgumentNullException(nameof(path)); - - return new Uri($"{uri.ToString().TrimEnd('/')}/{path}"); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/WebExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/WebExtensions.cs deleted file mode 100644 index caa9a73bb7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Extensions/WebExtensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -using Microsoft.SharePoint.Client; -using System; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.Extensions -{ - internal static class WebExtensions - { - internal static string GetLocalizedListName(this Web web, ListType listType, string defaultName) - { - var context = web.Context; - - int lcid = (int)web.EnsureProperty(w => w.Language); - - // TODO: Consider using caching - - string resourceName = null; - string assemblyName = "core"; - switch (listType) - { - case ListType.Blogs: - resourceName = "$Resources:blogpost_Folder"; - break; - case ListType.SitePages: - resourceName = "$Resources:pages_Folder"; - break; - case ListType.PublishingPages: - resourceName = "$Resources:List_Pages_UrlName"; - assemblyName = "osrvcore"; - break; - default: - break; - } - - ClientResult result = Microsoft.SharePoint.Client.Utilities.Utility.GetLocalizedString(context, resourceName, assemblyName, lcid); - context.ExecuteQueryRetry(); - var listName = new Regex(@"['´`]").Replace(result.Value, ""); - - return (string.IsNullOrEmpty(listName) ? defaultName : listName).ToLowerInvariant(); - } - - /// - /// Checks if the current web is a sub site or not - /// - /// Web to check - /// True is sub site, false otherwise - public static bool IsSubSite(this Web web) - { - if (web == null) throw new ArgumentNullException(nameof(web)); - - var site = (web.Context as ClientContext).Site; - var rootWeb = site.EnsureProperty(s => s.RootWeb); - - web.EnsureProperty(w => w.Id); - rootWeb.EnsureProperty(w => w.Id); - - if (rootWeb.Id != web.Id) - { - return true; - } - return false; - } - - /// - /// Returns a file as string - /// - /// The Web to process - /// The server relative URL to the file - /// The file contents as a string - /// # - /// - /// Based on https://github.com/SharePoint/PnP-Sites-Core/blob/master/Core/OfficeDevPnP.Core/Extensions/FileFolderExtensions.cs - /// Modified to force onpremises support - /// - /// - public static string GetFileByServerRelativeUrlAsString(this Web web, string serverRelativeUrl) - { - var file = web.GetFileByServerRelativeUrl(serverRelativeUrl); - var context = web.Context; - context.Load(file); - context.ExecuteQueryRetry(); - - Stream sourceStream; - ClientResult stream = file.OpenBinaryStream(); - web.Context.ExecuteQueryRetry(); - sourceStream = stream.Value; - - string returnString = string.Empty; - - using (Stream memStream = new MemoryStream()) - { - sourceStream.CopyTo(memStream); - memStream.Position = 0; - - using (var reader = new StreamReader(memStream)) - { - returnString = reader.ReadToEnd(); - } - } - - return returnString; - } - - /// - /// Returns a file as string - /// - /// The Web to process - /// The server relative URL to the file - /// The file contents as a string - public static string GetFileAsString(this Web web, string serverRelativeUrl) - { - return Task.Run(() => web.GetFileAsStringImplementation(serverRelativeUrl)).GetAwaiter().GetResult(); - } - - /// - /// Returns a file as string - /// - /// The Web to process - /// The server relative URL to the file - /// The file contents as a string - public static async Task GetFileAsStringAsync(this Web web, string serverRelativeUrl) - { - return await web.GetFileAsStringImplementation(serverRelativeUrl).ConfigureAwait(false); - } - - /// - /// Returns a file as string - /// - /// The Web to process - /// The server relative URL to the file - /// The file contents as a string - private static async Task GetFileAsStringImplementation(this Web web, string serverRelativeUrl) - { - var file = web.GetFileByServerRelativePath(ResourcePath.FromDecodedUrl(serverRelativeUrl)); - web.Context.Load(file); - await web.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - ClientResult stream = file.OpenBinaryStream(); - await web.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - - string returnString = string.Empty; - using (Stream memStream = new MemoryStream()) - { - stream.Value.CopyTo(memStream); - memStream.Position = 0; - using (var reader = new StreamReader(memStream)) - { - returnString = reader.ReadToEnd(); - } - } - - return returnString; - } - } - - /// - /// Defines the supported types of lists and libraries - /// - internal enum ListType - { - /// - /// List of blogs - /// - Blogs, - /// - /// List of site pages - /// - SitePages, - /// - /// List of pages - /// - PublishingPages, - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/BaseFunctionProcessor.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/BaseFunctionProcessor.cs deleted file mode 100644 index 1b42c2ed10..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/BaseFunctionProcessor.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Base class for function processors - /// - public abstract class BaseFunctionProcessor - { - /// - /// Allowed function parameter types - /// - internal enum FunctionType - { - String = 0, - Integer = 1, - Bool = 2, - Guid = 3, - DateTime = 4 - } - - /// - /// Definition of a function parameter - /// - internal class FunctionParameter - { - /// - /// Name of the parameter - /// - public string Name { get; set; } - /// - /// Type if the parameter - /// - public FunctionType Type { get; set; } - /// - /// Value of the parameter - /// - public string Value { get; set; } - - /// - /// Is this a static, hard coded, value. Eg 'my hardcoded string' - /// - public bool IsStatic { get; set; } - } - - /// - /// Definition of a function or selector - /// - internal class FunctionDefinition - { - /// - /// AddOn hosting the function/selector. Empty value means the function is hosted by the internal builtin functions library - /// - public string AddOn { get; set; } - /// - /// Name of the function/selector - /// - public string Name { get; set; } - /// - /// Parameter specifying the function result - /// - public FunctionParameter Output { get; set; } - /// - /// List of input parameter used to call the function - /// - public List Input { get; set; } - } - - /// - /// Defines a loaded AddOn function/selector class instance - /// - internal class AddOnType - { - /// - /// Name of the addon. The name is used to link the determine which class instance needs to be used to execute a function - /// - public string Name { get; set; } - /// - /// Instance of the class that holds the functions/selectors - /// - public object Instance { get; set; } - /// - /// Assembly holding the functions/selector class - /// - public Assembly Assembly { get; set; } - /// - /// Type of the functions/selector class - /// - public Type Type { get; set; } - } - - #region Helper methods - internal static FunctionType MapType(string inputType) - { - inputType = inputType.ToLower().Trim(); - - if (inputType == "string") - { - return FunctionType.String; - } - else if (inputType == "integer") - { - return FunctionType.Integer; - } - else if (inputType == "bool") - { - return FunctionType.Bool; - } - else if (inputType == "guid") - { - return FunctionType.Guid; - } - else if (inputType == "datetime") - { - return FunctionType.DateTime; - } - - return FunctionType.String; - } - - internal static object ExecuteMethod(object functionClassInstance, FunctionDefinition functionDefinition, MethodInfo methodInfo) - { - object result = null; - ParameterInfo[] parameters = methodInfo.GetParameters(); - - if (parameters.Length == 0) - { - // Call the method without parameters - result = methodInfo.Invoke(functionClassInstance, null); - } - else - { - // Method requires input, so fill the parameters - List paramInput = new List(functionDefinition.Input.Count); - foreach (var param in functionDefinition.Input) - { - switch (param.Type) - { - case FunctionType.String: - { - paramInput.Add(param.Value); - break; - } - case FunctionType.Integer: - { - if (Int32.TryParse(param.Value, out Int32 i)) - { - paramInput.Add(i); - } - else - { - paramInput.Add(Int32.MinValue); - } - break; - } - case FunctionType.Guid: - { - if (Guid.TryParse(param.Value, out Guid g)) - { - paramInput.Add(g); - } - else - { - paramInput.Add(Guid.Empty); - } - break; - } - case FunctionType.DateTime: - { - if (DateTime.TryParse(param.Value, out DateTime d)) - { - paramInput.Add(d); - } - else - { - paramInput.Add(DateTime.MinValue); - } - break; - } - case FunctionType.Bool: - { - if (bool.TryParse(param.Value, out bool b)) - { - paramInput.Add(b); - } - else - { - paramInput.Add(false); - } - break; - } - } - - } - - // Call the method with parameters - result = methodInfo.Invoke(functionClassInstance, paramInput.ToArray()); - } - - // Return the method invocation result - return result; - } - #endregion - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/DocumentationAttributes.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/DocumentationAttributes.cs deleted file mode 100644 index 694f210f95..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/DocumentationAttributes.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Base attribute to document a function or selector - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public abstract class BaseFunctionDocumentationAttribute : Attribute - { - /// - /// Description of the function - /// - public string Description { get; set; } - - /// - /// Function example - /// - public string Example { get; set; } - } - - /// - /// Function documentation attribute class - /// - public sealed class FunctionDocumentationAttribute: BaseFunctionDocumentationAttribute - { - } - - /// - /// Selector documentation attribute class - /// - public sealed class SelectorDocumentationAttribute : BaseFunctionDocumentationAttribute - { - } - - /// - /// Base parameter to document a function/selector parameter - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public abstract class ParameterDocumentationAttribute: Attribute - { - /// - /// Name of the parameter - /// - public string Name { get; set; } - - /// - /// Parameter description - /// - public string Description { get; set; } - } - - /// - /// Input parameter documentation attribute - /// - public sealed class InputDocumentationAttribute: ParameterDocumentationAttribute - { - - } - - /// - /// Output parameter documentation attribute - /// - public sealed class OutputDocumentationAttribute : ParameterDocumentationAttribute - { - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/FunctionProcessor.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/FunctionProcessor.cs deleted file mode 100644 index 942fdb5519..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/FunctionProcessor.cs +++ /dev/null @@ -1,374 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint.MappingFiles; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Class that executes functions and selectors defined in the mapping - /// - public class FunctionProcessor : BaseFunctionProcessor - { - //private WebPartMapping pageTransformation; - //private List addOnTypes; - - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly SharePointFunctionsService sharePointFunctionsService; - private readonly IOptions options; - - #region Construction - - /// - /// Instantiates the function processor. Also loads the defined add-ons - /// - public FunctionProcessor(ILogger logger, - IOptions options, - SharePointFunctionsService sharePointFunctionsService, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.sharePointFunctionsService = sharePointFunctionsService ?? throw new ArgumentNullException(nameof(sharePointFunctionsService)); - this.serviceProvider = serviceProvider; - } - - #endregion - - #region Public methods - - /// - /// Initializes the Function Processor with a custom transformation context and a CSOM source context - /// - /// The current page transformation context - /// The CSOM source context - public void Init(PageTransformationContext pageTransformationContext, ClientContext sourceContext) - { - // Configure the SharePoint Function service instance - this.sharePointFunctionsService.PageTransformationContext = pageTransformationContext; - this.sharePointFunctionsService.SourceContext = sourceContext; - - // NOTE: We removed support for addons - } - - /// - /// Executes the defined functions and selectors in the provided web part - /// - /// Web Part mapping data - /// Definition of the web part to be transformed - /// The ouput of the mapping selector if there was one executed, null otherwise - public string Process(ref WebPart webPartData, WebPartEntity webPart) - { - // First process the transform functions - foreach (var property in webPartData.Properties.ToList()) - { - // No function defined, so skip - if (string.IsNullOrEmpty(property.Functions)) - { - continue; - } - - // Multiple functions can be specified using ; as delimiter - var functionsToProcess = property.Functions.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); - // Use reflection to run the functions - ExecutePropertyFunctions(functionsToProcess, webPartData, webPart, property); - } - - // Process the selector function - if (!string.IsNullOrEmpty(webPartData.Mappings.Selector)) - { - FunctionDefinition functionDefinition = ParseFunctionDefinition(webPartData.Mappings.Selector, null, webPartData, webPart); - - // Execute function - MethodInfo methodInfo = null; - object functionClassInstance = null; - - // NOTE: We removed support for addons - methodInfo = this.sharePointFunctionsService.GetType().GetMethod(functionDefinition.Name); - functionClassInstance = this.sharePointFunctionsService; - - if (methodInfo != null) - { - // Execute the selector - object result = ExecuteMethod(functionClassInstance, functionDefinition, methodInfo); - return result.ToString(); - } - } - - return null; - } - - /// - /// Executes the defined functions and selectors in the provided web part - /// - /// Web Part mapping data - /// Definition of the web part to be transformed - /// - /// The ouput of the mapping selector if there was one executed, null otherwise - public void ProcessMappingFunctions(ref WebPart webPartData, WebPartEntity webPart, Mapping webPartMapping) - { - // No function defined, so skip - if (string.IsNullOrEmpty(webPartMapping.Functions)) - { - return; - } - - // Multiple functions can be specified using ; as delimiter - var functionsToProcess = webPartMapping.Functions.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); - // Use reflection to run the functions - ExecutePropertyFunctions(functionsToProcess, webPartData, webPart, null); - } - #endregion - - #region Helper methods - private void ExecutePropertyFunctions(string[] functionsToProcess, WebPart webPartData, WebPartEntity webPart, Property property) - { - // Process each function - foreach (var function in functionsToProcess) - { - // Parse the function - FunctionDefinition functionDefinition = ParseFunctionDefinition(function, property, webPartData, webPart); - - // Execute function - MethodInfo methodInfo = null; - object functionClassInstance = null; - - // NOTE: We removed support for addons - methodInfo = this.sharePointFunctionsService.GetType().GetMethod(functionDefinition.Name); - functionClassInstance = this.sharePointFunctionsService; - - if (methodInfo != null) - { - // Execute the function - object result = ExecuteMethod(functionClassInstance, functionDefinition, methodInfo); - - if (result is string || result is bool) - { - // Update the existing web part property or add a new one - if (webPart.Properties.Keys.Contains(functionDefinition.Output.Name)) - { - webPart.Properties[functionDefinition.Output.Name] = (result is bool) ? result.ToString().ToLower() : result.ToString(); - } - else - { - webPart.Properties.Add(functionDefinition.Output.Name, (result is bool) ? result.ToString().ToLower() : result.ToString()); - } - - // Add results from the function evaluation to the web part properties mapping data so that upcoming functions can use these new properties - if (Array.FindIndex(webPartData.Properties, p => p.Name.Equals(functionDefinition.Output.Name, StringComparison.InvariantCultureIgnoreCase)) < 0) - { - UpdateWebPartDataProperties(webPartData, functionDefinition.Output.Name); - } - } - else if (result is Dictionary) - { - if (result != null) - { - var parameters = result as Dictionary; - foreach (var param in parameters) - { - // Update the existing web part property or add a new one - if (webPart.Properties.Keys.Contains(param.Key)) - { - webPart.Properties[param.Key] = result.ToString(); - } - else - { - webPart.Properties.Add(param.Key, param.Value); - } - - // Add results from the function evaluation to the web part properties mapping data so that upcoming functions can use these new properties - if (Array.FindIndex(webPartData.Properties, p => p.Name.Equals(param.Key, StringComparison.InvariantCultureIgnoreCase)) < 0) - { - UpdateWebPartDataProperties(webPartData, param.Key); - } - } - } - } - } - - } - } - - private static void UpdateWebPartDataProperties(WebPart webPartData, string functionDefinitionName) - { - List tempList = new List(); - tempList.AddRange(webPartData.Properties); - - tempList.Add(new Property() - { - Functions = "", - Name = functionDefinitionName, - Type = PropertyType.@string - }); - - webPartData.Properties = tempList.ToArray(); - } - - private static FunctionDefinition ParseFunctionDefinition(string function, Property property, WebPart webPartData, WebPartEntity webPart) - { - // Supported function syntax: - // - EncodeGuid() - // - MyLib.EncodeGuid() - // - EncodeGuid({ListId}) - // - EncodeGuid({ListId}, {Param2}) - // - StaticString('a string') - // - {ViewId} = EncodeGuid() - // - {ViewId} = EncodeGuid({ListId}) - // - {ViewId} = MyLib.EncodeGuid({ListId}) - // - {ViewId} = EncodeGuid({ListId}, {Param2}) - - FunctionDefinition def = new FunctionDefinition(); - - // Let's grab 'static' parameters and replace them with a simple value, otherwise the parsing can go wrong due to special characters inside the static string - Dictionary staticParameters = null; - if (function.IndexOf("'") > 0) - { - staticParameters = new Dictionary(); - - // Grab '' enclosed strings - var regex = new Regex(@"('(?:[^'\\]|(?:\\\\)|(?:\\\\)*\\.{1})*')"); - var matches = regex.Matches(function); - - int staticReplacement = 0; - foreach (var match in matches) - { - staticParameters.Add($"'StaticParameter{staticReplacement}'", match.ToString()); - function = function.Replace(match.ToString(), $"'StaticParameter{staticReplacement}'"); - } - } - - // Set the output parameter - string functionString = null; - if (function.IndexOf("=") > 0) - { - var split = function.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries); - FunctionParameter output = new FunctionParameter() - { - Name = split[0].Replace("{", "").Replace("}", "").Trim(), - Type = FunctionType.String - }; - - def.Output = output; - functionString = split[1].Trim(); - } - else - { - FunctionParameter output = new FunctionParameter() - { - Name = property != null ? property.Name : "SelectedMapping", - Type = FunctionType.String - }; - - def.Output = output; - functionString = function.Trim(); - } - - // Analyze the fuction - string functionName = functionString.Substring(0, functionString.IndexOf("(")); - if (functionName.IndexOf(".") > -1) - { - // This is a custom function - def.AddOn = functionName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)[0]; - def.Name = functionName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)[1]; - } - else - { - // this is an BuiltIn function - def.AddOn = ""; - def.Name = functionString.Substring(0, functionString.IndexOf("(")); - } - - def.Input = new List(); - - // Analyze the function parameters - int staticCounter = 0; - var functionParameters = functionString.Substring(functionString.IndexOf("(") + 1).Replace(")", "").Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - foreach (var functionParameter in functionParameters) - { - FunctionParameter input = new FunctionParameter(); - if (functionParameter.Contains("{") && functionParameter.Contains("}")) - { - input.Name = functionParameter.Replace("{", "").Replace("}", "").Trim(); - } - else if (functionParameter.Contains("'")) - { - input.IsStatic = true; - input.Name = $"Static_{staticCounter}"; - - if (functionParameter.StartsWith("'StaticParameter")) - { - if (staticParameters.TryGetValue(functionParameter, out string staticParameterValue)) - { - input.Value = staticParameterValue.Replace("'", ""); - } - } - else - { - input.Value = functionParameter.Replace("'", ""); - } - - staticCounter++; - } - else - { - input.Name = functionParameter.Trim(); - } - - // Populate the function parameter with a value coming from the analyzed web part - var wpProp = webPartData.Properties?.FirstOrDefault(p => p.Name.Equals(input.Name, StringComparison.CurrentCultureIgnoreCase)); - if (wpProp != null) - { - // Map types used in the model to types used in function processor - input.Type = MapType(wpProp.Type.ToString()); - if (!input.IsStatic) - { - var wpInstanceProp = webPart.Properties.FirstOrDefault(p => p.Key.Equals(input.Name, StringComparison.CurrentCultureIgnoreCase)); - input.Value = wpInstanceProp.Value; - } - def.Input.Add(input); - } - else - { - // For add-in parts we've dynamically loaded all web part properties. These properties are typically not defined - // in the mapping as they differ per add-in part and we only have one ClientWebPart in our mapping. Therefore we - // perform an additional validation with the loaded web part properties - if (webPartData.Type.GetTypeShort() == WebParts.Client.GetTypeShort()) - { - if (webPart.Properties.ContainsKey(input.Name)) - { - // We can't know the type of these dynamic properties, hence they default to string - input.Type = MapType("string"); - input.Value = webPart.Properties[input.Name]; - def.Input.Add(input); - } - else - { - // Add with empty string as value. Since we can only have one ClientWebPart mapping it's valid for a parameter to be not available. - input.Type = MapType("string"); - input.Value = ""; - def.Input.Add(input); - } - } - else - { - throw new Exception($"Parameter {input.Name} was used but is not listed as a web part property that can be used."); - } - } - } - - return def; - } - #endregion - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/MediaWebpartConfigurationException.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/MediaWebpartConfigurationException.cs deleted file mode 100644 index 58b15fd1a6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/MediaWebpartConfigurationException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Exception class thrown when a SharePoint resource (e.g. file) is not available - /// -#pragma warning disable CA1032 - public class MediaWebpartConfigurationException : Exception - { - /// - /// Throws a ResourceNotFoundException message - /// - /// Error message - public MediaWebpartConfigurationException(string message) : base(message) { } - - /// - /// Throws a ResourceNotFoundException message - /// - /// Error message - /// Inner exception object - public MediaWebpartConfigurationException(string message, Exception innerException) : base(message, innerException) { } - } -#pragma warning restore CA1032 -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/NotAvailableAtTargetException.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/NotAvailableAtTargetException.cs deleted file mode 100644 index 63b18e506d..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/NotAvailableAtTargetException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ -#pragma warning disable CA1032 - /// - /// Exception class thrown when a SharePoint object (e.g. list) is not available at the target site - /// - public class NotAvailableAtTargetException: Exception - { - /// - /// Throws a NotAvailableAtTargetException message - /// - /// Error message - public NotAvailableAtTargetException(string message): base(message) { } - - /// - /// Throws a NotAvailableAtTargetException message - /// - /// Error message - /// Inner exception object - public NotAvailableAtTargetException(string message, Exception innerException) : base(message, innerException) { } - } -#pragma warning restore CA1032 -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/PublishingFunctionProcessor.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/PublishingFunctionProcessor.cs deleted file mode 100644 index a38cef9bd7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/PublishingFunctionProcessor.cs +++ /dev/null @@ -1,302 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Function processor for publishing page transformation - /// - public class PublishingFunctionProcessor : BaseFunctionProcessor - { - /// - /// Field types - /// -#pragma warning disable CA1720 - public enum FieldType - { - /// - /// String type - /// - String = 0, - - /// - /// Bool type - /// - Bool = 1, - - /// - /// Guid type - /// - Guid = 2, - - /// - /// Integer type - /// - Integer = 3, - - /// - /// Datetime type - /// - DateTime = 4, - - /// - /// User type - /// - User = 5, - } -#pragma warning restore CA1720 - - /// - /// Name token - /// - public string NameAttributeToken - { - get { return "{@Name}"; } - } - - //private PublishingPageTransformation publishingPageTransformation; - //private ClientContext sourceClientContext; - private ListItem page; - - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly SharePointPublishingFunctionsService sharePointPublishingFunctionsService; - private readonly IOptions options; - - #region Construction - - /// - /// Instantiates the function processor. Also loads the defined add-ons - /// - public PublishingFunctionProcessor(ILogger logger, - IOptions options, - SharePointPublishingFunctionsService sharePointPublishingFunctionsService, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.sharePointPublishingFunctionsService = sharePointPublishingFunctionsService ?? throw new ArgumentNullException(nameof(sharePointPublishingFunctionsService)); - this.serviceProvider = serviceProvider; - } - - #endregion - - #region Public methods - - /// - /// Initializes the Function Processor with a custom transformation context and a CSOM source context - /// - /// The current page transformation context - /// The CSOM source context - /// The source page list item - public void Init(PageTransformationContext pageTransformationContext, ClientContext sourceContext, ListItem pageItem) - { - // Configure the SharePoint Function service instance - this.sharePointPublishingFunctionsService.PageTransformationContext = pageTransformationContext; - this.sharePointPublishingFunctionsService.SourceContext = sourceContext; - this.page = pageItem; - - // NOTE: We removed support for addons - } - - /// - /// Replaces instances of the NameAttributeToken with the provided PropertyName - /// - /// A string value containing the function definition - /// The property to replace it with - /// The newly formatted function value. - public string ResolveFunctionToken(string functions, string propertyName) - { - return Regex.Replace(functions, NameAttributeToken, propertyName, RegexOptions.IgnoreCase); - } - - /// - /// Executes a function and returns results - /// - /// Function to process - /// Field/property the function runs on - /// Type of the field/property the function will run on - /// Function output - public Tuple Process(string functions, string propertyName, FieldType propertyType) - { - string propertyKey = ""; - string propertyValue = ""; - - if (!string.IsNullOrEmpty(functions)) - { - // Updating parsing logic to allow use of {@Name} token value in the function definition - functions = ResolveFunctionToken(functions, propertyName); - - var functionDefinition = ParseFunctionDefinition(functions, propertyName, propertyType, this.page); - - // Execute function - MethodInfo methodInfo = null; - object functionClassInstance = null; - - // Native builtin function - methodInfo = this.sharePointPublishingFunctionsService.GetType().GetMethod(functionDefinition.Name); - functionClassInstance = this.sharePointPublishingFunctionsService; - - if (methodInfo != null) - { - // Execute the function - object result = ExecuteMethod(functionClassInstance, functionDefinition, methodInfo); - - // output types support: string or bool - if (result is string || result is bool) - { - propertyKey = propertyName; - propertyValue = result.ToString(); - } - } - } - - return new Tuple(propertyKey, propertyValue); - } - #endregion - - #region Helper methods - private static FunctionDefinition ParseFunctionDefinition(string function, string propertyName, FieldType propertyType, ListItem page) - { - // Supported function syntax: - // - EncodeGuid() - // - MyLib.EncodeGuid() - // - EncodeGuid({ListId}) - // - StaticString('a string') - // - EncodeGuid({ListId}, {Param2}) - // - {ViewId} = EncodeGuid() - // - {ViewId} = EncodeGuid({ListId}) - // - {ViewId} = MyLib.EncodeGuid({ListId}) - // - {ViewId} = EncodeGuid({ListId}, {Param2}) - - FunctionDefinition def = new FunctionDefinition(); - - // Let's grab 'static' parameters and replace them with a simple value, otherwise the parsing can go wrong due to special characters inside the static string - Dictionary staticParameters = null; - if (function.IndexOf("'") > 0) - { - staticParameters = new Dictionary(); - - // Grab '' enclosed strings - var regex = new Regex(@"('(?:[^'\\]|(?:\\\\)|(?:\\\\)*\\.{1})*')"); - var matches = regex.Matches(function); - - int staticReplacement = 0; - foreach (var match in matches) - { - staticParameters.Add($"'StaticParameter{staticReplacement}'", match.ToString()); - function = function.Replace(match.ToString(), $"'StaticParameter{staticReplacement}'"); - staticReplacement++; - } - } - - // Set the output parameter - string functionString = null; - if (function.IndexOf("=") > 0) - { - var split = function.Split(new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries); - FunctionParameter output = new FunctionParameter() - { - Name = split[0].Replace("{", "").Replace("}", "").Trim(), - Type = FunctionType.String - }; - - def.Output = output; - functionString = split[1].Trim(); - } - else - { - FunctionParameter output = new FunctionParameter() - { - Name = propertyName, - Type = FunctionType.String - }; - - def.Output = output; - functionString = function.Trim(); - } - - // Analyze the fuction - string functionName = functionString.Substring(0, functionString.IndexOf("(")); - if (functionName.IndexOf(".") > -1) - { - // This is a custom function - def.AddOn = functionName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)[0]; - def.Name = functionName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)[1]; - } - else - { - // this is an BuiltIn function - def.AddOn = ""; - def.Name = functionString.Substring(0, functionString.IndexOf("(")); - } - - def.Input = new List(); - - // Analyze the function parameters - int staticCounter = 0; - var functionParameters = functionString.Substring(functionString.IndexOf("(") + 1).Replace(")", "").Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - foreach (var functionParameter in functionParameters) - { - FunctionParameter input = new FunctionParameter(); - if (functionParameter.Contains("{") && functionParameter.Contains("}")) - { - input.Name = functionParameter.Replace("{", "").Replace("}", "").Trim(); - } - else if (functionParameter.Contains("'")) - { - input.IsStatic = true; - input.Name = $"Static_{staticCounter}"; - - if (functionParameter.Trim().StartsWith("'StaticParameter")) - { - if (staticParameters.TryGetValue(functionParameter.Trim(), out string staticParameterValue)) - { - input.Value = staticParameterValue.Replace("'", ""); - } - } - else - { - input.Value = functionParameter.Replace("'", ""); - } - staticCounter++; - } - else - { - input.Name = functionParameter.Trim(); - } - - // Populate the function parameter with a value coming from publishing page - input.Type = MapType(propertyType.ToString()); - - if (!input.IsStatic) - { - if (propertyType == FieldType.String) - { - input.Value = page.GetFieldValueAs(input.Name); - } - else if (propertyType == FieldType.User) - { - if (page.FieldExistsAndUsed(input.Name)) - { - input.Value = ((FieldUserValue)page[input.Name]).LookupId.ToString(); - } - } - } - def.Input.Add(input); - } - - return def; - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointFunctionsService.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointFunctionsService.cs deleted file mode 100644 index d262eda858..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointFunctionsService.cs +++ /dev/null @@ -1,1774 +0,0 @@ -using AngleSharp; -using AngleSharp.Html.Parser; -using AngleSharp.Io; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.Services; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using PnP.Core.Transformation.SharePoint.Utilities; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Internal class that implements all the built-in mapping functions - /// - public class SharePointFunctionsService - { - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly IMemoryCache memoryCache; - private readonly HtmlTransformator htmlTransformator; - private readonly IUrlMappingProvider urlMappingProvider; - private readonly IUserMappingProvider userMappingProvider; - private readonly IOptions options; - - private Regex siteUrlRegex = new Regex(@"https?:\/\/(?[\w.]*)(?[\w\/.]*)"); - private Regex siteLinkRelativeUrlRegex = new Regex(@"\/sites\/(?\w*)\/(?[\w\/.]*)"); - - #region Constructor with DI - - /// - /// SharePoint functions class constructor - /// - public SharePointFunctionsService(ILogger logger, - HtmlTransformator htmlTransformator, - IUrlMappingProvider urlMappingProvider, - IUserMappingProvider userMappingProvider, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.htmlTransformator = htmlTransformator ?? throw new ArgumentNullException(nameof(htmlTransformator)); - this.urlMappingProvider = urlMappingProvider ?? throw new ArgumentNullException(nameof(urlMappingProvider)); - this.userMappingProvider = userMappingProvider ?? throw new ArgumentNullException(nameof(userMappingProvider)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - } - - #endregion - - #region Public Properties - - /// - /// Provides an hook to the CSOM source client context - /// - public PageTransformationContext PageTransformationContext { get; set; } - - /// - /// Provides an hook to the CSOM source client context - /// - public ClientContext SourceContext { get; set; } - - #endregion - - // All functions return either a single string, boolean or a Dictionary with key value pairs. - // Allowed input parameter types are string, int, bool, DateTime and Guid - - #region Generic functions - /// - /// Html encodes a string - /// - /// Text to html encode - /// Html encoded string - [FunctionDocumentation(Description = "Returns the html encoded value of this string.", - Example = "{EncodedText} = HtmlEncode({Text})")] - [InputDocumentation(Name = "{Text}", Description = "Text to html encode")] - [OutputDocumentation(Name = "{EncodedText}", Description = "Html encoded text")] - public string HtmlEncode(string text) - { - if (string.IsNullOrEmpty(text)) - { - return ""; - } - - return System.Web.HttpUtility.HtmlEncode(text); - } - - /// - /// Html encodes string for inclusion in JSON - /// - /// Text to html encode - /// Html encoded string for inclusion in JSON - [FunctionDocumentation(Description = "Returns the json html encoded value of this string.", - Example = "{JsonEncodedText} = HtmlEncodeForJson({Text})")] - [InputDocumentation(Name = "{Text}", Description = "Text to html encode for inclusion in json")] - [OutputDocumentation(Name = "{JsonEncodedText}", Description = "Html encoded text for inclusion in json file")] - public string HtmlEncodeForJson(string text) - { - if (string.IsNullOrEmpty(text)) - { - return ""; - } - - return System.Web.HttpUtility.HtmlEncode(text).Replace(""", @"\"").Replace(":", ":").Replace("@", "%40"); - } - - /// - /// Return true - /// - /// True - [FunctionDocumentation(Description = "Simply returns the string true.", - Example = "{UsePlaceHolders} = ReturnTrue()")] - [OutputDocumentation(Name = "{UsePlaceHolders}", Description = "Value true")] - public string ReturnTrue() - { - return "true"; - } - - /// - /// Return false - /// - /// False - [FunctionDocumentation(Description = "Simply returns the string false.", - Example = "{UsePlaceHolders} = ReturnFalse()")] - [OutputDocumentation(Name = "{UsePlaceHolders}", Description = "Value false")] - public string ReturnFalse() - { - return "false"; - } - - /// - /// Transforms the incoming path into a server relative path - /// - /// Path to transform - /// Server relative path - [FunctionDocumentation(Description = "Transforms the incoming path into a server relative path.", - Example = "{ServerRelativePath} = ReturnServerRelativePath({Path})")] - [InputDocumentation(Name = "{Path}", Description = "Path to transform")] - [OutputDocumentation(Name = "{ServerRelativePath}", Description = "Server relative path")] - public string ReturnServerRelativePath(string path) - { - if (string.IsNullOrEmpty(path)) - { - return string.Empty; - } - - var hostUri = new Uri(this.SourceContext.Web.Url); - string host = $"{hostUri.Scheme}://{hostUri.DnsSafeHost}"; - - return path.Replace(host, ""); - } - - /// - /// Returns the filename of the given path - /// - /// - /// File name - [FunctionDocumentation(Description = "Returns the filename of the given path.", - Example = "{FileName} = ReturnFileName({Path})")] - [InputDocumentation(Name = "{Path}", Description = "Path to analyze")] - [OutputDocumentation(Name = "{FileName}", Description = "File name with extension from the given path")] - public string ReturnFileName(string path) - { - if (string.IsNullOrEmpty(path)) - { - return ""; - } - - if (path.Equals("/_layouts/15/clientbin/mediaplaceholder.mp4")) - { - throw new MediaWebpartConfigurationException(SharePointTransformationResources.Error_MediaWebPartNotProperlyConfigured); - } - - return Path.GetFileName(path); - } - - /// - /// Concatenates 2 strings. - /// - /// First string - /// Second string - /// Concatenation of the passed strings - [FunctionDocumentation(Description = "Concatenates 2 strings.", - Example = "{CompleteString} = Concatenate({String1},{String2})")] - [InputDocumentation(Name = "{String1}", Description = "First string")] - [InputDocumentation(Name = "{String2}", Description = "Second string")] - [OutputDocumentation(Name = "{CompleteString}", Description = "Concatenation of the passed strings")] - public string Concatenate(string string1, string string2) - { - return ConcatenateWithDelimiter(string1, string2); - } - - /// - /// Concatenates 2 strings with a semicolon in between - /// - /// First string - /// Second string - /// Concatenation of the passed strings - [FunctionDocumentation(Description = "Concatenates 2 strings with a semicolon in between.", - Example = "{CompleteString} = ConcatenateWithSemiColonDelimiter({String1},{String2})")] - [InputDocumentation(Name = "{String1}", Description = "First string")] - [InputDocumentation(Name = "{String2}", Description = "Second string")] - [OutputDocumentation(Name = "{CompleteString}", Description = "Concatenation of the passed strings")] - public string ConcatenateWithSemiColonDelimiter(string string1, string string2) - { - return ConcatenateWithDelimiter(string1, string2, ";"); - } - - /// - /// Concatenates 2 strings with a pipe character in between - /// - /// First string - /// Second string - /// Concatenation of the passed strings - [FunctionDocumentation(Description = "Concatenates 2 strings with a pipe character in between.", - Example = "{CompleteString} = ConcatenateWithSemiColonDelimiter({String1},{String2})")] - [InputDocumentation(Name = "{String1}", Description = "First string")] - [InputDocumentation(Name = "{String2}", Description = "Second string")] - [OutputDocumentation(Name = "{CompleteString}", Description = "Concatenation of the passed strings")] - public string ConcatenateWithPipeDelimiter(string string1, string string2) - { - return ConcatenateWithDelimiter(string1, string2, "|"); - } - - private string ConcatenateWithDelimiter(string string1, string string2, string delimiter = null) - { - return $"{string1 ?? string.Empty}{delimiter ?? string.Empty}{string2 ?? string.Empty}"; - } - - /// - /// Returns the (static) string provided as input - /// - /// String provided as input - [FunctionDocumentation(Description = "Returns the (static) string provided as input", - Example = "StaticString('static string')")] - [InputDocumentation(Name = "'static string'", Description = "Static input string")] - [OutputDocumentation(Name = "return value", Description = "String provided as input")] - public string StaticString(string staticString) - { - return staticString; - } - #endregion - - #region Text functions - /// - /// Selector to allow to embed a spacer instead of an empty text - /// - /// Text to evaluate - /// Text if text needs to be inserted, Spacer if text was empty and you want a spacer - [SelectorDocumentation(Description = "Allows for option to include a spacer for empty text wiki text parts.", - Example = "TextSelector({CleanedText})")] - [InputDocumentation(Name = "{CleanedText}", Description = "Client side text part compliant html (cleaned via TextCleanup function)")] - [OutputDocumentation(Name = "Text", Description = "Will be output if the provided wiki text was not considered empty")] - [OutputDocumentation(Name = "Spacer", Description = "Will be output if the provided wiki text was considered empty")] - public string TextSelector(string text) - { - if (string.IsNullOrEmpty(text)) - { - return "Text"; - } - - var empty = this.htmlTransformator.IsEmptyParagraph(text); - - if (empty) - { - return "Spacer"; - } - else - { - return "Text"; - } - } - - /// - /// Wiki html rewrite to work in RTE - /// - /// Wiki html to rewrite - /// - /// Html that's compatible with RTE - [FunctionDocumentation(Description = "Rewrites wiki page html to be compliant with the html supported by the client side text part.", - Example = "{CleanedText} = TextCleanup({Text},{UsePlaceHolders})")] - [InputDocumentation(Name = "{Text}", Description = "Original wiki html content")] - [InputDocumentation(Name = "{UsePlaceHolders}", Description = "Parameter indicating if placeholders must be included for unsupported img/iframe elements inside wiki html")] - [OutputDocumentation(Name = "{CleanedText}", Description = "Html compliant with client side text part")] - public string TextCleanup(string text, string usePlaceHolders) - { - if (string.IsNullOrEmpty(text)) - { - return ""; - } - - // Rewrite url's if needed - if (!this.options.Value.SkipUrlRewrite) - { - text = this.urlMappingProvider.MapUrlAsync( - new UrlMappingProviderInput(this.PageTransformationContext, text)) - .GetAwaiter().GetResult().Text; - } - - bool usePlaceHolder = true; - - bool.TryParse(usePlaceHolders, out usePlaceHolder); - - return this.htmlTransformator.Transform(text, usePlaceHolder); - } - - /// - /// Checks if the provided html contains JavaScript - /// - /// Html content to check - /// True is the html contains script, false otherwise - [FunctionDocumentation(Description = "Checks if the provided html contains JavaScript", - Example = "{HasScript} = ContainsScript({Text})")] - [InputDocumentation(Name = "{Text}", Description = "Html content to check")] - [OutputDocumentation(Name = "{HasScript}", Description = "True is the html contains script, false otherwise")] - public bool ContainsScript(string content) - { - if (string.IsNullOrEmpty(content)) - { - return false; - } - var config = Configuration.Default.WithDefaultLoader(new LoaderOptions { IsResourceLoadingEnabled = true }).WithCss(); - var context = BrowsingContext.New(config); - HtmlParser parser = new HtmlParser(new HtmlParserOptions { IsEmbedded = true }, context); - - try - { - var doc = parser.ParseDocument(content); - // Script information - var scriptTags = doc.All.Where(p => p.LocalName == "script"); - if (scriptTags.Any()) - { - return true; - } - } - catch { } - - return false; - } - #endregion - - #region List functions, used by XsltListViewWebPart - /// - /// Selector that returns the base type of the list as input for selecting the correct mapping - /// - /// Id of the list - /// - /// Mapping to be used for the given list - [SelectorDocumentation(Description = "Analyzes a list and returns the list base type.", - Example = "ListSelectorListLibrary({ListId})")] - [InputDocumentation(Name = "{ListId}", Description = "Guid of the list to use")] - [InputDocumentation(Name = "{ViewXml}", Description = "Definition of the selected view")] - [OutputDocumentation(Name = "Library", Description = "The list is a document library")] - [OutputDocumentation(Name = "List", Description = "The list is a document list")] - [OutputDocumentation(Name = "Issue", Description = "The list is an issue list")] - [OutputDocumentation(Name = "TaskList", Description = "The list is an task list")] - [OutputDocumentation(Name = "DiscussionBoard", Description = "The list is a discussion board")] - [OutputDocumentation(Name = "Survey", Description = "The list is a survey")] - [OutputDocumentation(Name = "Undefined", Description = "The list base type is undefined")] - public string ListSelectorListLibrary(Guid listId, string viewXml) - { - if (listId == Guid.Empty) - { - return ""; - } - else - { - if (!string.IsNullOrEmpty(viewXml)) - { - // Detect calendar based on the specific Calendar view type - if (viewXml.IndexOf("Type=\"CALENDAR\"", StringComparison.InvariantCultureIgnoreCase) > -1 || - // Detect calendar "All Events" and "Current Events" views based upon their OOB field list we're displaying - viewXml.IndexOf("", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return "Calendar"; - } - } - - var list = this.SourceContext.Web.GetListById(listId); - list.EnsureProperties(p => p.BaseType, p => p.BaseTemplate); - - // "Detailed" inspection based on template - if (list.BaseTemplate == (int)ListTemplateType.Tasks || list.BaseTemplate == (int)ListTemplateType.TasksWithTimelineAndHierarchy) - { - return "TaskList"; - } - else if (list.BaseTemplate == (int)ListTemplateType.DiscussionBoard) - { - return "DiscussionBoard"; - } - - // "Generic" inspection based on type - if (list.BaseType == BaseType.DocumentLibrary) - { - return "Library"; - } - else if (list.BaseType == BaseType.GenericList) - { - return "List"; - } - else if (list.BaseType == BaseType.Issue) - { - return "Issue"; - } - else if (list.BaseType == BaseType.DiscussionBoard) - { - return "DiscussionBoard"; - } - else if (list.BaseType == BaseType.Survey) - { - return "Survey"; - } - - return "Undefined"; - } - } - - /// - /// Returns the cross site collection save list id. - /// - /// Id of the list - /// Cross site collection safe list id - [FunctionDocumentation(Description = "Returns the cross site collection save list id.", - Example = "{ListId} = ListCrossSiteCheck({ListId})")] - [InputDocumentation(Name = "{ListId}", Description = "Guid of the list to use")] - [OutputDocumentation(Name = "{ListId}", Description = "Cross site collection safe list id")] - public string ListCrossSiteCheck(Guid listId) - { - if (listId == Guid.Empty) - { - return string.Empty; - } - else - { - var sourceList = this.SourceContext.Web.GetListById(listId); - sourceList.EnsureProperty(p => p.Title); - string listTitleToCheck = sourceList.Title; - - return $"{{TargetListIdByTitle:{listTitleToCheck}}}"; - } - } - - /// - /// Function that returns the server relative url of the given list - /// - /// Id of the list - /// Server relative url of the list - [FunctionDocumentation(Description = "Returns the server relative url of a list.", - Example = "{ListServerRelativeUrl} = ListAddServerRelativeUrl({ListId})")] - [InputDocumentation(Name = "{ListId}", Description = "Guid of the list to use")] - [OutputDocumentation(Name = "{ListServerRelativeUrl}", Description = "Server relative url of the list")] - public string ListAddServerRelativeUrl(Guid listId) - { - if (listId == Guid.Empty) - { - return ""; - } - else - { - var list = this.SourceContext.Web.GetListById(listId); - list.EnsureProperty(p => p.RootFolder).EnsureProperty(p => p.ServerRelativeUrl); - return list.RootFolder.ServerRelativeUrl; - } - } - - /// - /// Function that returns the web relative url of the given list - /// - /// Id of the list - /// Web relative url of the list - [FunctionDocumentation(Description = "Returns the web relative url of a list.", - Example = "{ListWebRelativeUrl} = ListAddWebRelativeUrl({ListId})")] - [InputDocumentation(Name = "{ListId}", Description = "Guid of the list to use")] - [OutputDocumentation(Name = "{ListWebRelativeUrl}", Description = "Web relative url of the list")] - public string ListAddWebRelativeUrl(Guid listId) - { - if (listId == Guid.Empty) - { - return ""; - } - else - { - var list = this.SourceContext.Web.GetListById(listId); - list.EnsureProperty(p => p.RootFolder).EnsureProperty(p => p.ServerRelativeUrl); - this.SourceContext.Web.EnsureProperty(p => p.ServerRelativeUrl); - - // For lists in the rootweb of the root site collection of the tenant the replacement is not needed - if (!String.IsNullOrEmpty(this.SourceContext.Web.ServerRelativeUrl.TrimEnd('/'))) - { - return list.RootFolder.ServerRelativeUrl.Replace(this.SourceContext.Web.ServerRelativeUrl.TrimEnd('/'), ""); - } - else - { - return list.RootFolder.ServerRelativeUrl; - } - } - } - - /// - /// Checks if an XSLTListView web part has a hidden toolbar - /// - /// XmlDefinition attribute of the XSLTListViewWebPart - /// Boolean indicating if the toolbar should be hidden - [FunctionDocumentation(Description = "Checks if an XSLTListView web part has a hidden toolbar.", - Example = "{HideToolBar} = ListHideToolBar({XmlDefinition})")] - [InputDocumentation(Name = "{XmlDefinition}", Description = "XmlDefinition attribute of the XSLTListViewWebPart")] - [OutputDocumentation(Name = "{HideToolBar}", Description = "Boolean indicating if the toolbar should be hidden")] - public bool ListHideToolBar(string xmlDefinition) - { - if (string.IsNullOrEmpty(xmlDefinition)) - { - return false; - } - - // Get the "identifying" elements from the webpart view xml definition - var webPartViewElement = XElement.Parse(xmlDefinition); - - var toolBar = webPartViewElement.Descendants("Toolbar").FirstOrDefault(); - if (toolBar != null) - { - string toolBarType = toolBar.Attribute("Type") != null ? toolBar.Attribute("Type").Value : null; - - if (!string.IsNullOrEmpty(toolBarType)) - { - if (toolBarType.Equals("None", StringComparison.InvariantCultureIgnoreCase)) - { - return true; - } - } - } - - return false; - } - - /// - /// Tries to find the id of the view used to configure the web part - /// - /// Id of the list - /// Webpart view definition - /// Id of the detected view if found or otherwise the id of the default list view - [FunctionDocumentation(Description = "Detects the list view id that was used by the webpart by mapping the web part xmldefinition to the list views. If no view found the list default view id is returned.", - Example = "{ListViewId} = ListDetectUsedView({ListId},{XmlDefinition})")] - [InputDocumentation(Name = "{ListId}", Description = "Guid of the list to analyze")] - [InputDocumentation(Name = "{XmlDefinition}", Description = "XmlDefinition attribute of the XSLTListViewWebPart")] - [OutputDocumentation(Name = "{ListViewId}", Description = "Id of the view to be used")] - public string ListDetectUsedView(Guid listId, string xmlDefinition) - { - if (listId == Guid.Empty || string.IsNullOrEmpty(xmlDefinition)) - { - return ""; - } - - // Grab the list and the needed properties - var list = this.SourceContext.Web.GetListById(listId); - list.EnsureProperties(l => l.DefaultView, l => l.Views.Include(v => v.Hidden, v => v.Id, v => v.ListViewXml)); - - // Get the "identifying" elements from the webpart view xml definition - var webPartViewElement = XElement.Parse(xmlDefinition); - - // Analyze the views in the list to determine a possible mapping - foreach (var view in list.Views.AsEnumerable().Where(view => !view.Hidden && view.ListViewXml != null)) - { - var viewElement = XElement.Parse(view.ListViewXml); - - // Compare Query - if (webPartViewElement.Descendants("Query").FirstOrDefault() != null && viewElement.Descendants("Query").FirstOrDefault() != null) - { - var equalNodes = XmlComparer.AreEqual(webPartViewElement.Descendants("Query").FirstOrDefault(), viewElement.Descendants("Query").FirstOrDefault()); - if (!equalNodes.Success) - { - continue; - } - } - else - { - if (!(webPartViewElement.Descendants("Query").FirstOrDefault() == null && viewElement.Descendants("Query").FirstOrDefault() != null)) - { - continue; - } - } - - // Compare viewFields - if (webPartViewElement.Descendants("ViewFields").FirstOrDefault() != null && viewElement.Descendants("ViewFields").FirstOrDefault() != null) - { - var equalNodes = XmlComparer.AreEqual(webPartViewElement.Descendants("ViewFields").FirstOrDefault(), viewElement.Descendants("ViewFields").FirstOrDefault()); - if (!equalNodes.Success) - { - continue; - } - } - else - { - if (!(webPartViewElement.Descendants("ViewFields").FirstOrDefault() == null && viewElement.Descendants("ViewFields").FirstOrDefault() != null)) - { - continue; - } - } - - // Compare RowLimit - if (webPartViewElement.Descendants("RowLimit").FirstOrDefault() != null && viewElement.Descendants("RowLimit").FirstOrDefault() != null) - { - var equalNodes = XmlComparer.AreEqual(webPartViewElement.Descendants("RowLimit").FirstOrDefault(), viewElement.Descendants("RowLimit").FirstOrDefault()); - if (!equalNodes.Success) - { - continue; - } - } - else - { - if (!(webPartViewElement.Descendants("RowLimit").FirstOrDefault() == null && viewElement.Descendants("RowLimit").FirstOrDefault() != null)) - { - continue; - } - } - - // Yeah, we're still here so we found the matching view! - return view.Id.ToString(); - } - - // No matching view found, so proceed with the default view - return list.DefaultView.Id.ToString(); - } - - #endregion - - #region Image functions - /// - /// Does return image properties based on given server relative image path - /// - /// Server relative path of the image - /// A set of image properties - [FunctionDocumentation(Description = "Does lookup a file based on the given server relative path and return needed properties of the file. Returns null if file was not found.", - Example = "ImageLookup({ServerRelativeFileName})")] - [InputDocumentation(Name = "{ServerRelativeFileName}", Description = "Server relative file name of the image")] - [OutputDocumentation(Name = "{ImageListId}", Description = "Id of the list holding the file")] - [OutputDocumentation(Name = "{ImageUniqueId}", Description = "UniqueId of the file")] - [OutputDocumentation(Name = "{ImageHeight}", Description = "Height of the image")] - [OutputDocumentation(Name = "{ImageWidth}", Description = "Width of the image")] - public Dictionary ImageLookup(string serverRelativeImagePath) - { - - bool stop = false; - if (string.IsNullOrEmpty(serverRelativeImagePath)) - { - stop = true; - } - - this.SourceContext.Web.EnsureProperty(p => p.ServerRelativeUrl); - - // Check if this url is pointing to content living in this site - if (!stop && !serverRelativeImagePath.StartsWith(this.SourceContext.Web.ServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase)) - { - // We're not looking up the image, providing the server relative path to the modern Image web part is sufficient - stop = true; - } - - Dictionary results = new Dictionary(); - - if (stop) - { - results.Add("ImageListId", ""); - results.Add("ImageUniqueId", ""); - results.Add("ImageHeight", "-1"); - results.Add("ImageWidth", "-1"); - return results; - } - - try - { - var pageHeaderImage = this.SourceContext.Web.GetFileByServerRelativeUrl(serverRelativeImagePath); - this.SourceContext.Load(pageHeaderImage, p => p.UniqueId, p => p.ListId, p => p.Properties); - this.SourceContext.ExecuteQueryRetry(); - - results.Add("ImageListId", pageHeaderImage.ListId.ToString()); - results.Add("ImageUniqueId", pageHeaderImage.UniqueId.ToString()); - - if (pageHeaderImage.Properties.FieldValues.ContainsKey("vti_lastheight")) - { - var height = pageHeaderImage.Properties.FieldValues["vti_lastheight"].ToString(); - if (string.IsNullOrEmpty(height) || height == "0") - { - height = "-1"; - } - results.Add("ImageHeight", height); - } - if (pageHeaderImage.Properties.FieldValues.ContainsKey("vti_lastwidth")) - { - var width = pageHeaderImage.Properties.FieldValues["vti_lastwidth"].ToString(); - if (string.IsNullOrEmpty(width) || width == "0") - { - width = "-1"; - } - results.Add("ImageWidth", width); - } - - return results; - } - catch (ServerException ex) - { - if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - { - // Provided image was not found, should not happen - return null; - } - else - { - throw; - } - } - } - - /// - /// Copy the asset to target site in cross site transformation - /// - /// - [FunctionDocumentation(Description = "Transforms the incoming path into a server relative path. If the page is located on another site the asset is transferred and url updated. Any failures keep to the original value.", - Example = "{ServerRelativeFileName} = ReturnCrossSiteRelativePath({ImageLink})")] - [InputDocumentation(Name = "{ImageLink}", Description = "Original value for the image link")] - [OutputDocumentation(Name = "{ServerRelativeFileName}", Description = "New target location for the asset if transferred.")] - public string ReturnCrossSiteRelativePath(string imageLink) - { - // Defaults to the orignal operation - var serverRelativeAssetFileName = ReturnServerRelativePath(imageLink); - - // Deep validation of urls - var isValid = ValidateAssetInSupportedLocation(serverRelativeAssetFileName, this.SourceContext); - - if (isValid) - { - return RetrieveImageFileRelativePath(serverRelativeAssetFileName, this.SourceContext); - } - else - { - logger.LogError( - SharePointTransformationResources.Error_ReturnCrossSiteRelativePathFailedFallback - .CorrelateString(this.PageTransformationContext.Task.Id), - imageLink); - - // Fall back to send back the same link - return imageLink; - } - } - - private string RetrieveImageFileRelativePath(string sourceAssetRelativeUrl, ClientContext context) - { - // Check the string is not null - if (!string.IsNullOrEmpty(sourceAssetRelativeUrl)) - { - // Are we dealing with an _layouts image? - if (sourceAssetRelativeUrl.ContainsIgnoringCasing("_layouts/")) - { - var siteCollectionToken = "{SiteCollection}"; - return $"{siteCollectionToken}/{sourceAssetRelativeUrl.Substring(sourceAssetRelativeUrl.IndexOf("_layouts /", StringComparison.InvariantCultureIgnoreCase))}"; - } - - var targetAssetRelativeUrl = ""; - - // Determine if the asset URL resides in the root site collection - // TODO: What if it is in a sub web not on the same web as the page - if (!sourceAssetRelativeUrl.ContainsIgnoringCasing(context.Web.ServerRelativeUrl) && - sourceAssetRelativeUrl.ContainsIgnoringCasing(context.Site.ServerRelativeUrl)) - { - string siteCollectionUrl = context.Site.EnsureProperty(o => o.Url); - var siteCollContext = context.Clone(siteCollectionUrl); - - targetAssetRelativeUrl = PersistImageFileContent(sourceAssetRelativeUrl, siteCollContext); - } - else - { - targetAssetRelativeUrl = PersistImageFileContent(sourceAssetRelativeUrl, context); - } - - logger.LogInformation( - SharePointTransformationResources.Info_ImageFilePersisted - .CorrelateString(this.PageTransformationContext.Task.Id), - sourceAssetRelativeUrl, targetAssetRelativeUrl); - - return targetAssetRelativeUrl; - } - - logger.LogError( - SharePointTransformationResources.Error_AssetTransferFailedFallback - .CorrelateString(this.PageTransformationContext.Task.Id), - sourceAssetRelativeUrl); - - // Fall back to send back the same link - return sourceAssetRelativeUrl; - } - - private string PersistImageFileContent(string sourceAssetRelativeUrl, ClientContext context, int fileChunkSizeInMB = 3) - { - // Declare the result variable - Stream sourceStream = null; - - // Retrieve the currently configured instance of the assets persistence provider - var assetPersistenceProvider = serviceProvider.GetService(); - - // Retrieve the actual file stream - var imageFile = context.Web.GetFileByServerRelativeUrl(sourceAssetRelativeUrl); - context.Load(imageFile, f => f.Exists); - context.ExecuteQueryRetry(); - - // Determine the relative URL of the file, from a site point of view - var webUrl = context.Web.EnsureProperty(w => w.ServerRelativeUrl); - var assetSiteRelativeUrl = sourceAssetRelativeUrl.Substring(webUrl.Length); - - if (imageFile.Exists) - { - ClientResult imageFileData = imageFile.OpenBinaryStream(); - context.ExecuteQueryRetry(); - sourceStream = imageFileData.Value; - - if (sourceStream != null) - { - // Determine the target file name - var fileName = sourceAssetRelativeUrl.Substring(sourceAssetRelativeUrl.LastIndexOf("/") + 1); - - // Save the stream onto the currently configured persistence storage - var persistedFilePath = assetPersistenceProvider.WriteAssetAsync(sourceStream, fileName).GetAwaiter().GetResult(); - - return $"{{AssetPersistenceProvider:{persistedFilePath}|{assetSiteRelativeUrl}}}"; - } - } - - return string.Empty; - } - - /// - /// Checks if the URL is located in a supported location - /// - private static bool ValidateAssetInSupportedLocation(string sourceUrl, ClientContext context) - { - // Referenced assets should only be files e.g. - // not aspx pages - // located in the pages, site pages libraries - - var fileExtension = Path.GetExtension(sourceUrl).ToLower(); - - // Check block list - var containsBlockedExtension = SharePointConstants.BlockedAssetFileExtensions.Any(o => o == fileExtension.Replace(".", "")); - if (containsBlockedExtension) - { - return false; - } - - // Check allow list - var containsAllowedExtension = SharePointConstants.AllowedAssetFileExtensions.Any(o => o == fileExtension.Replace(".", "")); - if (!containsAllowedExtension) - { - return false; - } - - // Additional check to see if image is outside SharePoint for OnPrem to Online scenario for root site and subsites in root site collection - if (sourceUrl.ContainsIgnoringCasing("https://") || sourceUrl.ContainsIgnoringCasing("http://")) - { - var sourceBaseUrl = sourceUrl.GetBaseUrl(); - var sourceCCBaseUrl = context.Url.GetBaseUrl(); - - if (!sourceBaseUrl.Equals(sourceCCBaseUrl, StringComparison.InvariantCultureIgnoreCase)) - { - return false; - } - } - - // Ensure the referenced assets exist within the source site collection - var sourceSiteContextUrl = context.Site.EnsureProperty(w => w.ServerRelativeUrl); - - if (!sourceUrl.ContainsIgnoringCasing(sourceSiteContextUrl)) - { - return false; - } - - return true; - } - - /// - /// Rewrite the image anchor tag url - /// - /// Original anchor tag fetched from the source image - /// Original image url - /// New image url - /// The url after url rewrite. If the anchor and original image url were the same then the anchor will be set to the new image url - [FunctionDocumentation(Description = "Rewrite the image anchor tag url.", - Example = "ImageAnchorUrlRewrite({Anchor},{ImageUrl},{ServerRelativeFileName})")] - [InputDocumentation(Name = "{Anchor}", Description = "Original anchor tag fetched from the source image")] - [InputDocumentation(Name = "{ImageUrl}", Description = "Original image url")] - [InputDocumentation(Name = "{ServerRelativeFileName}", Description = "New image url")] - [OutputDocumentation(Name = "{Anchor}", Description = "The url after url rewrite. If the anchor and original image url were the same then the anchor will be set to the new image url")] - public string ImageAnchorUrlRewrite(string anchor, string originalImageUrl, string newImageUrl) - { - string anchorRewritten; - - if (string.IsNullOrEmpty(anchor)) - { - return anchor; - } - - // if the original image url and the anchor are the same then keep them the same - if (anchor.Equals(originalImageUrl, StringComparison.InvariantCultureIgnoreCase)) - { - // Image web part does know how to correctly encode a url - return newImageUrl; - } - else - { - anchorRewritten = this.urlMappingProvider.MapUrlAsync(new UrlMappingProviderInput(this.PageTransformationContext, anchor)).GetAwaiter().GetResult().Text; - } - - return anchorRewritten; - } - #endregion - - #region First party web parts hosted on classic pages - /// - /// Extracts the client side web part properties so they can be reused - /// - /// Html defining the client side web part hosted on a classic page - /// Client side web part properties ready for reuse - [FunctionDocumentation(Description = "Extracts the client side web part properties so they can be reused.", - Example = "{JsonProperties} = ExtractWebpartProperties({ClientSideWebPartData})")] - [InputDocumentation(Name = "{ClientSideWebPartData}", Description = "Web part data defining the client side web part configuration")] - [OutputDocumentation(Name = "{JsonProperties}", Description = "Json properties to configure the client side web part")] - public string ExtractWebpartProperties(string clientSideWebPartHtml) - { - if (string.IsNullOrEmpty(clientSideWebPartHtml)) - { - return "{}"; - } - - HtmlParser parser = new HtmlParser(new HtmlParserOptions() { IsEmbedded = true }); - using (var document = parser.ParseDocument(clientSideWebPartHtml)) - { - return document.Body.FirstElementChild.GetAttribute("data-sp-webpartdata"); - } - } - #endregion - - #region DocumentEmbed functions - /// - /// Does lookup a file based on the given server relative path and return needed properties of the file. Returns null if file was not found - /// - /// Server relative file name - /// - [FunctionDocumentation(Description = "Does lookup a file based on the given server relative path and return needed properties of the file. Returns null if file was not found.", - Example = "DocumentEmbedLookup({ServerRelativeFileName})")] - [InputDocumentation(Name = "{ServerRelativeFileName}", Description = "Server relative file name")] - [OutputDocumentation(Name = "{DocumentListId}", Description = "Id of the list holding the file")] - [OutputDocumentation(Name = "{DocumentUniqueId}", Description = "UniqueId of the file")] - [OutputDocumentation(Name = "{DocumentAuthor}", Description = "User principal name of the document author")] - [OutputDocumentation(Name = "{DocumentAuthorName}", Description = "Name of the file author")] - public Dictionary DocumentEmbedLookup(string serverRelativeUrl) - { - Dictionary results = new Dictionary(); - if (string.IsNullOrEmpty(serverRelativeUrl)) - { - results.Add("DocumentWeb", ""); - results.Add("DocumentListId", ""); - results.Add("DocumentUniqueId", ""); - results.Add("DocumentAuthor", ""); - results.Add("DocumentAuthorName", ""); - return results; - } - - // Assume document lives in current web - ClientContext contextToUse = this.SourceContext; - - this.SourceContext.Web.EnsureProperty(p => p.ServerRelativeUrl); - if (!serverRelativeUrl.StartsWith(this.SourceContext.Web.ServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase)) - { - try - { - Uri hostUri = new Uri(this.SourceContext.Web.EnsureProperty(p => p.Url)); - - // Find the web url hosting the content file - var webUrlResult = Web.GetWebUrlFromPageUrl(this.SourceContext, $"{hostUri.Scheme}://{hostUri.DnsSafeHost}{serverRelativeUrl}"); - this.SourceContext.ExecuteQueryRetry(); - - contextToUse = this.SourceContext.Clone(webUrlResult.Value); - } - catch (Exception ex) - { - logger.LogError(ex, - SharePointTransformationResources.Error_DocumentEmbedLookup - .CorrelateString(this.PageTransformationContext.Task.Id)); - - results.Add("DocumentWeb", ""); - results.Add("DocumentListId", ""); - results.Add("DocumentUniqueId", ""); - results.Add("DocumentAuthor", ""); - results.Add("DocumentAuthorName", ""); - return results; - } - } - - try - { - var document = contextToUse.Web.GetFileByServerRelativeUrl(serverRelativeUrl); - contextToUse.Load(document, p => p.UniqueId, p => p.ListId, p => p.Author); - contextToUse.Load(contextToUse.Web, p => p.ServerRelativeUrl); - contextToUse.ExecuteQueryRetry(); - - string[] authorParts = document.Author.LoginName.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - - results.Add("DocumentWeb", contextToUse.Web.ServerRelativeUrl); - results.Add("DocumentListId", document.ListId.ToString()); - results.Add("DocumentUniqueId", document.UniqueId.ToString()); - results.Add("DocumentAuthor", authorParts.Length == 3 ? authorParts[2] : ""); - results.Add("DocumentAuthorName", document.Author.Title); - - return results; - } - catch (ServerException ex) - { - if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - { - // provided file is not retrievable...we're eating the exception this file not be used in the target web part - logger.LogError(ex, - SharePointTransformationResources.Error_DocumentEmbedLookupFileNotRetrievable - .CorrelateString(this.PageTransformationContext.Task.Id)); - return null; - } - else - { - logger.LogError(ex, - SharePointTransformationResources.Error_DocumentEmbedLookup - .CorrelateString(this.PageTransformationContext.Task.Id)); - throw; - } - } - } - #endregion - - #region Content Embed functions - /// - /// Analyzes sourcetype and return recommended mapping - /// - /// Sourcetype of the viewed page in pageviewerwebpart - /// - [SelectorDocumentation(Description = "Analyzes sourcetype and return recommended mapping.", - Example = "ContentEmbedSelectorSourceType({SourceType})")] - [InputDocumentation(Name = "{SourceType}", Description = "Sourcetype of the viewed page in pageviewerwebpart")] - [OutputDocumentation(Name = "WebPage", Description = "The embedded content is a page")] - [OutputDocumentation(Name = "ServerFolderOrFile", Description = "The embedded content points to a server folder or file")] - public string ContentEmbedSelectorSourceType(string sourceType) - { - if (sourceType == "4") - { - return "WebPage"; - } - - return "ServerFolderOrFile"; - } - - /// - /// Content editor can be transformed in various ways depending on whether a link was used, what file type was used, if script is used or not... - /// - /// Link value if set - /// Content embedded inside the web part - /// Text content of the file. Return empty string if file was not found - /// The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance - /// - [SelectorDocumentation(Description = "Content editor can be transformed in various ways depending on whether a link was used, what file type was used, if script is used or not...", - Example = "ContentEmbedSelectorContentLink({ContentLink}, {Content}, {FileContents}, {UseCommunityScriptEditor})")] - [InputDocumentation(Name = "{ContentLink}", Description = "Link value if set")] - [InputDocumentation(Name = "{Content}", Description = "Content embedded inside the web part")] - [InputDocumentation(Name = "{FileContents}", Description = "Text content of the file. Return empty string if file was not found")] - [InputDocumentation(Name = "{UseCommunityScriptEditor}", Description = "The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance")] - [OutputDocumentation(Name = "Link", Description = "If the link was not empty and it was an aspx file")] - [OutputDocumentation(Name = "NonASPXLink", Description = "If the link was not empty and it was not an aspx file but the file contents did contain JavaScript")] - [OutputDocumentation(Name = "NonASPXLinkNoScript", Description = "If the link was not empty and it was not an aspx file and the contents did not contain JavaScript")] - [OutputDocumentation(Name = "NonASPXUseCommunityScriptEditor", Description = "Use the community script editor to host the content")] - [OutputDocumentation(Name = "Content", Description = "If no link was specified but content was embedded and it contains JavaScript")] - [OutputDocumentation(Name = "ContentNoScript", Description = "If no link was specified and the embedded content and it does not contain JavaScript")] - [OutputDocumentation(Name = "ContentUseCommunityScriptEditor", Description = "Use the community script editor to host the content")] - public string ContentEmbedSelectorContentLink(string contentLink, string embeddedContent, string fileContent, string useCommunityScriptEditor) - { - bool.TryParse(useCommunityScriptEditor, out bool useCommunityScriptEditorBool); - - if (!string.IsNullOrEmpty(contentLink)) - { - if (contentLink.ToLower().EndsWith(".aspx")) - { - return "Link"; - } - else - { - if (!ContainsScript(fileContent)) - { - return "NonASPXLinkNoScript"; - } - else - { - if (useCommunityScriptEditorBool) - { - return "NonASPXUseCommunityScriptEditor"; - } - else - { - return "NonASPXLink"; - } - } - } - } - else - { - if (!ContainsScript(embeddedContent)) - { - return "ContentNoScript"; - } - else - { - if (useCommunityScriptEditorBool) - { - return "ContentUseCommunityScriptEditor"; - } - else - { - return "Content"; - } - } - } - } - - /// - /// Throws an exception when link to .aspx file. - /// - /// Link value if set - /// Unused variable - [FunctionDocumentation(Description = "Throws an exception when link to .aspx file.", - Example = "{Temp} = ContentEmbedCrossSiteCheck({ContentLink})")] - [InputDocumentation(Name = "{ContentLink}", Description = "Link value if set")] - [OutputDocumentation(Name = "{Temp}", Description = "Unused variable")] - public string ContentEmbedCrossSiteCheck(string contentLink) - { - - if (!IsCrossSiteTransfer() || string.IsNullOrEmpty(contentLink)) - { - return ""; - } - else - { - if (contentLink.ToLower().EndsWith(".aspx")) - { - throw new NotAvailableAtTargetException($"ASPX Page with link {contentLink} is not available in the target site collection. This web part will be skipped."); - } - } - - return ""; - } - - /// - /// Loads contents of a file as a string. - /// - /// Server relative url to the file to load - /// Text content of the file. Return empty string if file was not found - [FunctionDocumentation(Description = "Loads contents of a file as a string.", - Example = "{FileContents} = LoadContentFromFile({ContentLink})")] - [InputDocumentation(Name = "{ContentLink}", Description = "Server relative url to the file to load")] - [OutputDocumentation(Name = "{FileContents}", Description = "Text content of the file. Return empty string if file was not found")] - - public string LoadContentFromFile(string contentLink) - { - if (string.IsNullOrEmpty(contentLink) || Path.GetExtension(contentLink).Equals(".aspx", StringComparison.InvariantCultureIgnoreCase)) - { - return ""; - } - - this.SourceContext.Web.EnsureProperty(p => p.ServerRelativeUrl); - if (!contentLink.StartsWith(this.SourceContext.Web.ServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase)) - { - try - { - // Content editor does allow a web part on a sub web to point to a file in the rootweb...Pointing to files outside of the current site collection is not allowed - Uri hostUri = new Uri(this.SourceContext.Web.EnsureProperty(p => p.Url)); - - // Find the web url hosting the content file - var webUrlResult = Web.GetWebUrlFromPageUrl(this.SourceContext, $"{hostUri.Scheme}://{hostUri.DnsSafeHost}{contentLink}"); - this.SourceContext.ExecuteQueryRetry(); - - using (var cc = this.SourceContext.Clone(webUrlResult.Value)) - { - return cc.Web.GetFileAsString(contentLink); - } - } - catch (Exception ex) - { - logger.LogError(ex, - SharePointTransformationResources.Error_LoadContentFromFile - .CorrelateString(this.PageTransformationContext.Task.Id)); - return ""; - } - } - else - { - try - { - return this.SourceContext.Web.GetFileAsString(contentLink); - } - catch (ServerException ex) - { - if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - { - // Provided html was not found, should not happen but if it happens we're not stopping the transformation - logger.LogError(ex, - SharePointTransformationResources.Error_LoadContentFromFileContentLink - .CorrelateString(this.PageTransformationContext.Task.Id)); - return ""; - } - else - { - logger.LogError( - SharePointTransformationResources.Error_LoadContentFromFileContentLink - .CorrelateString(this.PageTransformationContext.Task.Id), ex); - return ""; - } - } - } - } - #endregion - - #region HighlightedContent functions - /// - /// Maps the user documents web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - /// - /// A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - [FunctionDocumentation(Description = "Maps the user documents web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part", - Example = "SiteDocumentsToHighlightedContentProperties()")] - [OutputDocumentation(Name = "JsonProperties", Description = "Properties collection for the contentrollup (= Highlighted Content) web part")] - [OutputDocumentation(Name = "SearchablePlainTexts", Description = "SearchablePlainTexts nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "Links", Description = "Links nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "ImageSources", Description = "ImageSources nodes to be added in the serverProcessedContent node")] - public Dictionary UserDocumentsToHighlightedContentProperties() - { - Dictionary results = new Dictionary(); - - ContentByQuerySearchTransformator cqs = new ContentByQuerySearchTransformator(this.SourceContext); - var res = cqs.TransformUserDocuments(); - - // Output the calculated properties so then can be used in the mapping - results.Add("JsonProperties", res.Properties); - results.Add("SearchablePlainTexts", res.SearchablePlainTexts); - results.Add("Links", res.Links); - results.Add("ImageSources", res.ImageSources); - - return results; - } - - /// - /// Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - /// - /// - /// - /// - /// - /// A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - [FunctionDocumentation(Description = "Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part", - Example = "ContentBySearchToHighlightedContentProperties({DataProviderJSON}, {SelectedPropertiesJson}, {ResultsPerPage}, {RenderTemplateId})")] - [InputDocumentation(Name = "{DataProviderJson}", Description = "")] - [InputDocumentation(Name = "{SelectedPropertiesJson}", Description = "")] - [InputDocumentation(Name = "{ResultsPerPage}", Description = "")] - [InputDocumentation(Name = "{RenderTemplateId}", Description = "")] - [OutputDocumentation(Name = "JsonProperties", Description = "Properties collection for the contentrollup (= Highlighted Content) web part")] - [OutputDocumentation(Name = "SearchablePlainTexts", Description = "SearchablePlainTexts nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "Links", Description = "Links nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "ImageSources", Description = "ImageSources nodes to be added in the serverProcessedContent node")] - public Dictionary ContentBySearchToHighlightedContentProperties(string dataProviderJson, string selectedPropertiesJson, int resultsPerPage, string renderTemplateId) - { - Dictionary results = new Dictionary(); - - ContentBySearch cbs = new ContentBySearch() - { - DataProviderJson = dataProviderJson, - SelectedPropertiesJson = selectedPropertiesJson, - ResultsPerPage = resultsPerPage, - RenderTemplateId = renderTemplateId - }; - - ContentByQuerySearchTransformator cqs = new ContentByQuerySearchTransformator(this.SourceContext); - var res = cqs.TransformContentBySearchWebPartToHighlightedContent(cbs); - - // Output the calculated properties so then can be used in the mapping - results.Add("JsonProperties", res.Properties); - results.Add("SearchablePlainTexts", res.SearchablePlainTexts); - results.Add("Links", res.Links); - results.Add("ImageSources", res.ImageSources); - - return results; - } - - /// - /// Maps content by query web part data into a properties collection for the contentrollup (= Highlighted Content) web part - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - [FunctionDocumentation(Description = "Maps content by query web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part", - Example = "ContentByQueryToHighlightedContentProperties({WebUrl},{ListGuid},{ListName},{ServerTemplate},{ContentTypeBeginsWithId},{FilterField1},{Filter1ChainingOperator},{FilterDisplayValue1},{FilterOperator1},{FilterField2},{Filter2ChainingOperator},{FilterDisplayValue2},{FilterOperator2},{FilterField3},{FilterDisplayValue3},{FilterOperator3},{SortBy},{SortByDirection},{GroupBy},{GroupByDirection},{ItemLimit},{DisplayColumns},{DataMappings})")] - [InputDocumentation(Name = "{WebUrl}", Description = "")] - [InputDocumentation(Name = "{ListGuid}", Description = "")] - [InputDocumentation(Name = "{ListName}", Description = "")] - [InputDocumentation(Name = "{ServerTemplate}", Description = "")] - [InputDocumentation(Name = "{ContentTypeBeginsWithId}", Description = "")] - [InputDocumentation(Name = "{FilterField1}", Description = "")] - [InputDocumentation(Name = "{Filter1ChainingOperator}", Description = "")] - [InputDocumentation(Name = "{FilterField1Value}", Description = "")] - [InputDocumentation(Name = "{FilterOperator1}", Description = "")] - [InputDocumentation(Name = "{FilterField2}", Description = "")] - [InputDocumentation(Name = "{Filter2ChainingOperator}", Description = "")] - [InputDocumentation(Name = "{FilterField2Value}", Description = "")] - [InputDocumentation(Name = "{FilterOperator2}", Description = "")] - [InputDocumentation(Name = "{FilterField3}", Description = "")] - [InputDocumentation(Name = "{FilterField3Value}", Description = "")] - [InputDocumentation(Name = "{FilterOperator3}", Description = "")] - [InputDocumentation(Name = "{SortBy}", Description = "")] - [InputDocumentation(Name = "{SortByDirection}", Description = "")] - [InputDocumentation(Name = "{GroupBy}", Description = "")] - [InputDocumentation(Name = "{GroupByDirection}", Description = "")] - [InputDocumentation(Name = "{ItemLimit}", Description = "")] - [InputDocumentation(Name = "{DisplayColumns}", Description = "")] - [InputDocumentation(Name = "{DataMappings}", Description = "")] - [OutputDocumentation(Name = "JsonProperties", Description = "Properties collection for the contentrollup (= Highlighted Content) web part")] - [OutputDocumentation(Name = "SearchablePlainTexts", Description = "SearchablePlainTexts nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "Links", Description = "Links nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "ImageSources", Description = "ImageSources nodes to be added in the serverProcessedContent node")] - public Dictionary ContentByQueryToHighlightedContentProperties(string webUrl, string listGuid, string listName, string serverTemplate, string contentTypeBeginsWithId, - string filterField1, string filter1ChainingOperator, string filterField1Value, string filterOperator1, - string filterField2, string filter2ChainingOperator, string filterField2Value, string filterOperator2, - string filterField3, string filterField3Value, string filterOperator3, - string sortBy, string sortByDirection, string groupBy, string groupByDirection, string itemLimit, int displayColumns, - string dataMappings) - { - Dictionary results = new Dictionary(); - - ContentByQuery cbq = new ContentByQuery() - { - WebUrl = webUrl, - ListGuid = listGuid, - ListName = listName, - ServerTemplate = serverTemplate, - ContentTypeBeginsWithId = contentTypeBeginsWithId, - - FilterField1 = filterField1, - Filter1ChainingOperator = (FilterChainingOperator)Enum.Parse(typeof(FilterChainingOperator), filter1ChainingOperator, true), - FilterField1Value = filterField1Value, - FilterOperator1 = (FilterFieldQueryOperator)Enum.Parse(typeof(FilterFieldQueryOperator), filterOperator1, true), - FilterField2 = filterField2, - Filter2ChainingOperator = (FilterChainingOperator)Enum.Parse(typeof(FilterChainingOperator), filter2ChainingOperator, true), - FilterField2Value = filterField2Value, - FilterOperator2 = (FilterFieldQueryOperator)Enum.Parse(typeof(FilterFieldQueryOperator), filterOperator2, true), - FilterField3 = filterField3, - FilterField3Value = filterField3Value, - FilterOperator3 = (FilterFieldQueryOperator)Enum.Parse(typeof(FilterFieldQueryOperator), filterOperator3, true), - - SortBy = sortBy, - SortByDirection = (SortDirection)Enum.Parse(typeof(SortDirection), sortByDirection, true), - GroupBy = groupBy, - GroupByDirection = (SortDirection)Enum.Parse(typeof(SortDirection), groupByDirection, true), - - ItemLimit = Convert.ToInt32(itemLimit), - DisplayColumns = displayColumns, - - DataMappings = dataMappings - }; - - ContentByQuerySearchTransformator cqs = new ContentByQuerySearchTransformator(this.SourceContext); - var res = cqs.TransformContentByQueryWebPartToHighlightedContent(cbq); - - // Output the calculated properties so then can be used in the mapping - results.Add("JsonProperties", res.Properties); - results.Add("SearchablePlainTexts", res.SearchablePlainTexts); - results.Add("Links", res.Links); - results.Add("ImageSources", res.ImageSources); - - return results; - } - - /// - /// Analyzes a list and returns if the list can be transformed - /// - /// Guid of the list used by the CBQ web part - /// Name of the list used by the CBQ web part - /// - [SelectorDocumentation(Description = "Analyzes a list and returns if the list can be transformed.", - Example = "ContentByQuerySelector({ListGuid},{ListName})")] - [InputDocumentation(Name = "{ListGuid}", Description = "Guid of the list used by the CBQ web part")] - [InputDocumentation(Name = "{ListName}", Description = "Name of the list used by the CBQ web part")] - [OutputDocumentation(Name = "Default", Description = "Transform the list")] - [OutputDocumentation(Name = "NoTransformation", Description = "Don't transform the list")] - public string ContentByQuerySelector(string listGuid, string listName) - { - - // Scoped to list? - Guid.TryParse(listGuid, out Guid listId); - - if (!string.IsNullOrEmpty(listName) || listId != Guid.Empty) - { - // Scope to list - List list = null; - if (listId != Guid.Empty) - { - list = this.SourceContext.Web.GetListById(listId); - } - else - { - list = this.SourceContext.Web.GetListByTitle(listName); - } - - this.SourceContext.Load(list, p => p.BaseType); - this.SourceContext.ExecuteQueryRetry(); - - if (list.BaseType != BaseType.DocumentLibrary) - { - return "NoTransformation"; - } - } - - return "Default"; - } - #endregion - - #region SummaryLink functions - /// - /// Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping - /// - /// The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance - /// Whether to transform via the QuickLinks web part or via Text - [SelectorDocumentation(Description = "Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping", - Example = "SummaryLinkSelector({SummaryLinksToQuickLinks})")] - [InputDocumentation(Name = "{SummaryLinksToQuickLinks}", Description = "The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance")] - [OutputDocumentation(Name = "UseQuickLinks", Description = "Transform to the QuickLinks web part")] - [OutputDocumentation(Name = "UseText", Description = "Transform to the formatted text")] - public string SummaryLinkSelector(string useQuickLinks) - { - if (bool.TryParse(useQuickLinks, out bool useQuickLinksBool)) - { - if (useQuickLinksBool) - { - return "UseQuickLinks"; - } - } - - return "UseText"; - } - - /// - /// Rewrites summarylinks web part html to be compliant with the html supported by the client side text part - /// - /// Original wiki html content - /// Specifies the kind of border that surrounds the webpart, see PartChromeType for a complete list of possible values - /// Webpart title - /// Html compliant with client side text part - [FunctionDocumentation(Description = "Rewrites summarylinks web part html to be compliant with the html supported by the client side text part.", - Example = "{CleanedText} = TextCleanUpSummaryLinks({Text})")] - [InputDocumentation(Name = "{Text}", Description = "Original wiki html content")] - [OutputDocumentation(Name = "{CleanedText}", Description = "Html compliant with client side text part")] - public string TextCleanUpSummaryLinks(string text, int chromeType, string title) - { - if (string.IsNullOrEmpty(text)) - { - return ""; - } - - // Add header for all but the chrometype = none and chrometype = border only - string webPartTitle = null; - if (chromeType != 2 && chromeType != 4) - { - if (!string.IsNullOrEmpty(title)) - { - webPartTitle = title; - } - } - - // Rewrite url's if needed - if (!this.options.Value.SkipUrlRewrite) - { - text = this.urlMappingProvider.MapUrlAsync(new UrlMappingProviderInput(this.PageTransformationContext, text)).GetAwaiter().GetResult().Text; - } - - var summaryLinksHtmlTransformator = new SummaryLinksHtmlTransformator - { - WebPartTitle = webPartTitle - }; - - return summaryLinksHtmlTransformator.Transform(text, false); - } - - // TODO: Update this one - /// - /// Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part - /// - /// Original wiki html content - /// - /// Properties collection for the quicklinks web part - [FunctionDocumentation(Description = "Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part", - Example = "SummaryLinksToQuickLinksProperties({Text},{QuickLinksJsonProperties})")] - [InputDocumentation(Name = "{Text}", Description = "Original wiki html content")] - [InputDocumentation(Name = "{QuickLinksJsonProperties}", Description = "QuickLinks JSON properties blob (optional)")] - [OutputDocumentation(Name = "JsonProperties", Description = "Properties collection for the quicklinks web part")] - [OutputDocumentation(Name = "SearchablePlainTexts", Description = "SearchablePlainTexts nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "Links", Description = "Links nodes to be added in the serverProcessedContent node")] - [OutputDocumentation(Name = "ImageSources", Description = "ImageSources nodes to be added in the serverProcessedContent node")] - public Dictionary SummaryLinksToQuickLinksProperties(string text, string quickLinksJsonProperties = "") - { - Dictionary results = new Dictionary(); - - var links = new SummaryLinksHtmlTransformator().GetLinks(text); - - if (IsCrossSiteTransfer()) - { - foreach (var link in links) - { - // preview images - if (!string.IsNullOrEmpty(link.ImageUrl)) - { - var serverRelativeAssetFileName = ReturnServerRelativePath(link.ImageUrl); - link.ImageUrl = PersistImageFileContent(serverRelativeAssetFileName, this.SourceContext); - } - - // urls - if (!string.IsNullOrEmpty(link.Url)) - { - var serverRelativeAssetFileName = ReturnServerRelativePath(link.Url); - if (siteLinkRelativeUrlRegex.IsMatch(serverRelativeAssetFileName)) - { - var match = siteLinkRelativeUrlRegex.Match(serverRelativeAssetFileName); - - var newAssetLocation = match.Groups["siteRelativePath"].Value; - link.Url = $"{{SiteRelativeLink:{newAssetLocation}}}"; - } - } - } - } - - // Rewrite url's if needed - if (!this.options.Value.SkipUrlRewrite) - { - foreach (var link in links) - { - var urlMappingInput = new UrlMappingProviderInput(this.PageTransformationContext, link.Url); - var urlMappingOutput = this.urlMappingProvider.MapUrlAsync(urlMappingInput).GetAwaiter().GetResult(); - - link.Url = urlMappingOutput.Text; - } - } - - QuickLinksTransformator qlt = new QuickLinksTransformator(this.SourceContext); - var res = qlt.Transform(links, quickLinksJsonProperties); - - // Output the calculated properties so then can be used in the mapping - results.Add("JsonProperties", res.Properties); - results.Add("SearchablePlainTexts", res.SearchablePlainTexts); - results.Add("Links", res.Links); - results.Add("ImageSources", res.ImageSources); - - return results; - } - #endregion - - #region Script Editor functions - /// - /// Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping - /// - /// The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance - /// Whether to transform via the community script editor web part - [SelectorDocumentation(Description = "Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping", - Example = "ScriptEditorSelector({UseCommunityScriptEditor})")] - [InputDocumentation(Name = "{UseCommunityScriptEditor}", Description = "The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance")] - [OutputDocumentation(Name = "UseCommunityScriptEditor", Description = "Transform to the community script editor web part")] - [OutputDocumentation(Name = "NoScriptEditor", Description = "Don't transform as there's no script editor")] - public string ScriptEditorSelector(string useCommunityScriptEditor) - { - if (bool.TryParse(useCommunityScriptEditor, out bool useCommunityScriptEditorBool)) - { - if (useCommunityScriptEditorBool) - { - return "UseCommunityScriptEditor"; - } - } - - return "NoScriptEditor"; - } - #endregion - - #region Contact functions - /// - /// Checks if the passed value is a user or not - /// - /// Account of the user - /// Indication if user is valid or not - [SelectorDocumentation(Description = "Checks if the passed value is a user or not", - Example = "UserExistsSelector({PersonEmail})")] - [InputDocumentation(Name = "{PersonEmail}", Description = "Account of the user")] - [OutputDocumentation(Name = "InvalidUser", Description = "User is invalid")] - [OutputDocumentation(Name = "ValidUser", Description = "User info is valid")] - public string UserExistsSelector(string person) - { - if (string.IsNullOrEmpty(person)) - { - return "InvalidUser"; - } - - return "ValidUser"; - } - - /// - /// Looks up a person from the UserInfo list and returns the needed details - /// - /// User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format) - /// Information about the found user - [FunctionDocumentation(Description = "Looks up a person from the UserInfo list and returns the needed details", - Example = "LookupPerson({ContactLoginName})")] - [InputDocumentation(Name = "{ContactLoginName}", Description = "User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format)")] - [OutputDocumentation(Name = "PersonName", Description = "Name of the user")] - [OutputDocumentation(Name = "PersonEmail", Description = "User's email")] - [OutputDocumentation(Name = "PersonUPN", Description = "UPN of the user")] - [OutputDocumentation(Name = "PersonRole", Description = "Role of the user")] - [OutputDocumentation(Name = "PersonDepartment", Description = "User's department")] - [OutputDocumentation(Name = "PersonPhone", Description = "Phone number of the user")] - [OutputDocumentation(Name = "PersonSip", Description = "SIP address of the user")] - public Dictionary LookupPerson(string person) - { - - // NOTE: So far, we removed support for On-Prem User Mapping - Dictionary result = new Dictionary(); - - if (string.IsNullOrEmpty(person)) - { - return result; - } - - person = this.userMappingProvider.MapUserAsync(new UserMappingProviderInput(this.PageTransformationContext, person)).GetAwaiter().GetResult().UserPrincipalName; - - string CAMLQueryByName = @" - - - - - - {0} - - - - "; - - List siteUserInfoList = this.SourceContext.Web.SiteUserInfoList; - CamlQuery query = new CamlQuery - { - ViewXml = string.Format(CAMLQueryByName, person) - }; - var loadedUsers = this.SourceContext.LoadQuery(siteUserInfoList.GetItems(query)); - this.SourceContext.ExecuteQueryRetry(); - - bool handled = false; - if (loadedUsers != null) - { - var loadedUser = loadedUsers.FirstOrDefault(); - if (loadedUser != null) - { - result.Add("PersonUPN", loadedUser["UserName"] != null ? loadedUser["UserName"].ToString() : ""); - result.Add("PersonRole", loadedUser["JobTitle"] != null ? loadedUser["JobTitle"].ToString() : ""); - result.Add("PersonDepartment", loadedUser["Department"] != null ? loadedUser["Department"].ToString() : ""); - result.Add("PersonPhone", loadedUser["WorkPhone"] != null ? loadedUser["WorkPhone"].ToString() : ""); - result.Add("PersonSip", loadedUser["SipAddress"] != null ? loadedUser["SipAddress"].ToString() : ""); - result.Add("PersonEmail", loadedUser["EMail"] != null ? loadedUser["EMail"].ToString() : ""); - handled = true; - } - } - - if (!handled) - { - // Fallback... - var personParts = person.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - if (personParts.Length == 3) - { - person = personParts[2]; - } - else if (personParts.Length == 2) - { - person = personParts[1]; - } - - if (person.Contains("\\")) - { - // On-premises account which was not mapped - personParts = person.Split(new string[] { "\\" }, StringSplitOptions.RemoveEmptyEntries); - if (personParts.Length == 2) - { - person = personParts[1]; - } - } - - // Important that person does not contain any characters that can mess up json serialization - - result.Add("PersonName", ""); - result.Add("PersonEmail", person); - result.Add("PersonUPN", person); - result.Add("PersonRole", ""); - result.Add("PersonDepartment", ""); - result.Add("PersonPhone", ""); - result.Add("PersonSip", ""); - } - - return result; - } - #endregion - - #region Helper methods - private bool IsCrossSiteTransfer() - { - if (this.SourceContext == null) - { - return false; - } - - var targatPageUri = this.PageTransformationContext.TargetPageUri.ToString(); - var sourceUri = this.SourceContext.Web.EnsureProperty(w => w.Url); - - string targetPageServerRelativeUrl = string.Empty; - if (!string.IsNullOrEmpty(targatPageUri) && siteUrlRegex.IsMatch(targatPageUri)) - { - var matches = siteUrlRegex.Matches(targatPageUri); - var serverRelativeUrl = matches[0].Groups["serverRelativeUrl"]; - - targetPageServerRelativeUrl = serverRelativeUrl.Value; - } - - string sourceServerRelativeUrl = string.Empty; - if (!string.IsNullOrEmpty(sourceUri) && siteUrlRegex.IsMatch(sourceUri)) - { - var matches = siteUrlRegex.Matches(sourceUri); - var serverRelativeUrl = matches[0].Groups["serverRelativeUrl"]; - - sourceServerRelativeUrl = serverRelativeUrl.Value; - } - - return (!targetPageServerRelativeUrl.StartsWith(sourceServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase)); - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointPublishingFunctionsService.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointPublishingFunctionsService.cs deleted file mode 100644 index c8ca44533e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/SharePointPublishingFunctionsService.cs +++ /dev/null @@ -1,717 +0,0 @@ -using AngleSharp.Dom; -using AngleSharp.Html.Parser; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using Microsoft.SharePoint.Client.Taxonomy; -using Newtonsoft.Json; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.Services; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Internal class that implements all the built-in mapping functions for publishing pages - /// - public class SharePointPublishingFunctionsService - { - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly IMemoryCache memoryCache; - private readonly HtmlTransformator htmlTransformator; - private readonly IUrlMappingProvider urlMappingProvider; - private readonly IUserMappingProvider userMappingProvider; - private readonly IOptions options; - private readonly SharePointFunctionsService sharePointFunctionsService; - - private Regex siteUrlRegex = new Regex(@"https?:\/\/(?[\w.]*)(?[\w\/.]*)"); - private Regex siteLinkRelativeUrlRegex = new Regex(@"\/sites\/(?\w*)\/(?[\w\/.]*)"); - - #region Constructor with DI - - /// - /// SharePoint functions class constructor - /// - public SharePointPublishingFunctionsService(ILogger logger, - HtmlTransformator htmlTransformator, - IUrlMappingProvider urlMappingProvider, - IUserMappingProvider userMappingProvider, - IOptions options, - SharePointFunctionsService sharePointFunctionsService, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.htmlTransformator = htmlTransformator ?? throw new ArgumentNullException(nameof(htmlTransformator)); - this.urlMappingProvider = urlMappingProvider ?? throw new ArgumentNullException(nameof(urlMappingProvider)); - this.userMappingProvider = userMappingProvider ?? throw new ArgumentNullException(nameof(userMappingProvider)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.sharePointFunctionsService = sharePointFunctionsService ?? throw new ArgumentNullException(nameof(sharePointFunctionsService)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - } - - #endregion - - #region Public Properties - - private PageTransformationContext pageTransformationContext; - private ClientContext sourceContext; - - /// - /// Provides an hook to the CSOM source client context - /// - public PageTransformationContext PageTransformationContext { - get - { - return this.pageTransformationContext; - } - set - { - this.pageTransformationContext = value; - this.sharePointFunctionsService.PageTransformationContext = value; - } - } - - /// - /// Provides an hook to the CSOM source client context - /// - public ClientContext SourceContext { - get - { - return this.sourceContext; - } - set - { - this.sourceContext = value; - this.sharePointFunctionsService.SourceContext = value; - } - } - - #endregion - - // All functions return either a single string, boolean or a Dictionary with key value pairs. - // Allowed input parameter types are string, int, bool, DateTime and Guid - - #region Text functions - /// - /// Returns an empty string - /// - /// Empty string - [FunctionDocumentation(Description = "Returns an empty string", - Example = "EmptyString()")] - [OutputDocumentation(Name = "return value", Description = "Empty string")] - public string EmptyString() - { - return ""; - } - - - /// - /// Returns an the (static) string provided as input - /// - /// Static string that will be returned - /// String provided as input - [FunctionDocumentation(Description = "Returns an the (static) string provided as input", - Example = "StaticString('static string')")] - [InputDocumentation(Name = "'static string'", Description = "Static input string")] - [OutputDocumentation(Name = "return value", Description = "String provided as input")] - public string StaticString(string staticString) - { - return staticString; - } - - /// - /// Prefixes a string - /// - /// Prefix to be applied - /// Value to apply the prefix to - /// Apply prefix also when the content field is empty - /// Prefixed string - [FunctionDocumentation(Description = "Prefixes the input text with another text. The applyIfContentIsEmpty parameter controls if the prefix also needs to happen when the actual content is empty", - Example = "Prefix('<H1>Prefix some extra text</H1>', {PublishingPageContent}, 'false')")] - [InputDocumentation(Name = "'prefix string'", Description = "Static input string which will be used as prefix")] - [InputDocumentation(Name = "{PublishingPageContent}", Description = "The actual publishing page HTML field content to prefix")] - [InputDocumentation(Name = "'static boolean value'", Description = "Static bool ('true', 'false') to indicate if the prefixing still needs to happen when the {PublishingPageContent} field content is emty")] - [OutputDocumentation(Name = "return value", Description = "Value of {PublishingPageContent} prefixed with the provided prefix value")] - public string Prefix(string prefix, string content, string applyIfContentIsEmpty) - { - if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(content)) - { - return prefix + content; - } - else if (string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(content)) - { - return content; - } - else if (!string.IsNullOrEmpty(prefix) && string.IsNullOrEmpty(content)) - { - if (!bool.TryParse(applyIfContentIsEmpty, out bool applyIfEmpty)) - { - applyIfEmpty = true; - } - - if (applyIfEmpty) - { - return prefix; - } - else - { - return ""; - } - } - else - { - return ""; - } - } - - /// - /// Suffixes a string - /// - /// Suffix to be applied - /// Value to apply the suffix to - /// Apply suffix also when the content field is empty - /// Prefixed string - [FunctionDocumentation(Description = "Suffixes the input text with another text. The applyIfContentIsEmpty parameter controls if the suffix also needs to happen when the actual content is empty", - Example = "Suffix('<H1>Suffix some extra text</H1>', {PublishingPageContent}, 'false')")] - [InputDocumentation(Name = "'suffix string'", Description = "Static input string which will be used as suffix")] - [InputDocumentation(Name = "{PublishingPageContent}", Description = "The actual publishing page HTML field content to suffix")] - [InputDocumentation(Name = "'static boolean value'", Description = "Static bool ('true', 'false') to indicate if the suffixing still needs to happen when the {PublishingPageContent} field content is emty")] - [OutputDocumentation(Name = "return value", Description = "Value of {PublishingPageContent} suffixed with the provided suffix value")] - public string Suffix(string suffix, string content, string applyIfContentIsEmpty) - { - if (!string.IsNullOrEmpty(suffix) && !string.IsNullOrEmpty(content)) - { - return content + suffix; - } - else if (string.IsNullOrEmpty(suffix) && !string.IsNullOrEmpty(content)) - { - return content; - } - else if (!string.IsNullOrEmpty(suffix) && string.IsNullOrEmpty(content)) - { - if (!bool.TryParse(applyIfContentIsEmpty, out bool applyIfEmpty)) - { - applyIfEmpty = true; - } - - if (applyIfEmpty) - { - return suffix; - } - else - { - return ""; - } - } - else - { - return ""; - } - } - - /// - /// Prefixes and suffixes a string - /// - /// Prefix to be applied - /// Suffix to be applied - /// Value to apply the prefix/suffix to - /// Apply prefix/suffix also when the content field is empty - /// Prefixed string - [FunctionDocumentation(Description = "Prefixes and suffixes the input text with another text. The applyIfContentIsEmpty parameter controls if the prefix/suffix also needs to happen when the actual content is empty", - Example = "PrefixAndSuffix('<H1>Prefix some extra text</H1>','<H1>Suffix some extra text</H1>',{PublishingPageContent},'false')")] - [InputDocumentation(Name = "'prefix string'", Description = "Static input string which will be used as prefix")] - [InputDocumentation(Name = "'suffix string'", Description = "Static input string which will be used as suffix")] - [InputDocumentation(Name = "{PublishingPageContent}", Description = "The actual publishing page HTML field content to prefix/suffix")] - [InputDocumentation(Name = "'static boolean value'", Description = "Static bool ('true', 'false') to indicate if the prefixing/suffixing still needs to happen when the {PublishingPageContent} field content is emty")] - [OutputDocumentation(Name = "return value", Description = "Value of {PublishingPageContent} prefixed/suffixed with the provided values")] - public string PrefixAndSuffix(string prefix, string suffix, string content, string applyIfContentIsEmpty) - { - if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix) && !string.IsNullOrEmpty(content)) - { - return prefix + content + suffix; - } - if (!string.IsNullOrEmpty(prefix) && string.IsNullOrEmpty(suffix) && !string.IsNullOrEmpty(content)) - { - return prefix + content; - } - if (string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix) && !string.IsNullOrEmpty(content)) - { - return content + suffix; - } - else if (string.IsNullOrEmpty(suffix) && string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(content)) - { - return content; - } - else if (string.IsNullOrEmpty(content)) - { - if (!bool.TryParse(applyIfContentIsEmpty, out bool applyIfEmpty)) - { - applyIfEmpty = true; - } - - if (applyIfEmpty) - { - if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix)) - { - return prefix + suffix; - } - else if (string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix)) - { - return suffix; - } - else - { - return prefix; - } - } - else - { - return ""; - } - } - else - { - return ""; - } - } - #endregion - - #region Image functions - /// - /// Returns the server relative image url of a Publishing Image field value - /// - /// Publishing Image field value - /// Server relative image url - [FunctionDocumentation(Description = "Returns the server relative image url of a Publishing Image field value.", - Example = "ToImageUrl({PublishingPageImage})")] - [InputDocumentation(Name = "{PublishingPageImage}", Description = "Publishing Image field value")] - [OutputDocumentation(Name = "return value", Description = "Server relative image url")] - public string ToImageUrl(string htmlImage) - { - // If the image string is not a html image representation then simply return the trimmed value. If an image has a link it's wrapped in an anchor tag - if (string.IsNullOrEmpty(htmlImage) || !(htmlImage.Trim().StartsWith(" - var htmlDoc = parser.ParseDocument(htmlImage); - var imgElement = htmlDoc.QuerySelectorAll("img").FirstOrDefault(); - - string imageUrl = ""; - - if (imgElement != null && imgElement != default(IElement) && imgElement.HasAttribute("src")) - { - imageUrl = imgElement.GetAttribute("src"); - - // drop of url params (if any) - if (imageUrl.Contains("?")) - { - imageUrl = imageUrl.Substring(0, imageUrl.IndexOf("?")); - } - } - - return imageUrl; - } - - /// - /// Returns the image alternate text of a Publishing Image field value. - /// - /// PublishingPageImage - /// Image alternate text - [FunctionDocumentation(Description = "Returns the image alternate text of a Publishing Image field value.", - Example = "ToImageAltText({PublishingPageImage})")] - [InputDocumentation(Name = "{PublishingPageImage}", Description = "Publishing Image field value")] - [OutputDocumentation(Name = "return value", Description = "Image alternate text")] - public string ToImageAltText(string htmlImage) - { - // If the image string is not a html image representation then simply return the trimmed value. If an image has a link it's wrapped in an anchor tag - if (string.IsNullOrEmpty(htmlImage) || !(htmlImage.Trim().StartsWith(" - var htmlDoc = parser.ParseDocument(htmlImage); - var imgElement = htmlDoc.QuerySelectorAll("img").FirstOrDefault(); - - string imageAltText = ""; - - if (imgElement != null && imgElement != default(IElement) && imgElement.HasAttribute("alt")) - { - imageAltText = imgElement.GetAttribute("alt"); - } - - return imageAltText; - } - - /// - /// Returns the image anchor url of a Publishing Image field value - /// - /// Publishing Image field value - /// Image anchor url - [FunctionDocumentation(Description = "Returns the image anchor url of a Publishing Image field value.", - Example = "ToImageAnchor({PublishingPageImage})")] - [InputDocumentation(Name = "{PublishingPageImage}", Description = "Publishing Image field value")] - [OutputDocumentation(Name = "return value", Description = "Image anchor url")] - public string ToImageAnchor(string htmlImage) - { - // If the image string is not a html image representation then simply return the trimmed value. If an image has a link it's wrapped in an anchor tag - if (string.IsNullOrEmpty(htmlImage) || !(htmlImage.Trim().StartsWith(" - var htmlDoc = parser.ParseDocument(htmlImage); - var anchorElement = htmlDoc.QuerySelectorAll("a").FirstOrDefault(); - - string imageAnchor = ""; - - if (anchorElement != null && anchorElement != default(IElement) && anchorElement.HasAttribute("href")) - { - imageAnchor = anchorElement.GetAttribute("href"); - - // drop of url params (if any) - if (imageAnchor.Contains("?")) - { - imageAnchor = imageAnchor.Substring(0, imageAnchor.IndexOf("?")); - } - } - - return imageAnchor; - } - - /// - /// Returns the image caption of a Publishing Html image caption field - /// - /// Publishing Html image caption field value - /// Image caption - [FunctionDocumentation(Description = "Returns the image caption of a Publishing Html image caption field", - Example = "ToImageCaption({PublishingImageCaption})")] - [InputDocumentation(Name = "{PublishingImageCaption}", Description = "Publishing Html image caption field value")] - [OutputDocumentation(Name = "return value", Description = "Image caption")] - public string ToImageCaption(string htmlField) - { - // If the image string is not a html image representation then simply return the trimmed value. If an image has a link it's wrapped in an anchor tag - if (string.IsNullOrEmpty(htmlField)) - { - return ""; - } - - // Sample input:

Some caption

- try - { - HtmlParser parser = new HtmlParser(); - - var htmlDoc = parser.ParseDocument(htmlField); - - string imageCaption = null; - - if (htmlDoc.FirstElementChild != null) - { - imageCaption = htmlDoc.FirstElementChild.TextContent; - } - - if (!string.IsNullOrEmpty(imageCaption)) - { - return imageCaption; - } - } - catch - { - // No need to fail for this reason... - } - - return ""; - } - - ///// - ///// Returns a page preview image url - ///// - ///// A publishing image field value or a string containing a server relative image path - ///// A formatted preview image url - //[FunctionDocumentation(Description = "Returns a page preview image url.", - // Example = "ToPreviewImageUrl({PreviewImage})")] - //[InputDocumentation(Name = "{PreviewImage}", Description = "A publishing image field value or a string containing a server relative image path")] - //[OutputDocumentation(Name = "return value", Description = "A formatted preview image url")] - //public string ToPreviewImageUrl(string image) - //{ - // if (string.IsNullOrEmpty(image)) - // { - // return ""; - // } - - // // If the image string is a html image representation - // if (image.Trim().StartsWith(" p.Id).ToString().Replace("-", ""); - // string webIdString = this.targetClientContext.Web.EnsureProperty(p => p.Id).ToString().Replace("-", ""); - // if (imageProperties.TryGetValue("ImageUniqueId", out string uniqueIdString)) - // { - // uniqueIdString = uniqueIdString.Replace("-", ""); - // string extension = System.IO.Path.GetExtension(previewServerRelativeUrl); - // if (!string.IsNullOrEmpty(extension)) - // { - // extension = extension.Replace(".", ""); - // } - - // if (!string.IsNullOrEmpty(siteIdString) && !string.IsNullOrEmpty(webIdString) && !string.IsNullOrEmpty(uniqueIdString) && !string.IsNullOrEmpty(extension)) - // { - // return $"{this.targetClientContext.Web.GetUrl()}/_layouts/15/getpreview.ashx?guidSite={siteIdString}&guidWeb={webIdString}&guidFile={uniqueIdString}&ext={extension}"; - // } - // } - - // // Something went wrong...leave preview image url blank so that the default logic during page save can still pick up a nice preview image - // return ""; - //} - #endregion - - #region Person functions - /// - /// Looks up user information for passed user id - /// - /// The id (int) of a user - /// A formatted json blob describing the user's details - [FunctionDocumentation(Description = "Looks up user information for passed user id", - Example = "ToAuthors({PublishingContact})")] - [InputDocumentation(Name = "{userId}", Description = "The id (int) of a user")] - [OutputDocumentation(Name = "return value", Description = "A formatted json blob describing the user's details")] - - public string ToAuthors(string userId) - { - if (int.TryParse(userId, out int userIdInt)) - { - // Get the user information from the source site - var author = GetUserFromUserList(this.SourceContext, userIdInt); - - // If the provided ID is a group then no point in continuing... - if (author != null && !author.IsGroup) - { - // Will this user be mapped to another user? - var newUpn = this.userMappingProvider.MapUserAsync(new UserMappingProviderInput(this.PageTransformationContext, author.LoginName)).GetAwaiter().GetResult().UserPrincipalName; - - // Drop online prefix to avoid second unneeded lookup via upn later on - if (newUpn.StartsWith("i:0#.f|membership|")) - { - newUpn = newUpn.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries)[2]; - } - - // Don't serialize null values - var jsonSerializerSettings = new JsonSerializerSettings() - { - MissingMemberHandling = MissingMemberHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - - var json = JsonConvert.SerializeObject(author, jsonSerializerSettings); - return json; - } - } - - return ""; - } - #endregion - - #region Taxonomy functions - /// - /// Populate a taxonomy field based upon provided term id's. You can configure to optionally overwrite existing values - /// - /// List of term id's to set, multiple values can also be used when the taxonomy field is configured to accept multiple terms - /// Static bool ('true', 'false') to indicate if the default term values have to be set in case the fiels already contains terms - /// String with term information needed to set the taxonomy field - /// - [FunctionDocumentation(Description = "Populate a taxonomy field based upon provided term id's. You can configure to optionally overwrite existing values", - Example = "DefaultTaxonomyFieldValue({TaxField2},'a65537e8-aa27-4b3a-bad6-f0f61f84b9f7|69524923-a5a0-44d1-b5ec-7f7c6d0ec160','true')")] - [InputDocumentation(Name = "{Taxonomy Field}", Description = "The taxonomy field to update")] - [InputDocumentation(Name = "'term ids split by |'", Description = "List of term id's to set, multiple values can also be used when the taxonomy field is configured to accept multiple terms")] - [InputDocumentation(Name = "'static boolean value'", Description = "Static bool ('true', 'false') to indicate if the default term values have to be set in case the fiels already contains terms")] - [OutputDocumentation(Name = "return value", Description = "String with term information needed to set the taxonomy field")] - public string DefaultTaxonomyFieldValue(string fieldValue, string termIdString, string overwriteString) - { - List termIds = new List(); - if (termIdString.Contains("|")) - { - string[] termIdParts = termIdString.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - termIds.AddRange(termIdParts); - } - else - { - termIds.Add(termIdString); - } - - if (!bool.TryParse(overwriteString, out bool overwrite)) - { - overwrite = false; - } - - if (!string.IsNullOrEmpty(fieldValue) && !overwrite) - { - return null; - } - - string resultingTermInfo = ""; - foreach (var term in termIds) - { - if (Guid.TryParse(term, out Guid termId)) - { - var termName = GetTermFromId(this.SourceContext, termId); - - if (termName != null) - { - if (string.IsNullOrEmpty(resultingTermInfo)) - { - resultingTermInfo = $"{{TaxonomyTerm:{termName}|{termId}}}"; - } - else - { - resultingTermInfo += $"{{TaxonomyTerm:{termName}|{termId}}}"; - } - } - } - } - - return resultingTermInfo; - } - #endregion - - #region Helper methods - - /// - /// Lookup a user from the site's user list based upon the user's id - /// - /// Context of the web holding the user list - /// Id of the user to fetch - /// A UserEntity instance holding information about the user - public UserEntity GetUserFromUserList(ClientContext context, int userListId) - { - string key = context.Web.EnsureProperty(w => w.Url); - - try - { - string CAMLQueryByName = @" - - - - - - {0} - - - - "; - - List siteUserInfoList = context.Web.SiteUserInfoList; - CamlQuery query = new CamlQuery - { - ViewXml = string.Format(CAMLQueryByName, userListId) - }; - var loadedUsers = context.LoadQuery(siteUserInfoList.GetItems(query)); - context.ExecuteQueryRetry(); - - UserEntity author = null; - if (loadedUsers != null) - { - var loadedUser = loadedUsers.FirstOrDefault(); - if (loadedUser != null) - { - if (loadedUser["Name"] == null) - { - return null; - } - - bool isGroup = loadedUser["Name"].ToString().StartsWith("c:0t.c|tenant|"); - string userUpnValue = loadedUser["Name"].ToString().GetUserName(); - - author = new UserEntity() - { - Upn = userUpnValue, - Name = loadedUser["Title"] != null ? loadedUser["Title"].ToString() : "", - Role = loadedUser["JobTitle"] != null ? loadedUser["JobTitle"].ToString() : "", - LoginName = loadedUser["Name"] != null ? loadedUser["Name"].ToString() : "", - IsGroup = isGroup || IsGroup(userUpnValue), - }; - - author.Id = $"i:0#.f|membership|{author.Upn}"; - - // return - return author; - } - } - } - catch (Exception) - { - // Logging is not needed as an "empty" ensured user is handled by the callers of this method - } - - return null; - } - - private static bool IsGroup(string loginName) - { - // Possible input - // c:0t.c|tenant|b0f984d9-e9d5-432a-bec9-896f910254ba (group in SPO) - // S-5-1-76-1812374880-3438888550-261701130-6117 (group in SPO on-premises) - - if (loginName.StartsWith("c:0t.c|tenant|") || IsSID(loginName)) - { - return true; - } - else - { - return false; - } - } - - private static bool IsSID(string loginName) - { - return Regex.IsMatch(loginName.ToUpper(), @"^S-\d-\d+-(\d+-){1,14}\d+$"); - } - - /// - /// Return information for the given term - /// - /// ClientContext object to operate on - /// Id of the term to lookup - /// Term label - public string GetTermFromId(ClientContext context, Guid termId) - { - // Lookup the provided term - TaxonomySession taxSession = TaxonomySession.GetTaxonomySession(context); - var loadedTerm = taxSession.GetTerm(termId); - context.Load(loadedTerm, p => p.Name); - context.ExecuteQueryRetry(); - - return loadedTerm.Name; - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlCompare.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlCompare.cs deleted file mode 100644 index ea460a8bab..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlCompare.cs +++ /dev/null @@ -1,225 +0,0 @@ -// Eli Algranti Copyright © 2013 -// Code taken from http://xmlspecificationcompare.codeplex.com/ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// Loosely compares XML documents for equality: - /// - /// Order of siblings in an element is ignored. - /// Text nodes are the only node in at the bottom of the tree so sibling text nodes are merged for comparison. - /// The prefix used for a namespace is ignored. - /// Comments are ignored. - /// - /// This type of comparison is useful when comparing the XML documents used as messages, configuration, etc. in various specifications. - /// - internal static class XmlComparer - { - /// - /// Compare two XML fragements - /// - /// Fragment A - /// Fragment B - /// Information about the equality of the fragments - public static XmlEqualityResult AreEqual(string xmlA, string xmlB) - { - return AreEqual(ParseXml(xmlA).Root, ParseXml(xmlB).Root); - } - - - /// - /// Compare two XML fragements - /// - /// Fragment A - /// Fragment B - /// Information about the equality of the fragments - public static XmlEqualityResult AreEqual(XElement xmlA, XElement xmlB) - { - if (xmlA == null) - throw new ArgumentNullException(nameof(xmlA), "The input Xml cannot be null"); - - if (xmlB == null) - throw new ArgumentNullException(nameof(xmlB), "The input Xml cannot be null"); - - if (!xmlA.Name.Equals(xmlB.Name)) - { - return new XmlEqualityResult - { - ErrorMessage = "Root elements do not match.", - FailObject = xmlA - }; - } - - var result = AreObjectsEqual(xmlA, xmlB); - return new XmlEqualityResult - { - ErrorMessage = result.ErrorMessage, - FailObject = result.FailObject - }; - - } - - private static InternalResult AreObjectsEqual(XElement xmlA, XElement xmlB) - { - var result = AreAttributesEqual(xmlA, xmlB); - if (result.FailObject != null) - return result; - - result = AreChildrenEqual(xmlA, xmlB); - if (result.FailObject != null) - return result; - - return AreLeafEqual(xmlA, xmlB); - } - - private static InternalResult AreAttributesEqual(XElement xmlA, XElement xmlB) - { - var attributesB = xmlB.Attributes().Where(a => !a.IsNamespaceDeclaration).ToDictionary(a => a.Name); - - var attributesA = xmlA.Attributes().Where(a => !a.IsNamespaceDeclaration).ToList(); - - if (attributesA.Count != attributesB.Count) - { - return new InternalResult - { - FailObject = xmlA, - ErrorMessage = "Element has different number of attributes" - }; - } - - foreach (var attributeA in attributesA) - { - XAttribute attributeB; - if (attributesB.TryGetValue(attributeA.Name, out attributeB) && attributeA.Value == attributeB.Value) - continue; - - return new InternalResult - { - FailObject = attributeA, - ErrorMessage = "No matching attribute found." - }; - } - - return new InternalResult(); - } - - private static InternalResult AreLeafEqual(XElement xmlA, XElement xmlB) - { - var valueA = GetElementValue(xmlA); - if (valueA == GetElementValue(xmlB)) - return new InternalResult(); - - return new InternalResult - { - FailObject = string.IsNullOrEmpty(valueA) - ? xmlA - : xmlA.Nodes().First(n => n is XText) - }; - } - - private static string GetElementValue(XElement element) - { - return string.Join("", - element.Nodes() - .Where(n => n is XText) - .Cast() - .Select(t => t is XCData ? t.Value : t.Value.Trim())); - } - - private static InternalResult AreChildrenEqual(XElement xmlA, XElement xmlB) - { - var elementsBLookup = xmlB.Elements() - .ToLookup(n => n.Name).ToDictionary(g => g.Key, g => g.ToList()); - - foreach (var childA in xmlA.Elements()) - { - List possibleMatches; - if (!elementsBLookup.TryGetValue(childA.Name, out possibleMatches)) - { - return new InternalResult - { - FailObject = childA - }; - } - - InternalResult result; - var matchIndex = FindChildMatch(childA, possibleMatches, out result); - - if (matchIndex < 0) - { - return result; - } - - if (possibleMatches.Count > 1) - { - possibleMatches.RemoveAt(matchIndex); - } - else - { - elementsBLookup.Remove(childA.Name); - } - } - - if (elementsBLookup.Count > 0) - { - return new InternalResult - { - FailObject = xmlA, - ErrorMessage = "Different number of child elements" - }; - } - - return new InternalResult(); - } - - private static int FindChildMatch(XElement child, IList possibleMatches, out InternalResult result) - { - result = new InternalResult(); - for (var i = 0; i < possibleMatches.Count; ++i) - { - result = AreObjectsEqual(child, possibleMatches[i]); - - if (result.FailObject == null) - return i; - } - - return -1; - } - - /// - /// Parses the provided XML - /// - /// Provided XML - /// Parsed XML as - /// - public static XDocument ParseXml(string xml) - { - try - { - return XDocument.Parse(xml); - } - catch (Exception e) - { - throw new ArgumentException("The string provided is not a valid XML Document.", e); - } - } - - private struct InternalResult - { - public string ErrorMessage; - public XObject FailObject; - } - - } -} - - - - - - - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlEqualityResult.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlEqualityResult.cs deleted file mode 100644 index 22af8f11f9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Functions/XmlEqualityResult.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Eli Algranti Copyright © 2013 -// Code taken from http://xmlspecificationcompare.codeplex.com/ -using System.Xml.Linq; - -namespace PnP.Core.Transformation.SharePoint.Functions -{ - /// - /// The result of an equiality comparison with - /// - internal class XmlEqualityResult - { - private const string DefaultError = "Can't find match for subtree."; - private const string SuccessMessage = "Success"; - private string errorMessage; - - /// - /// Gets whether the match was successful - /// - public bool Success - { - get { return FailObject == null; } - } - - /// - /// Gets or sets the object that failed the match - /// - public XObject FailObject { get; set; } - - /// - /// Gets or sets a descriptive error message if the match failed. - /// - /// - /// If set to null or not set the default Error Message is returned. - /// - public string ErrorMessage - { - get - { - return errorMessage ?? (Success ? SuccessMessage : DefaultError); - } - - set - { - errorMessage = value; - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.cs deleted file mode 100644 index 12f8163b91..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.cs +++ /dev/null @@ -1,1310 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by xsd, Version=4.8.3928.0. -// -namespace PnP.Core.Transformation.SharePoint.MappingFiles.Publishing -{ - using System.Xml.Serialization; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace= "http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - [System.Xml.Serialization.XmlRootAttribute(Namespace= "http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema", IsNullable=false)] - public partial class PublishingPageTransformation { - - private AddOn[] addOnsField; - - private PageLayout[] pageLayoutsField; - - private string versionField; - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public AddOn[] AddOns { - get { - return this.addOnsField; - } - set { - this.addOnsField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public PageLayout[] PageLayouts { - get { - return this.pageLayoutsField; - } - set { - this.pageLayoutsField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Version { - get { - return this.versionField; - } - set { - this.versionField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class AddOn { - - private string nameField; - - private string typeField; - - private string assemblyField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Assembly { - get { - return this.assemblyField; - } - set { - this.assemblyField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class FixedWebPartProperty { - - private string nameField; - - private WebPartProperyType typeField; - - private string valueField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public WebPartProperyType Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Value { - get { - return this.valueField; - } - set { - this.valueField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum WebPartProperyType { - - /// - @string, - - /// - @bool, - - /// - guid, - - /// - integer, - - /// - datetime, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class FixedWebPart { - - private FixedWebPartProperty[] propertyField; - - private string typeField; - - private int rowField; - - private int columnField; - - private int orderField; - - private bool orderFieldSpecified; - - /// - [System.Xml.Serialization.XmlElementAttribute("Property")] - public FixedWebPartProperty[] Property { - get { - return this.propertyField; - } - set { - this.propertyField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Row { - get { - return this.rowField; - } - set { - this.rowField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Column { - get { - return this.columnField; - } - set { - this.columnField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool OrderSpecified { - get { - return this.orderFieldSpecified; - } - set { - this.orderFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class WebPartOccurrence { - - private string typeField; - - private int rowField; - - private int columnField; - - private int orderField; - - private bool orderFieldSpecified; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Row { - get { - return this.rowField; - } - set { - this.rowField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Column { - get { - return this.columnField; - } - set { - this.columnField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool OrderSpecified { - get { - return this.orderFieldSpecified; - } - set { - this.orderFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class WebPartZone { - - private WebPartOccurrence[] webPartZoneLayoutField; - - private int zoneIndexField; - - private string zoneIdField; - - private int rowField; - - private int columnField; - - private int orderField; - - private bool orderFieldSpecified; - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public WebPartOccurrence[] WebPartZoneLayout { - get { - return this.webPartZoneLayoutField; - } - set { - this.webPartZoneLayoutField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int ZoneIndex { - get { - return this.zoneIndexField; - } - set { - this.zoneIndexField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string ZoneId { - get { - return this.zoneIdField; - } - set { - this.zoneIdField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Row { - get { - return this.rowField; - } - set { - this.rowField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Column { - get { - return this.columnField; - } - set { - this.columnField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool OrderSpecified { - get { - return this.orderFieldSpecified; - } - set { - this.orderFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class WebPartProperty { - - private string nameField; - - private WebPartProperyType typeField; - - private string functionsField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public WebPartProperyType Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Functions { - get { - return this.functionsField; - } - set { - this.functionsField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class WebPartField { - - private WebPartProperty[] propertyField; - - private string nameField; - - private string targetWebPartField; - - private string fieldIdField; - - private int rowField; - - private int columnField; - - private int orderField; - - private bool orderFieldSpecified; - - /// - [System.Xml.Serialization.XmlElementAttribute("Property")] - public WebPartProperty[] Property { - get { - return this.propertyField; - } - set { - this.propertyField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string TargetWebPart { - get { - return this.targetWebPartField; - } - set { - this.targetWebPartField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string FieldId { - get { - return this.fieldIdField; - } - set { - this.fieldIdField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Row { - get { - return this.rowField; - } - set { - this.rowField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Column { - get { - return this.columnField; - } - set { - this.columnField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool OrderSpecified { - get { - return this.orderFieldSpecified; - } - set { - this.orderFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class MetaDataField { - - private string nameField; - - private string targetFieldNameField; - - private string functionsField; - - private bool showInPagePropertiesField; - - private bool showInPagePropertiesFieldSpecified; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string TargetFieldName { - get { - return this.targetFieldNameField; - } - set { - this.targetFieldNameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Functions { - get { - return this.functionsField; - } - set { - this.functionsField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool ShowInPageProperties { - get { - return this.showInPagePropertiesField; - } - set { - this.showInPagePropertiesField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool ShowInPagePropertiesSpecified { - get { - return this.showInPagePropertiesFieldSpecified; - } - set { - this.showInPagePropertiesFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class MetaData { - - private MetaDataField[] fieldField; - - private bool showPagePropertiesField; - - private bool showPagePropertiesFieldSpecified; - - private int pagePropertiesRowField; - - private bool pagePropertiesRowFieldSpecified; - - private int pagePropertiesColumnField; - - private bool pagePropertiesColumnFieldSpecified; - - private int pagePropertiesOrderField; - - private bool pagePropertiesOrderFieldSpecified; - - /// - [System.Xml.Serialization.XmlElementAttribute("Field")] - public MetaDataField[] Field { - get { - return this.fieldField; - } - set { - this.fieldField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool ShowPageProperties { - get { - return this.showPagePropertiesField; - } - set { - this.showPagePropertiesField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool ShowPagePropertiesSpecified { - get { - return this.showPagePropertiesFieldSpecified; - } - set { - this.showPagePropertiesFieldSpecified = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int PagePropertiesRow { - get { - return this.pagePropertiesRowField; - } - set { - this.pagePropertiesRowField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool PagePropertiesRowSpecified { - get { - return this.pagePropertiesRowFieldSpecified; - } - set { - this.pagePropertiesRowFieldSpecified = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int PagePropertiesColumn { - get { - return this.pagePropertiesColumnField; - } - set { - this.pagePropertiesColumnField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool PagePropertiesColumnSpecified { - get { - return this.pagePropertiesColumnFieldSpecified; - } - set { - this.pagePropertiesColumnFieldSpecified = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int PagePropertiesOrder { - get { - return this.pagePropertiesOrderField; - } - set { - this.pagePropertiesOrderField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool PagePropertiesOrderSpecified { - get { - return this.pagePropertiesOrderFieldSpecified; - } - set { - this.pagePropertiesOrderFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class HeaderField { - - private string nameField; - - private HeaderFieldHeaderProperty headerPropertyField; - - private string functionsField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public HeaderFieldHeaderProperty HeaderProperty { - get { - return this.headerPropertyField; - } - set { - this.headerPropertyField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Functions { - get { - return this.functionsField; - } - set { - this.functionsField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum HeaderFieldHeaderProperty { - - /// - ImageServerRelativeUrl, - - /// - TopicHeader, - - /// - AlternativeText, - - /// - Authors, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class Header { - - private HeaderField[] fieldField; - - private HeaderType typeField; - - private HeaderAlignment alignmentField; - - private bool showPublishedDateField; - - private bool showPublishedDateFieldSpecified; - - /// - [System.Xml.Serialization.XmlElementAttribute("Field")] - public HeaderField[] Field { - get { - return this.fieldField; - } - set { - this.fieldField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public HeaderType Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public HeaderAlignment Alignment { - get { - return this.alignmentField; - } - set { - this.alignmentField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool ShowPublishedDate { - get { - return this.showPublishedDateField; - } - set { - this.showPublishedDateField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool ShowPublishedDateSpecified { - get { - return this.showPublishedDateFieldSpecified; - } - set { - this.showPublishedDateFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum HeaderType { - - /// - FullWidthImage, - - /// - NoImage, - - /// - ColorBlock, - - /// - CutInShape, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum HeaderAlignment { - - /// - Left, - - /// - Center, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class Section { - - private int rowField; - - private BackgroundEmphasis emphasisField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public int Row { - get { - return this.rowField; - } - set { - this.rowField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public BackgroundEmphasis Emphasis { - get { - return this.emphasisField; - } - set { - this.emphasisField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum BackgroundEmphasis { - - /// - None, - - /// - Neutral, - - /// - Soft, - - /// - Strong, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class SectionEmphasis { - - private Section[] sectionField; - - private BackgroundEmphasis verticalColumnEmphasisField; - - private bool verticalColumnEmphasisFieldSpecified; - - /// - [System.Xml.Serialization.XmlElementAttribute("Section")] - public Section[] Section { - get { - return this.sectionField; - } - set { - this.sectionField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public BackgroundEmphasis VerticalColumnEmphasis { - get { - return this.verticalColumnEmphasisField; - } - set { - this.verticalColumnEmphasisField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool VerticalColumnEmphasisSpecified { - get { - return this.verticalColumnEmphasisFieldSpecified; - } - set { - this.verticalColumnEmphasisFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public partial class PageLayout { - - private SectionEmphasis sectionEmphasisField; - - private Header headerField; - - private MetaData metaDataField; - - private WebPartField[] webPartsField; - - private WebPartZone[] webPartZonesField; - - private FixedWebPart[] fixedWebPartsField; - - private string nameField; - - private string alsoAppliesToField; - - private string associatedContentTypeField; - - private PageLayoutPageLayoutTemplate pageLayoutTemplateField; - - private bool includeVerticalColumnField; - - private bool includeVerticalColumnFieldSpecified; - - private PageLayoutPageHeader pageHeaderField; - - /// - public SectionEmphasis SectionEmphasis { - get { - return this.sectionEmphasisField; - } - set { - this.sectionEmphasisField = value; - } - } - - /// - public Header Header { - get { - return this.headerField; - } - set { - this.headerField = value; - } - } - - /// - public MetaData MetaData { - get { - return this.metaDataField; - } - set { - this.metaDataField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute("Field", IsNullable=false)] - public WebPartField[] WebParts { - get { - return this.webPartsField; - } - set { - this.webPartsField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public WebPartZone[] WebPartZones { - get { - return this.webPartZonesField; - } - set { - this.webPartZonesField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute("WebPart", IsNullable=false)] - public FixedWebPart[] FixedWebParts { - get { - return this.fixedWebPartsField; - } - set { - this.fixedWebPartsField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string AlsoAppliesTo { - get { - return this.alsoAppliesToField; - } - set { - this.alsoAppliesToField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string AssociatedContentType { - get { - return this.associatedContentTypeField; - } - set { - this.associatedContentTypeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public PageLayoutPageLayoutTemplate PageLayoutTemplate { - get { - return this.pageLayoutTemplateField; - } - set { - this.pageLayoutTemplateField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool IncludeVerticalColumn { - get { - return this.includeVerticalColumnField; - } - set { - this.includeVerticalColumnField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool IncludeVerticalColumnSpecified { - get { - return this.includeVerticalColumnFieldSpecified; - } - set { - this.includeVerticalColumnFieldSpecified = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public PageLayoutPageHeader PageHeader { - get { - return this.pageHeaderField; - } - set { - this.pageHeaderField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum PageLayoutPageLayoutTemplate { - - /// - OneColumn, - - /// - TwoColumns, - - /// - TwoColumnsWithSidebarLeft, - - /// - TwoColumnsWithSidebarRight, - - /// - TwoColumnsWithHeader, - - /// - TwoColumnsWithHeaderAndFooter, - - /// - ThreeColumns, - - /// - ThreeColumnsWithHeader, - - /// - ThreeColumnsWithHeaderAndFooter, - - /// - AutoDetect, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema")] - public enum PageLayoutPageHeader { - - /// - None, - - /// - Default, - - /// - Custom, - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xml b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xml deleted file mode 100644 index 16ade8a298..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xml +++ /dev/null @@ -1,290 +0,0 @@ - - - - - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - -
-
-
\ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xsd b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xsd deleted file mode 100644 index 6e977c0f68..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/pagelayoutmapping.xsd +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - - This is the base element of a page pagelayout transformation file. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-pagelayoutmapping.bat b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-pagelayoutmapping.bat deleted file mode 100644 index 4a8d6946fe..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-pagelayoutmapping.bat +++ /dev/null @@ -1,3 +0,0 @@ -pushd "C:\github\pnpcore\src\sdk\PnP.Core.Transformation.SharePoint\MappingFiles" -"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\xsd.exe" -c pagelayoutmapping.xsd /n:PnP.Core.Transformation.SharePoint.MappingFiles.Publishing -popd \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-webpartmapping.bat b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-webpartmapping.bat deleted file mode 100644 index aa05e0a389..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/refresh-webpartmapping.bat +++ /dev/null @@ -1,5 +0,0 @@ -pushd "C:\github\pnpcore\src\sdk\PnP.Core.Transformation.SharePoint\MappingFiles" -"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\xsd.exe" -c webpartmapping.xsd /n:PnP.Core.Transformation.SharePoint.MappingFiles -pwsh.exe -Command "(gc webpartmapping.cs) -replace 'partial class PageTransformation', 'partial class WebPartMapping' | Out-File -encoding ASCII webpartmapping.cs" -pwsh.exe -Command "$content = Get-Content webpartmapping.cs; $content[23] = ' [System.Xml.Serialization.XmlRootAttribute(Namespace=\"http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema\", IsNullable=false, ElementName = \"PageTransformation\")]'; $content | Set-Content webpartmapping.cs" -popd \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.cs deleted file mode 100644 index ead2d8aa29..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.cs +++ /dev/null @@ -1,632 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by xsd, Version=4.8.3928.0. -// -namespace PnP.Core.Transformation.SharePoint.MappingFiles { - using System.Xml.Serialization; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - [System.Xml.Serialization.XmlRootAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema", IsNullable=false, ElementName = "PageTransformation")] - public partial class WebPartMapping { - - private BaseWebPart baseWebPartField; - - private AddOn[] addOnsField; - - private WebPart[] webPartsField; - - private string versionField; - - /// - public BaseWebPart BaseWebPart { - get { - return this.baseWebPartField; - } - set { - this.baseWebPartField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public AddOn[] AddOns { - get { - return this.addOnsField; - } - set { - this.addOnsField = value; - } - } - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public WebPart[] WebParts { - get { - return this.webPartsField; - } - set { - this.webPartsField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Version { - get { - return this.versionField; - } - set { - this.versionField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class BaseWebPart { - - private Property[] propertiesField; - - private Mappings mappingsField; - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public Property[] Properties { - get { - return this.propertiesField; - } - set { - this.propertiesField = value; - } - } - - /// - public Mappings Mappings { - get { - return this.mappingsField; - } - set { - this.mappingsField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class Property { - - private string nameField; - - private PropertyType typeField; - - private string functionsField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public PropertyType Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Functions { - get { - return this.functionsField; - } - set { - this.functionsField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public enum PropertyType { - - /// - @string, - - /// - @bool, - - /// - guid, - - /// - integer, - - /// - datetime, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class WebPart { - - private Property[] propertiesField; - - private Mappings mappingsField; - - private string typeField; - - private bool crossSiteTransformationSupportedField; - - private bool crossSiteTransformationSupportedFieldSpecified; - - /// - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable=false)] - public Property[] Properties { - get { - return this.propertiesField; - } - set { - this.propertiesField = value; - } - } - - /// - public Mappings Mappings { - get { - return this.mappingsField; - } - set { - this.mappingsField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool CrossSiteTransformationSupported { - get { - return this.crossSiteTransformationSupportedField; - } - set { - this.crossSiteTransformationSupportedField = value; - } - } - - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - public bool CrossSiteTransformationSupportedSpecified { - get { - return this.crossSiteTransformationSupportedFieldSpecified; - } - set { - this.crossSiteTransformationSupportedFieldSpecified = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class Mappings { - - private Mapping[] mappingField; - - private string selectorField; - - /// - [System.Xml.Serialization.XmlElementAttribute("Mapping")] - public Mapping[] Mapping { - get { - return this.mappingField; - } - set { - this.mappingField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Selector { - get { - return this.selectorField; - } - set { - this.selectorField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class Mapping { - - private ClientSideText[] clientSideTextField; - - private ClientSideWebPart[] clientSideWebPartField; - - private string nameField; - - private bool defaultField; - - private string functionsField; - - /// - [System.Xml.Serialization.XmlElementAttribute("ClientSideText")] - public ClientSideText[] ClientSideText { - get { - return this.clientSideTextField; - } - set { - this.clientSideTextField = value; - } - } - - /// - [System.Xml.Serialization.XmlElementAttribute("ClientSideWebPart")] - public ClientSideWebPart[] ClientSideWebPart { - get { - return this.clientSideWebPartField; - } - set { - this.clientSideWebPartField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public bool Default { - get { - return this.defaultField; - } - set { - this.defaultField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Functions { - get { - return this.functionsField; - } - set { - this.functionsField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class ClientSideText { - - private string textField; - - private string orderField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Text { - get { - return this.textField; - } - set { - this.textField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType="integer")] - public string Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class ClientSideWebPart { - - private ClientSideWebPartType typeField; - - private string controlIdField; - - private string jsonControlDataField; - - private string orderField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public ClientSideWebPartType Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string ControlId { - get { - return this.controlIdField; - } - set { - this.controlIdField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string JsonControlData { - get { - return this.jsonControlDataField; - } - set { - this.jsonControlDataField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute(DataType="integer")] - public string Order { - get { - return this.orderField; - } - set { - this.orderField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public enum ClientSideWebPartType { - - /// - Custom, - - /// - Text, - - /// - ContentRollup, - - /// - BingMap, - - /// - ContentEmbed, - - /// - DocumentEmbed, - - /// - Image, - - /// - ImageGallery, - - /// - LinkPreview, - - /// - NewsFeed, - - /// - NewsReel, - - /// - News, - - /// - PowerBIReportEmbed, - - /// - QuickChart, - - /// - SiteActivity, - - /// - VideoEmbed, - - /// - YammerEmbed, - - /// - Events, - - /// - GroupCalendar, - - /// - Hero, - - /// - List, - - /// - PageTitle, - - /// - People, - - /// - QuickLinks, - - /// - Divider, - - /// - MicrosoftForms, - - /// - Spacer, - - /// - ClientWebPart, - - /// - PowerApps, - - /// - CodeSnippet, - - /// - PageFields, - - /// - Weather, - - /// - YouTube, - - /// - MyDocuments, - - /// - YammerFullFeed, - - /// - CountDown, - - /// - ListProperties, - - /// - MarkDown, - - /// - Planner, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema")] - public partial class AddOn { - - private string nameField; - - private string typeField; - - private string assemblyField; - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Name { - get { - return this.nameField; - } - set { - this.nameField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Type { - get { - return this.typeField; - } - set { - this.typeField = value; - } - } - - /// - [System.Xml.Serialization.XmlAttributeAttribute()] - public string Assembly { - get { - return this.assemblyField; - } - set { - this.assemblyField = value; - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xml b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xml deleted file mode 100644 index 0542b84b4d..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xml +++ /dev/null @@ -1,1374 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xsd b/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xsd deleted file mode 100644 index 09afdab630..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingFiles/webpartmapping.xsd +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - This is the base element of a page transformation file. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The description of a Global Unique IDentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/CodeConversion.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/CodeConversion.cs deleted file mode 100644 index cdd7842232..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/CodeConversion.cs +++ /dev/null @@ -1,175 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.MappingProviders.HtmlMapping -{ - /// - /// Methods to convert colors - /// - internal static class CodeConversion - { - /// - /// Map wiki table style to a RTE compatible style - /// - /// Code used for the wiki table style - /// RTE compatible table style - internal static string TableStyleCodeToName(int tableStyleCode) - { - //ms-rteTable-default: basic grid lines - //ms-rteTable-0: no grid - //ms-rteTable-1: table style 2, light banded: no header, alternating light gray rows, no grid - //ms-rteTable-2: table style 4, light lines: header, no alternating rows, no grid - //ms-rteTable-3: table style 5, grid: no header, no alternating rows, grid - //ms-rteTable-4: table style 6, Accent 1: header, no alternating rows, grid, blue colors - //ms-rteTable-5: table style 7, Accent 2: header, no alternating rows, grid, light blue colors - //ms-rteTable-6: table style 3, medium two tones: header with alternating blue colors, no grid - //ms-rteTable-7: table style 8, Accent 3: header, no alternating rows, grid, green colors - //ms-rteTable-8: table style 9, Accent 4: header, no alternating rows, grid, brownish colors - //ms-rteTable-9: table style 10, Accent 5: header, no alternating rows, grid, red colors - //ms-rteTable-10: table style 11, Accent 6: header, no alternating rows, grid, purple colors - - switch (tableStyleCode) - { - case 0: - case 3: - { - return "simpleTableStyleNeutral"; - } - case 1: - { - return "bandedRowTableStyleNeutral"; - } - case 2: - { - return "filledHeaderTableStyleNeutral"; - } - case 4: - case 5: - case 7: - case 8: - case 9: - case 10: - { - return "filledHeaderTableStyleTheme"; - } - case 6: - { - return "bandedRowTableStyleTheme"; - } - } - - return "borderHeaderTableStyleNeutral"; - } - - /// - /// Translates SharePoint wiki font size (e.g. ms-rtefontsize-3 means font size 3) to RTE font size name - /// - /// Wiki font size code - /// RTE font size name - internal static string FontCodeToName(int fontCode) - { - switch (fontCode) - { - case 1: //8pt - { - return "Small"; - } - case 2: //10t - { - return "Medium"; - } - case 3: //12pt - { - return "MediumPlus"; - } - case 4: //18pt - { - // Return empty as this will be mapped to default size - return ""; - } - case 5: //24pt - { - return "XxLarge"; - } - case 6: //36pt - { - return "XxxLarge"; - } - case 7: //48pt - { - return "XxLargePlus"; - } - case 8: //72pt - { - return "Super"; - } - } - - return null; - } - - /// - /// Translated SharePoint Wiki foreground color number (ms-rteforecolor-2 means number 2 is used) to RTE compatible color name - /// - /// Used color number - /// RTE color string - internal static string ColorCodeToForegroundColorName(int colorCode) - { - return colorCode switch - { - 1 => "RedDark", - 2 => "Red", - 3 => "Yellow", - 4 => "YellowLight", - 5 => "GreenLight", - 6 => "Green", - 7 => "BlueLight", - 8 => "Blue", - 9 => "BlueDark", - 10 => "Purple", - _ => null - }; - } - - /// - /// Translated SharePoint Wiki foreground theme color number (e.g. ms-rteThemeForeColor-6-1) to RTE compatible color name - /// - /// Theme color code - /// RTE color string - internal static string ThemeCodeToForegroundColorName(int themeCode) - { - return themeCode switch - { - 0 => - // 0 (light) will go to NeutralPrimary which is the default, hence returning null - null, - 1 => "ThemeSecondary", - 2 => "ThemePrimary", - 3 => "ThemeDarkAlt", - 4 => "ThemeDark", - 5 => "ThemeDarker", - _ => null - }; - } - - /// - /// Translated SharePoint Wiki background color number (ms-rtebackcolor-5 means number 5 is used) to RTE compatible color name - /// - /// Used color number - /// RTE color string - internal static string ColorCodeToBackgroundColorName(int colorCode) - { - return colorCode switch - { - 1 => "Maroon", - 2 => "Red", - 3 => "Yellow", - 4 => "Yellow", - 5 => "Green", - 6 => "Green", - 7 => "Aqua", - 8 => "Blue", - 9 => "DarkBlue", - 10 => "Purple", - _ => null - }; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/ElementExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/ElementExtensions.cs deleted file mode 100644 index 0bfbd69461..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/ElementExtensions.cs +++ /dev/null @@ -1,379 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using AngleSharp.Css.Dom; -using AngleSharp.Dom; -using AngleSharp.Html.Dom; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders.HtmlMapping -{ - /// - /// Extensions methods for - /// - internal static class ElementExtensions - { - /// - /// Gets if element is a block - /// - /// - /// - internal static bool IsBlockElement(this IElement element) - { - var tag = element.TagName.ToLower(); - if (tag == "article" || - tag == "div" || - tag == "p" || - tag == "h1" || - tag == "h2" || - tag == "h3" || - tag == "h3" || - tag == "h4" || - tag == "h5" || - tag == "h6" || - tag == "li" || - tag == "ol" || - tag == "ul" || - tag == "address" || - tag == "aside" || - tag == "canvas" || - tag == "dd" || - tag == "dl" || - tag == "dt" || - tag == "fieldset" || - tag == "figcaption" || - tag == "figure" || - tag == "footer" || - tag == "form" || - tag == "header" || - tag == "hr" || - tag == "main" || - tag == "nav" || - tag == "noscript" || - tag == "output" || - tag == "pre" || - tag == "section" || - tag == "table" || - tag == "tfoot" || - tag == "video" || - tag == "aside" || - tag == "blockquote") - { - return true; - } - - return false; - } - - /// - /// Gets if the element has strike through style - /// - /// - /// - internal static bool IsStrikeThrough(this IElement element) - { - if (element.GetStyle() != null && (!string.IsNullOrEmpty(element.GetStyle().GetTextDecoration()) && element.GetStyle().GetTextDecoration().Equals("line-through", StringComparison.InvariantCultureIgnoreCase) || - !string.IsNullOrEmpty(element.GetStyle().GetPropertyValue("text-decoration-line")) && element.GetStyle().GetPropertyValue("text-decoration-line").Equals("line-through", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - - return false; - } - - /// - /// Gets if element is underline - /// - /// - /// - internal static bool IsUnderline(this IElement element) - { - if (element.GetStyle() != null && (!string.IsNullOrEmpty(element.GetStyle().GetTextDecoration()) && element.GetStyle().GetTextDecoration().Equals("underline", StringComparison.InvariantCultureIgnoreCase) || - !string.IsNullOrEmpty(element.GetStyle().GetPropertyValue("text-decoration-line")) && element.GetStyle().GetPropertyValue("text-decoration-line").Equals("underline", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - - return false; - } - - /// - /// Returns the table as a normalized table object. Includes replacing row and col spans by actual empty cells - /// - /// Table to normalize - /// Normalized table - internal static Table NormalizeTable(this IElement table) - { - // Determine the table dimension, item1 = max cols / item 2 = max rows - var tableDimension = GetTableDimensions(table); - - // Create table object - Table normalizedTable = new Table() - { - ClassName = table.ClassName, - }; - - // A 2 dimensional array to hold the normalized table cells - IElementCell[,] cells = new IElementCell[tableDimension.Item2, tableDimension.Item1]; - // An array to hold the header cells (if any) - IElementCell[] headerCells = new IElementCell[tableDimension.Item1]; - - var tableBodyElement = (table as IHtmlTableElement).Bodies[0]; - var rows = tableBodyElement.Children.Where(p => p.TagName.Equals("tr", StringComparison.InvariantCultureIgnoreCase)); - if (rows != null && rows.Any()) - { - int rowPos = 0; - foreach (var row in rows) - { - // We assume a table header only has TH cells - var tableHeaders = row.Children.Where(p => p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - var cellsInTableHeader = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase)); - if (tableHeaders != null && tableHeaders.Any() && !cellsInTableHeader.Any()) - { - int colPos = 0; - foreach (var tableHeader in tableHeaders) - { - headerCells[colPos] = new IElementCell(tableHeader); - - //Check header styles - if (tableHeader.GetStyle() != null && !string.IsNullOrEmpty(tableHeader.GetStyle().GetTextAlign())) - { - headerCells[colPos].TextAlign = tableHeader.GetStyle().GetTextAlign(); - } - - // Prep for next cell - colPos++; - - var colSpan = tableHeader.GetAttribute("colspan"); - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd)) - { - for (int i = 0; i < columnCellsToAdd - 1; i++) - { - headerCells[colPos] = new IElementCell(null); - colPos++; - } - } - } - } - else - { - // Support model of TD cells mixed with TH - var tableCells = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase) || p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - if (tableCells != null && tableCells.Any()) - { - int colPos = 0; - foreach (var tableCell in tableCells) - { - - if (cells[rowPos, colPos] != null) - { - // cells was already filled due to previous row span expansion. Find next free cell on this row - while ((cells[rowPos, colPos] != null) && colPos < tableDimension.Item1) - { - colPos++; - } - } - - cells[rowPos, colPos] = new IElementCell(tableCell); - - //Check header styles - if (tableCell.GetStyle() != null && !string.IsNullOrEmpty(tableCell.GetStyle().GetTextAlign())) - { - cells[rowPos, colPos].TextAlign = tableCell.GetStyle().GetTextAlign(); - } - - // Prep for next cell - colPos++; - - var colSpan = tableCell.GetAttribute("colspan"); - var rowSpan = tableCell.GetAttribute("rowspan"); - - // We do have both col and row span set - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd) && - !string.IsNullOrEmpty(rowSpan) && rowSpan != "1" && int.TryParse(rowSpan, out int rowCellsToAdd)) - { - int tempRowPos = rowPos; - for (int j = 0; j < rowCellsToAdd; j++) - { - int tempColPos = colPos - 1; - for (int i = 0; i < columnCellsToAdd; i++) - { - if (cells[tempRowPos, tempColPos] == null) - { - cells[tempRowPos, tempColPos] = new IElementCell(null); - } - tempColPos++; - } - tempRowPos++; - } - } - else - { - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd2)) - { - for (int i = 0; i < columnCellsToAdd2 - 1; i++) - { - cells[rowPos, colPos] = new IElementCell(null); - colPos++; - } - } - - if (!string.IsNullOrEmpty(rowSpan) && rowSpan != "1" && int.TryParse(rowSpan, out int rowCellsToAdd2)) - { - int tempRowPos = rowPos + 1; - for (int i = 0; i < rowCellsToAdd2 - 1; i++) - { - cells[tempRowPos, colPos - 1] = new IElementCell(null); - tempRowPos++; - } - } - } - } - - // Only increase row pos for actual rows as header is handled differently - rowPos++; - } - else - { - // Deals with rows formatted without TD or TH tags (e.g. ) - rowPos++; - } - } - } - } - - normalizedTable.Header = headerCells; - normalizedTable.Cells = cells; - - // Let's assume there's badly shaped tables as well...fill the empty cells with an empty cell value so they're correctly replaced - for (int rowPos = 0; rowPos < normalizedTable.Cells.GetLength(0); rowPos += 1) - { - for (int colPos = 0; colPos < normalizedTable.Cells.GetLength(1); colPos += 1) - { - if (normalizedTable.Cells[rowPos, colPos] == null) - { - normalizedTable.Cells[rowPos, colPos] = new IElementCell(null); - } - } - } - - if (normalizedTable.HasHeader) - { - for (int colPos = 0; colPos < normalizedTable.Header.GetLength(0); colPos += 1) - { - if (normalizedTable.Header[colPos] == null) - { - normalizedTable.Header[colPos] = new IElementCell(null); - } - } - } - - // Determine insertion point - if (table.ParentElement != null) - { - var startNode = table.ParentElement; - IElement lastTableElement = null; - - // walk up the parent tree - while (startNode != null) - { - if (startNode.TagName.Equals("table", StringComparison.InvariantCultureIgnoreCase)) - { - lastTableElement = startNode; - } - startNode = startNode.ParentElement; - } - - if (lastTableElement != null) - { - // This table was nested, let's take the parent element of the 'highest' table as insertion point - normalizedTable.InsertionPoint = lastTableElement.ParentElement; - } - - } - - return normalizedTable; - } - - /// - /// Gets the dimensions of a table, excluding the header - /// - /// Table to investigate - /// Tuple containing the columns and rows - private static (int columns, int rows) GetTableDimensions(IElement table) - { - int maxCols = 0; - int maxRows = 0; - bool hasHeader = false; - - var tableBodyElement = (table as IHtmlTableElement).Bodies[0]; - var rows = tableBodyElement.Children.Where(p => p.TagName.Equals("tr", StringComparison.InvariantCultureIgnoreCase)); - if (rows != null && rows.Any()) - { - maxRows = rows.Count(); - foreach (var row in rows) - { - // Do we have a header - var tableHeaders = row.Children.Where(p => p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - - var tdInHeader = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase)); - int tdCellsInHeader = 0; - if (tdInHeader != null) - { - tdCellsInHeader = tdInHeader.Count(); - } - - if (tableHeaders != null && tableHeaders.Any() && tdCellsInHeader == 0) - { - hasHeader = true; - } - - // It's allowed to mix TH and TD in a table row - var tableCells = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase) || p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - if (tableCells != null && tableCells.Any()) - { - int colCount = 0; - foreach (var tableCell in tableCells) - { - var colSpan = tableCell.GetAttribute("colspan"); - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1") - { - if (int.TryParse(colSpan, out int columnCellsToAdd)) - { - colCount = colCount + columnCellsToAdd; - } - else - { - colCount++; - } - } - else - { - colCount++; - } - } - - if (colCount > maxCols) - { - maxCols = colCount; - } - } - } - } - - if (hasHeader) - { - // remove one row as header is handled separately - maxRows--; - } - - // If there's a TR then there must be one cell - if (maxRows > 0 && maxCols == 0) - { - maxCols = 1; - } - - return (maxCols, maxRows); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/Table.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/Table.cs deleted file mode 100644 index 570c0fc664..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/HtmlMapping/Table.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using AngleSharp.Dom; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders.HtmlMapping -{ - internal class Table - { - internal string ClassName { get; set; } - internal IElement InsertionPoint { get; set; } - internal IElementCell[] Header { get; set; } - internal IElementCell[,] Cells { get; set; } - - internal bool HasHeader - { - get - { - if (this.Header == null) - { - return false; - } - - for (int colPos = 0; colPos < this.Header.Length; colPos += 1) - { - if (this.Header[colPos] != null) - { - return true; - } - } - - return false; - } - } - } - - internal class IElementCell - { - internal bool HasValue - { - get - { - return Element != null; - } - } - internal IElement Element { get; set; } - - internal IElementCell(IElement element) - { - this.Element = element; - } - - internal string TextAlign { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointHtmlMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointHtmlMappingProvider.cs deleted file mode 100644 index f5fc887e9b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointHtmlMappingProvider.cs +++ /dev/null @@ -1,1116 +0,0 @@ -using AngleSharp; -using AngleSharp.Css.Dom; -using AngleSharp.Dom; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using AngleSharp.Io; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingProviders.HtmlMapping; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointHtmlMappingProvider : IHtmlMappingProvider - { - private readonly ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - - private const int DefaultTableWidth = 800; - private readonly HtmlParser parser; - - private Guid taskId; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointHtmlMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - - // Instantiate the AngleSharp Html parser, configure to load the Style property as well - var config = Configuration.Default.WithDefaultLoader(new LoaderOptions { IsResourceLoadingEnabled = true }).WithCss(); - var context = BrowsingContext.New(config); - parser = new HtmlParser(new HtmlParserOptions { IsEmbedded = true }, context); - } - /// - /// Maps HTML content from classic to modern - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - public Task MapHtmlAsync(HtmlMappingProviderInput input, CancellationToken token = default) - { - this.taskId = input.Context.Task.Id; - - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapHtmlAsync" - .CorrelateString(this.taskId)); - - string text = input.HtmlContent; - - // Strip out the "zero width space characters" - text = text.Replace("\u200B", string.Empty); - text = text.Replace("\u200b", string.Empty); - - using var document = this.parser.ParseDocument(text); - // Process headings: RTE does h2, h3, h4 while wiki does h1, h2, h3, h4. Wiki h4 to h6 will become (formatted) text - TransformHeadings(document, 6, 0); - TransformHeadings(document, 5, 0); - TransformHeadings(document, 4, 0); - TransformHeadings(document, 3, 4); - TransformHeadings(document, 2, 3); - TransformHeadings(document, 1, 2); - - // Process blockquotes - TransformBlockQuotes(document.QuerySelectorAll("blockquote"), document); - - // Process elements that can hold forecolor, backcolor, fontsize, underline and strikethrough information - TransformElements(document.QuerySelectorAll("span"), document); - TransformElements(document.QuerySelectorAll("sup"), document); - TransformElements(document.QuerySelectorAll("sub"), document); - TransformElements(document.QuerySelectorAll("strong"), document); - TransformElements(document.QuerySelectorAll("em"), document); - TransformElements(document.QuerySelectorAll("p"), document); - - - // Process image and iframes ==> put a place holder text as these will be dropped by RTE on edit mode - if (input.UsePlaceHolders) - { - AddPlaceHolders(document); - } - - // Process tables - TransformTables(document.QuerySelectorAll("table"), document); - - // Finalize the transformation by cleaning the html - // - styling information: RTE does only support a limited set of styles - // - html nodes that are not supported in RTE (clean/replace nodes but avoid dropping relevant content) - CleanStyles(document); - CleanHtmlNodes(document); - - // Return the transformed html - if (document.DocumentElement.Children.Length > 1) - { - text = document.DocumentElement.Children[1].InnerHtml; - } - - return Task.FromResult(new HtmlMappingProviderOutput(text)); - } - - /// - /// Clean html nodes - /// - /// - protected virtual void CleanHtmlNodes(IHtmlDocument document) - { - if (document == null) throw new ArgumentNullException(nameof(document)); - - // HR tag --> replace by BR - foreach (var element in document.All.Where(p => p.TagName.Equals("hr", StringComparison.InvariantCultureIgnoreCase))) - { - if (element.ParentElement != null) - { - var container = document.CreateElement("span"); - container.AppendChild(document.CreateElement("br")); - container.AppendChild(document.CreateElement("br")); - element.ParentElement.ReplaceChild(container, element); - } - } - } - - /// - /// Clean css stylesheet - /// - /// - protected virtual void CleanStyles(IHtmlDocument document) - { - if (document == null) throw new ArgumentNullException(nameof(document)); - - foreach (var element in document.All.Where(p => p.HasAttribute("style"))) - { - - if (string.IsNullOrEmpty(element.GetAttribute("style"))) - { - // If the style attribute was empty then drop it - element.RemoveAttribute("style"); - } - else - { - if (element.IsBlockElement()) - { - // Save the styles we want to maintain - string marginLeft = element.GetStyle().GetMarginLeft(); - string textAlign = element.GetStyle().GetTextAlign(); - - // Delete all styling information from the element - element.RemoveAttribute("style"); - - // Add the "styles to keep" again - if (!string.IsNullOrEmpty(marginLeft)) - { - element.GetStyle().SetMarginLeft(marginLeft); - } - if (!string.IsNullOrEmpty(textAlign)) - { - element.GetStyle().SetTextAlign(textAlign); - } - } - else - { - // Save the styles we want to maintain - string width = element.GetStyle().GetWidth(); - string textAlign = element.GetStyle().GetTextAlign(); - - // Delete all styling information from the element - element.RemoveAttribute("style"); - - // Add the "styles to keep" again - if (!string.IsNullOrEmpty(width)) - { - element.GetStyle().SetWidth(width); - } - if (!string.IsNullOrEmpty(textAlign)) - { - element.GetStyle().SetTextAlign(textAlign); - } - } - } - } - } - - /// - /// Transform tables - /// - /// - /// - protected virtual void TransformTables(IHtmlCollection tables, IHtmlDocument document) - { - if (tables == null) throw new ArgumentNullException(nameof(tables)); - if (document == null) throw new ArgumentNullException(nameof(document)); - - List> tableReplaceList = new List>(); - - foreach (var table in tables) - { - Table normalizedTable = null; - - try - { - // Normalize table by removing the col and row spans and returning a Table object containing matrix of cells, header cells and table information - normalizedTable = table.NormalizeTable(); - } - catch (Exception ex) - { - logger.LogWarning( - string.Format("{0} - {1}", - SharePointTransformationResources.Warning_TableCouldNotBeNormalized, ex) - .CorrelateString(this.taskId)); - } - - // If we could not normalize this table then let's skip it - if (normalizedTable == null) - { - continue; - } - - // Skip empty tables - if (normalizedTable.Cells.GetLength(0) == 0 && normalizedTable.Cells.GetLength(1) == 0) - { - continue; - } - - //
- var newTableElement = document.CreateElement($"div"); - newTableElement.ClassName = "canvasRteResponsiveTable"; - - //
- var innerDiv = document.CreateElement("div"); - // Possible alignments: tableLeftAlign, tableCenterAlign and tableRightAlign, since wiki does not have this option default to left align - innerDiv.ClassList.Add(new string[] { "tableLeftAlign", "tableWrapper" }); - newTableElement.AppendChild(innerDiv); - - // - var tableElement = document.CreateElement("table"); - //ms-rteTable-default: basic grid lines - string tableClassName = "borderHeaderTableStyleNeutral"; - if (!string.IsNullOrEmpty(normalizedTable.ClassName)) - { - if (normalizedTable.ClassName.Equals("ms-rteTable-default", StringComparison.InvariantCultureIgnoreCase)) - { - tableClassName = "borderHeaderTableStyleNeutral"; - } - else - { - if (int.TryParse(normalizedTable.ClassName.ToLower().Replace("ms-rtetable-", ""), out int tableStyleCode)) - { - tableClassName = CodeConversion.TableStyleCodeToName(tableStyleCode); - } - } - } - - tableElement.ClassName = tableClassName; - tableElement.SetAttribute("title", "Table"); - innerDiv.AppendChild(tableElement); - - // - var tableBody = document.CreateElement("tbody"); - tableElement.AppendChild(tableBody); - - // Table cell width - var cellWidth = GetDefaultCellTableCellWidths(normalizedTable.Cells.GetLength(1)); - - // Table headers - if (normalizedTable.HasHeader) - { - // Header is transformed into a row with bold formatted headers - var newRow = document.CreateElement("tr"); - int headerCounter = 0; - - for (int colPos = 0; colPos < normalizedTable.Header.Length; colPos += 1) - { - var tableHeaderValue = document.CreateElement("strong"); - if (normalizedTable.Header[colPos].HasValue) - { - tableHeaderValue.TextContent = normalizedTable.Header[colPos].Element.TextContent; - } - - var tableHeaderCell = document.CreateElement("td"); - tableHeaderCell.GetStyle().SetWidth($"{cellWidth[headerCounter]}px"); - - // Restore alignment if needed - if (normalizedTable.Header[colPos].TextAlign != null) - { - tableHeaderCell.GetStyle().SetTextAlign(normalizedTable.Header[colPos].TextAlign); - } - - headerCounter++; - - tableHeaderCell.AppendChild(tableHeaderValue); - newRow.AppendChild(tableHeaderCell); - } - - // Append the new header as table row - tableBody.AppendChild(newRow); - } - - // Iterate the table rows - for (int rowPos = 0; rowPos < normalizedTable.Cells.GetLength(0); rowPos += 1) - { - var newRow = document.CreateElement("tr"); - int cellCounter = 0; - - for (int colPos = 0; colPos < normalizedTable.Cells.GetLength(1); colPos += 1) - { - var newTableCell = document.CreateElement("td"); - newTableCell.GetStyle().SetWidth($"{cellWidth[cellCounter]}px"); - cellCounter++; - - // Did we put a "real" cell? - if (normalizedTable.Cells[rowPos, colPos].HasValue) - { - // Copy over the content, take over html content as cell can have formatting inside - // Formatting of the table cell content was already done in previous steps, so we simply copy what we have - newTableCell.InnerHtml = normalizedTable.Cells[rowPos, colPos].Element.InnerHtml; - } - - // Restore alignment if needed - if (normalizedTable.Cells[rowPos, colPos].TextAlign != null) - { - newTableCell.GetStyle().SetTextAlign(normalizedTable.Cells[rowPos, colPos].TextAlign); - } - - newRow.AppendChild(newTableCell); - } - - // Append the new row - tableBody.AppendChild(newRow); - } - - // Add table to list for doing actual replacements once we're done analyzing all tables - tableReplaceList.Add(new Tuple(table, newTableElement, normalizedTable.InsertionPoint)); - } - - foreach (var tableToReplace in tableReplaceList.Where(p => p.Item3 != null)) - { - // Insert the new table at the insertion point. Add a br to work around an RTE bug that you can't separate tables - tableToReplace.Item3.AppendChild(document.CreateElement("br")); - tableToReplace.Item3.AppendChild(tableToReplace.Item2); - - // Remove the old table - tableToReplace.Item1.Parent.RemoveChild(tableToReplace.Item1); - } - - foreach (var tableToReplace in tableReplaceList.Where(p => p.Item3 == null)) - { - // Swap old table with new table - tableToReplace.Item1.Parent.ReplaceChild(tableToReplace.Item2, tableToReplace.Item1); - } - - // Drop any nested table - foreach (var tableToCheck in tableReplaceList) - { - var nestedTables = tableToCheck.Item2.QuerySelectorAll("table"); - if (nestedTables.Any()) - { - foreach (var nestedTable in nestedTables.ToList()) - { - // the new Table element has a DIV as top node, an inner DIV and then the table as child - if (tableToCheck.Item2.FirstElementChild != null && tableToCheck.Item2.FirstElementChild.FirstElementChild != null) - { - if (nestedTable != tableToCheck.Item2.FirstElementChild.FirstElementChild) - { - nestedTable.ParentElement.RemoveChild(nestedTable); - } - } - } - } - } - } - - /// - /// Apply placeholder - /// - /// - protected virtual void AddPlaceHolders(IHtmlDocument document) - { - if (document == null) throw new ArgumentNullException(nameof(document)); - - var images = document.QuerySelectorAll("img"); - var iframes = document.QuerySelectorAll("iframe"); - var elements = images.Union(iframes); - - foreach (var element in elements) - { - // Add a text content in place of the element - string webPartType = ""; - string sourceValue = ""; - var source = element.Attributes.Where(p => p.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (source != null) - { - sourceValue = source.Value; - } - if (element is IHtmlImageElement) - { - webPartType = "Image"; - } - else if (element is IHtmlInlineFrameElement) - { - webPartType = "IFrame"; - } - - string placeHolder = $"***{webPartType} placeholder for source {sourceValue}***"; - - // Create P element and insert it just before our current image or iframe element - var newElement = document.CreateElement($"P"); - newElement.TextContent = placeHolder; - - if (element.Parent != null) - { - element.Parent.InsertBefore(newElement, element); - } - } - } - - /// - /// Transform block quotes - /// - /// - /// - protected virtual void TransformBlockQuotes(IHtmlCollection blockQuotes, IHtmlDocument document) - { - if (blockQuotes == null) throw new ArgumentNullException(nameof(blockQuotes)); - if (document == null) throw new ArgumentNullException(nameof(document)); - - int level = 1; - // Dictionary that holds the nodes that will be replaced. Replacement node is a span that groups the elements at the given level. - // We can't immediately replace the nodes as that will break the model where we walk the tree to find the level and top node - Dictionary replacementList = new Dictionary(); - - // Iterate over all blockquote nodes in the html - foreach (var blockQuote in blockQuotes) - { - // Only process block quotes that are used for indentation - if (blockQuote.OuterHtml.ToLower().Contains("margin:0px 0px 0px 40px")) - { - if ((blockQuote.ChildElementCount > 0 && blockQuote.Children[0].TagName.ToLower() == "blockquote")) - { - // do nothing if we still see a blockquote as child element - } - else - { - // We're at the bottom level, the children of this blockquote node do contain regular content. This is always wrapped inside either P or Div tags - bool replacementDone = false; - IElement insertionContainer = null; - IElement topLevelBlockQuote = null; - - // Calculate the level this blockquote is at, it's top level blockquote node, the insertion container for that level (if it exists), whether strike-through was used and whether underline was used - bool strikeThroughWasUsed = false; - bool underLineWasUsed = false; - DetectBlockQuoteLevelParentContainer(replacementList, blockQuote, ref level, ref topLevelBlockQuote, ref insertionContainer, ref strikeThroughWasUsed, ref underLineWasUsed); - - // For the first level we get null as top level blockquote, so assign the current blockquote - if (topLevelBlockQuote == null) - { - topLevelBlockQuote = blockQuote; - } - - // If we found an insertion container then we'll append converted nodes to that container - if (insertionContainer != null) - { - replacementDone = true; - } - - // Important to do a ToList() to get a copy as the Children list might be affected by the code in the loop! - foreach (var nodeToProcess in blockQuote.Children.ToList()) - { - // Indention in realized via P element with the needed pixels as margin-left - IElement newElement; - - // Block elements get their indentation style on the element, others will we wrapped inside a P - bool isBlockElement = nodeToProcess.IsBlockElement(); - string innerHtml = ""; - if (isBlockElement) - { - newElement = nodeToProcess; - newElement.GetStyle().SetMarginLeft($"{level * 40}px"); - // Store the block element html for later adding - innerHtml = nodeToProcess.InnerHtml; - // since we're injecting the block element content again we first remove all nodes from it - foreach (var child in newElement.ChildNodes.ToList()) - { - newElement.RemoveChild(child); - } - } - else - { - newElement = document.CreateElement($"p"); - newElement.GetStyle().SetMarginLeft($"{level * 40}px"); - } - - if (strikeThroughWasUsed) - { - // Since strikethrough was used wrap the contents in an s element - var newLineThroughElement = document.CreateElement("s"); - if (isBlockElement) - { - newLineThroughElement.InnerHtml = innerHtml; - } - else - { - newLineThroughElement.InnerHtml = nodeToProcess.OuterHtml; - } - newElement.AppendChild(newLineThroughElement); - } - else if (underLineWasUsed) - { - // Since underline was used wrap the contents in an u element - var newUnderlineElement = document.CreateElement("u"); - if (isBlockElement) - { - newUnderlineElement.InnerHtml = innerHtml; - } - else - { - newUnderlineElement.InnerHtml = nodeToProcess.OuterHtml; - } - newElement.AppendChild(newUnderlineElement); - } - else - { - if (isBlockElement) - { - newElement.InnerHtml = innerHtml; - } - else - { - newElement.InnerHtml = nodeToProcess.OuterHtml; - } - } - - // if no insertion container was created then create it as span and add the new element - if (insertionContainer == null) - { - insertionContainer = document.CreateElement("span"); - insertionContainer.AppendChild(newElement); - } - - if (!replacementDone) - { - // Since we can't simply replace the node (as that would break future blockquote tree traversals) we're storing the "new" node in a list. - // Replacement will happen at the very end of this flow - if (!replacementList.ContainsKey(topLevelBlockQuote)) - { - replacementList.Add(topLevelBlockQuote, insertionContainer); - } - } - else - { - // There's already an insertion container, so we can simply append our element - insertionContainer.AppendChild(newElement); - } - - replacementDone = true; - } - - // reset variables since we're starting a new blockquote - level = 1; - } - } - } - - // Perform the actual replacements - foreach (var node in replacementList) - { - var parent = node.Key.Parent; - if (parent.Contains(node.Key)) - { - parent.ReplaceChild(node.Value, node.Key); - } - } - } - - /// - /// Transform headings - /// - /// - /// - /// - protected virtual void TransformHeadings(IHtmlDocument document, int fromSize, int toSize) - { - if (document == null) throw new ArgumentNullException(nameof(document)); - - var fromNodes = document.QuerySelectorAll($"h{fromSize}"); - foreach (var fromNode in fromNodes) - { - var parent = fromNode.Parent; - - if (toSize == 0) - { - // wrap the content inside a div so that further formatting processing will pick it up - var newElement = document.CreateElement("div"); - newElement.InnerHtml = fromNode.InnerHtml; - parent.ReplaceChild(newElement, fromNode); - } - else - { - var newElement = document.CreateElement($"h{toSize}"); - newElement.InnerHtml = fromNode.InnerHtml; - - // Copy the text alignment style - if (fromNode.GetStyle() != null && !string.IsNullOrEmpty(fromNode.GetStyle().GetTextAlign())) - { - newElement.GetStyle().SetTextAlign(fromNode.GetStyle().GetTextAlign()); - } - parent.ReplaceChild(newElement, fromNode); - } - } - } - - /// - /// Transform elements - /// - /// - /// - protected virtual void TransformElements(IHtmlCollection elementsToTransform, IHtmlDocument document) - { - if (elementsToTransform == null) throw new ArgumentNullException(nameof(elementsToTransform)); - if (document == null) throw new ArgumentNullException(nameof(document)); - - foreach (var element in elementsToTransform) - { - var parent = element.Parent; - - // ================================ - // Rewrite font styles outside of the header styles - // ================================ - // Norm - //ms-rteStyle-Normal - //ms-rteStyle-Quote = Italic - //ms-rteStyle-IntenseQuote = Italic + Underline - //ms-rteStyle-Emphasis = italic + light blue - //ms-rteStyle-IntenseEmphasis = italic + light blue + underline - //ms-rteStyle-References = light gray - //ms-rteStyle-IntenseReferences = light gray + underline - //ms-rteStyle-Accent1 = light blue - //ms-rteStyle-Accent2 = dark blue - var rtestylenormal = element.ClassList.PartialMatch("ms-rtestyle-normal"); - if (!string.IsNullOrEmpty(rtestylenormal)) - { - element.ClassList.Remove(rtestylenormal); - } - - // Replace "rte styles" with their wiki style alternatives which then can be picked up by the default processing - var rtestyle = element.ClassList.PartialMatch("ms-rtestyle-"); - bool addItalic = false; - if (!string.IsNullOrEmpty(rtestyle)) - { - // Remove the wiki style - element.ClassList.Remove(rtestyle); - - rtestyle = rtestyle.ToLower().Replace("ms-rtestyle-", ""); - // Underline style - if (rtestyle.Equals("IntenseQuote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseReferences", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element Style which will be picked up later on - element.GetStyle().SetTextDecoration("underline"); - } - - // Light blue - if (rtestyle.Equals("Emphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("Accent1", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element ClassList which will be picked up later on - element.ClassList.Add("ms-rteforecolor-8"); - } - - // Light gray - if (rtestyle.Equals("References", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseReferences", StringComparison.InvariantCultureIgnoreCase)) - { - // no color to add as default is light gray - } - - // Dark blue - if (rtestyle.Equals("Accent2", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element ClassList which will be picked up later on - element.ClassList.Add("ms-rteforecolor-9"); - } - - // Italic style - if (rtestyle.Equals("Quote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseQuote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("Emphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase)) - { - // As we need to insert an EM element we can't do that right now. Let's start with setting an indicator - addItalic = true; - } - } - - // ================================ - // rewrite colors, back and fore color + size can be defined as class on a single span element - // ================================ - // red - var themeForeColor = element.ClassList.PartialMatch("ms-rtethemeforecolor-"); - if (!string.IsNullOrEmpty(themeForeColor)) - { - string newClass = null; - - // Modern Theme colors - // Darker, Dark, Dark Alternate, Primary, Secondary - // Neutral Tertiary, Neutral Secondary, Primary alternate, Neutral primary, Neutral Dark - if (int.TryParse(themeForeColor.ToLower()[themeForeColor.ToLower().Length - 1].ToString(), out int themeCode)) - { - string colorName = CodeConversion.ThemeCodeToForegroundColorName(themeCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"fontColor{colorName}"; - } - } - - element.ClassList.Remove(themeForeColor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // red - var rtethemebackcolor = element.ClassList.PartialMatch("ms-rtethemebackcolor-"); - if (!string.IsNullOrEmpty(rtethemebackcolor)) - { - // There are no themed back colors in modern, so for now drop the color span and the background color - element.ClassList.Remove(rtethemebackcolor); - } - - //Red,  - //superscript - var rteforecolor = element.ClassList.PartialMatch("ms-rteforecolor-"); - if (!string.IsNullOrEmpty(rteforecolor)) - { - // Modern Theme colors - // Dark Red, Red, Orange, Yellow, Light green - // Green, Light Blue, Blue, Dark Blue, Purple - - string newClass = null; - if (int.TryParse(rteforecolor.ToLower().Replace("ms-rteforecolor-", ""), out int colorCode)) - { - string colorName = CodeConversion.ColorCodeToForegroundColorName(colorCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"fontColor{colorName}"; - } - } - - element.ClassList.Remove(rteforecolor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // lowerscript - var rtebackcolor = element.ClassList.PartialMatch("ms-rtebackcolor-"); - if (!string.IsNullOrEmpty(rtebackcolor)) - { - // Modern Theme colors - // Dark Red, Red, Orange, Yellow, Light green - // Green, Light Blue, Blue, Dark Blue, Purple - - string newClass = null; - if (int.TryParse(rtebackcolor.ToLower().Replace("ms-rtebackcolor-", ""), out int colorCode)) - { - string colorName = CodeConversion.ColorCodeToBackgroundColorName(colorCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"highlightColor{colorName}"; - } - } - - element.ClassList.Remove(rtebackcolor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // ================================ - // rewrite font size - // ================================ - var rtefontsize = element.ClassList.PartialMatch("ms-rtefontsize-"); - if (!string.IsNullOrEmpty(rtefontsize)) - { - string newClass = null; - if (int.TryParse(rtefontsize.ToLower().Replace("ms-rtefontsize-", ""), out int fontsizeCode)) - { - string fontSize = CodeConversion.FontCodeToName(fontsizeCode); - if (!string.IsNullOrEmpty(fontSize)) - { - newClass = $"fontSize{fontSize}"; - } - } - - element.ClassList.Remove(rtefontsize); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // ================================ - // rewrite fonts - // ================================ - // custom font - var rtefontface = element.ClassList.PartialMatch("ms-rtefontface-"); - if (!string.IsNullOrEmpty(rtefontface)) - { - // There are no themed back colors in modern, so for now drop the color span and the background color - element.ClassList.Remove(rtefontface); - } - - // rewrite striked and underline - // striked - // underline - bool replacementDone = false; - bool isStrikeThroughOnElementToKeep = false; - bool isUnderlineOnElementToKeep = false; - string elementToKeep = ""; - if (element.IsStrikeThrough()) - { - // strike through can be on an element that we're replacing as well (like em)...if so don't - // replace em with strike through now, but wait until at the very end - if (element.TagName.Equals("em", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("strong", StringComparison.InvariantCultureIgnoreCase) || - //element.TagName.Equals("blockquote", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sup", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sub", StringComparison.InvariantCultureIgnoreCase)) - { - elementToKeep = element.TagName; - isStrikeThroughOnElementToKeep = true; - } - else - { - ReplaceByRelevantTag(document, element, parent, "s"); - replacementDone = true; - } - } - else if (element.IsUnderline()) - { - if (element.TagName.Equals("em", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("strong", StringComparison.InvariantCultureIgnoreCase) || - //element.TagName.Equals("blockquote", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sup", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sub", StringComparison.InvariantCultureIgnoreCase)) - { - elementToKeep = element.TagName; - isUnderlineOnElementToKeep = true; - } - else - { - // Don't apply the default logic when we need to insert a EM tag...a node can only be replaced once, so postpone the - // replacement by an U element when we also need to insert an EM tag - if (!addItalic) - { - ReplaceByRelevantTag(document, element, parent, "u"); - replacementDone = true; - } - } - } - - // No need to wrap a span into a new span - if (element is IHtmlSpanElement) - { - if (addItalic) - { - // We needed to insert an EM tag as the provided style uses italic - var newElement = document.CreateElement("span"); - newElement.ClassList.Add(element.ClassList.ToArray()); - var newItalic = document.CreateElement("em"); - - // Transform the piece of html we add in this span, needed to handle spans nested in here as these otherwise would not be found anymore - string innerHtml = TransformInnerHtml(element.InnerHtml); - - // If the implemented style also used underline then add the U node as well - if (element.IsUnderline()) - { - var newUnderline = document.CreateElement("u"); - newUnderline.InnerHtml = innerHtml; - newItalic.AppendChild(newUnderline); - } - else - { - newItalic.InnerHtml = innerHtml; - } - - newElement.AppendChild(newItalic); - parent.ReplaceChild(newElement, element); - } - else - { - // if we still did not replace the span element, the span has no classes set anymore and the span does not have child elements then we can replace it by text - if (!replacementDone && element.ClassList.Length == 0 && element.FirstElementChild == null) - { - ReplaceChildElementByText(parent, element, document); - } - else - { - // drop style attribute if still present - if (element.HasAttribute("style")) - { - element.RemoveAttribute("style"); - } - // if the element has no classes anymore then let's drop the class attribute - if (element.ClassList.Length == 0 && element.HasAttribute("class")) - { - element.RemoveAttribute("class"); - } - } - } - } - else - { - // Non span element with styling that was transformed will be wrapped in a span containing the styling which wraps a "clean" element - var newElement = document.CreateElement("span"); - newElement.ClassList.Add(element.ClassList.ToArray()); - // no point in having an empty class attribute - if (element.HasAttribute("class")) - { - element.RemoveAttribute("class"); - } - - if (isStrikeThroughOnElementToKeep) - { - var strikeThroughElement = document.CreateElement("s"); - newElement.AppendChild(strikeThroughElement); - // Create the element that held the strikethrough style - var emElement = document.CreateElement(elementToKeep.ToLower()); - emElement.InnerHtml = element.InnerHtml; - strikeThroughElement.AppendChild(emElement); - } - else if (isUnderlineOnElementToKeep) - { - var underlineElement = document.CreateElement("u"); - newElement.AppendChild(underlineElement); - // Create the element that held the underline style - var emElement = document.CreateElement(elementToKeep.ToLower()); - emElement.InnerHtml = element.InnerHtml; - underlineElement.AppendChild(emElement); - } - else - { - newElement.InnerHtml = element.OuterHtml; - } - - // Only replace the element if it's still available...it could have been replaced earlier on - if (parent.Contains(element)) - { - parent.ReplaceChild(newElement, element); - } - } - } - } - - #region Helper methods - /// - /// Recursively loop the blockquote elements until we're at the top level, returns needed information to process: - /// - Level: how many indents where used - /// - TopLevelBlockQuote: what is the top level blockquote that we'll be using as "replacement node" - /// - If there already was a container created to store content at this level then let's return that one - /// - If by walking the blockquote tree we see strike through being used then indicate that - /// - If by walking the blockquote tree we see underline being used then indicate that - /// - /// - /// - /// - /// - /// - /// - /// - private void DetectBlockQuoteLevelParentContainer(Dictionary replacementList, IElement blockQuote, ref int level, ref IElement topLevelBlockQuote, ref IElement insertionContainer, ref bool strikeThroughWasUsed, ref bool underLineWasUsed) - { - if (blockQuote.IsStrikeThrough()) - { - strikeThroughWasUsed = true; - } - if (blockQuote.IsUnderline()) - { - underLineWasUsed = true; - } - - if (blockQuote.Parent is IElement) - { - if (blockQuote.ParentElement.TagName.ToLower() == "blockquote") - { - topLevelBlockQuote = blockQuote.ParentElement; - level++; - - if (blockQuote.ParentElement.IsStrikeThrough()) - { - strikeThroughWasUsed = true; - } - if (blockQuote.ParentElement.IsUnderline()) - { - underLineWasUsed = true; - } - - DetectBlockQuoteLevelParentContainer(replacementList, blockQuote.ParentElement, ref level, ref topLevelBlockQuote, ref insertionContainer, ref strikeThroughWasUsed, ref underLineWasUsed); - } - else - { - if (insertionContainer == null && topLevelBlockQuote != null && replacementList.ContainsKey(topLevelBlockQuote)) - { - insertionContainer = replacementList[topLevelBlockQuote]; - } - } - } - } - - private string TransformInnerHtml(string innerHtml) - { - // Let's inspect if there's still span's in the html we take over...these spans are not in our current list of spans - // and as such will be ignored if we don't handle them. - using var documentTemp = this.parser.ParseDocument(innerHtml); - TransformElements(documentTemp.QuerySelectorAll("span"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("sup"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("sub"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("strong"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("em"), documentTemp); - - if (documentTemp.DocumentElement.Children.Length > 1) - { - innerHtml = documentTemp.DocumentElement.Children[1].InnerHtml; - } - - return innerHtml; - } - - private void ReplaceByRelevantTag(IHtmlDocument document, IElement element, INode parent, string tag) - { - var newElement = document.CreateElement(tag); - - // Transform the piece of html we add in this span, needed to handle spans nested in here as these otherwise would not be found anymore - string innerHtml = TransformInnerHtml(element.InnerHtml); - - // If the element has a class defined then wrap inside a span - if (element.ClassList.Length > 0) - { - var newSpan = document.CreateElement("span"); - newSpan.ClassList.Add(element.ClassList.ToArray()); - newSpan.InnerHtml = innerHtml; - newElement.AppendChild(newSpan); - } - else - { - newElement.InnerHtml = innerHtml; - } - - parent.ReplaceChild(newElement, element); - } - - private static void ReplaceChildElementByText(INode parent, IElement child, IHtmlDocument document) - { - if (!string.IsNullOrEmpty(child.TextContent)) - { - // Add a text content in place of the element - var newElement = document.CreateTextNode(child.TextContent); - parent.ReplaceChild(newElement, child); - } - else - { - // If no content then drop the element - parent.RemoveChild(child); - } - } - - private static List GetDefaultCellTableCellWidths(int columns) - { - List widths = new List(columns); - - int width = DefaultTableWidth / columns; - int lastWidth = DefaultTableWidth - ((columns - 1) * width); - - for (int i = 0; i < columns; i++) - { - if (i < columns - 1) - { - widths.Add(width); - } - else - { - widths.Add(lastWidth); - } - } - - return widths; - } - - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMappingProvider.cs deleted file mode 100644 index 1eeb64f728..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMappingProvider.cs +++ /dev/null @@ -1,4419 +0,0 @@ -using AngleSharp.Dom; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using Microsoft.SharePoint.Client.WebParts; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.Functions; -using PnP.Core.Transformation.SharePoint.MappingFiles; -using PnP.Core.Transformation.SharePoint.Model; -using PnP.Core.Transformation.SharePoint.Services; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using PnP.Core.Transformation.SharePoint.Services.MappingProviders; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using System.Xml.XPath; -using PnPCoreModelSP = PnP.Core.Model.SharePoint; -using PublishingMapping = PnP.Core.Transformation.SharePoint.MappingFiles.Publishing; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// Default implementation of - /// - public class SharePointMappingProvider : IMappingProvider - { - private readonly Regex invalidCharsRegex = new Regex(@"[\*\?\|\\\t/:""'<>#{}%~&]", RegexOptions.Compiled); - private readonly Regex invalidRulesRegex = new Regex(@"\.{2,}", RegexOptions.Compiled); - private readonly Regex startEndRegex = new Regex(@"^[\. ]|[\. ]$", RegexOptions.Compiled); - private readonly Regex extraSpacesRegex = new Regex(" {2,}", RegexOptions.Compiled); - - class CombinedMapping - { - public int Order { get; set; } - public ClientSideText ClientSideText { get; set; } - public ClientSideWebPart ClientSideWebPart { get; set; } - } - - private ILogger logger; - private readonly IOptions spOptions; - private readonly IOptions pageOptions; - private readonly IMemoryCache memoryCache; - private readonly IServiceProvider serviceProvider; - private readonly TokenParser tokenParser; - private readonly PublishingFunctionProcessor publishingFunctionProcessor; - - private const string webPartMarkerString = "[[WebPartMarker]]"; - - private Guid taskId; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// SharePoint configuration options - /// Target page configuration options - /// Function processor for publishing pages - /// Service provider - public SharePointMappingProvider(ILogger logger, - IOptions spOptions, - IOptions pageOptions, - PublishingFunctionProcessor publishingFunctionProcessor, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.spOptions = spOptions ?? throw new ArgumentNullException(nameof(spOptions)); - this.pageOptions = pageOptions ?? throw new ArgumentNullException(nameof(pageOptions)); - this.publishingFunctionProcessor = publishingFunctionProcessor ?? throw new ArgumentNullException(nameof(publishingFunctionProcessor)); - this.serviceProvider = serviceProvider; - - this.memoryCache = this.serviceProvider.GetService(); - this.tokenParser = this.serviceProvider.GetService(); - } - - /// - /// Maps an item from the source platform to the target platform - /// - /// The input for the mapping - /// The cancellation token to use, if any - /// The output of the mapping - public async Task MapAsync(MappingProviderInput input, CancellationToken token = default) - { - this.taskId = input.Context.Task.Id; - - MappingProviderOutput result = new MappingProviderOutput(); - - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapAsync" - .CorrelateString(this.taskId)); - - // Validate input - if (input == null) throw new ArgumentNullException(nameof(input)); - - // Validate source item - var sourceItem = input.Context.SourceItem as SharePointSourceItem; - if (sourceItem == null) throw new ApplicationException(SharePointTransformationResources.Error_MissiningSharePointInputItem); - - // Get a reference to the file and item that we need to transform - var sourceContext = sourceItem.SourceContext; - var pageFile = sourceItem.SourceContext.Web.GetFileByServerRelativeUrl(sourceItem.Id.ServerRelativeUrl); - var pageItem = pageFile.ListItemAllFields; - sourceContext.Load(pageFile); - sourceContext.Load(pageItem); - await sourceContext.ExecuteQueryAsync().ConfigureAwait(false); - - // Check if the target site is the same as the source site - var crossSiteTransformation = IsCrossSiteTransformation(input, sourceItem); - var crossTenantTransformation = IsCrossTenantTransformation(input, sourceItem); - - // Store telemetry settings - result.TelemetryProperties.Add(TelemetryPropertiesNames.CrossFarm, crossTenantTransformation.ToString()); - result.TelemetryProperties.Add(TelemetryPropertiesNames.CrossSite, crossSiteTransformation.ToString()); - - // Read source page information - var sourcePage = ReadSourcePageInformation(pageItem); - - // And set target properties accordingly - result.TargetPage.Author = sourcePage.Author.Email; - result.TargetPage.Editor = sourcePage.Editor.Email; - result.TargetPage.Created = sourcePage.Created.Date; - result.TargetPage.Modified = sourcePage.Modified.Date; - - // Determine if the page is a Root Page - sourcePage.IsRootPage = pageFile != null && pageItem == null; - - // and configure the resulting object - result.TargetPage.IsHomePage = sourcePage.IsRootPage; - - // Evaluate the source page type - sourcePage.PageType = EvaluatePageType(pageFile, pageItem, crossSiteTransformation, crossTenantTransformation); - - // Store telemetry settings - result.TelemetryProperties.Add(TelemetryPropertiesNames.PageType, sourcePage.PageType.ToString()); - - // Retrieve Version and ExactVersion of source and target - (sourcePage.SourceVersion, sourcePage.SourceVersionNumber) = sourceContext.GetVersions(); - - // Store telemetry settings - result.TelemetryProperties.Add(TelemetryPropertiesNames.SourceVersion, sourcePage.SourceVersion.ToString()); - result.TelemetryProperties.Add(TelemetryPropertiesNames.SourceVersionNumber, sourcePage.SourceVersionNumber.ToString()); - - // Log that we've finished the validation stage - logger.LogInformation( - SharePointTransformationResources.Info_PageValidationChecksComplete - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl); - - // Extract the list of Web Parts to process and the reference page layout - var (pageLayout, layout, webPartsToProcess) = await AnalyzePageAsync(input.Context, pageFile, pageItem, sourcePage).ConfigureAwait(false); - - // Determine the target page title - result.TargetPage.PageTitle = DeterminePageTitle(input.Context, pageFile, pageItem, sourcePage, webPartsToProcess); - - // Start the actual transformation - var transformationStartDateTime = DateTime.Now; - - // Load information from source context - LoadClientObjects(sourceContext); - - // Retrieve the parent Folder of the page, if any and the target page name - string targetPageName = null; - (sourcePage.Folder, targetPageName) = DeterminePageFolder(sourceContext, sourcePage, pageItem, input.Context.TargetPageUri); - - // Log that we've finished the analysis stage - logger.LogInformation( - SharePointTransformationResources.Info_PageAnalysisComplete - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl); - result.TargetPage.Folder = sourcePage.Folder; - - if (sourcePage.PageType == SourcePageType.BlogPage) - { - var generatedBlogPageName = $"{result.TargetPage.PageTitle.Replace(" ", "-")}-{pageItem[SharePointConstants.IDField]}.aspx"; - - // Based on this blog - http://www.simplyaprogrammer.com/2008/05/importing-files-into-sharepoint.html - string sanitizedName = extraSpacesRegex.Replace( - invalidRulesRegex.Replace( - invalidCharsRegex.Replace(input: generatedBlogPageName, - replacement: string.Empty).Trim(), "."), " "); - - while (startEndRegex.IsMatch(sanitizedName)) - { - sanitizedName = startEndRegex.Replace(sanitizedName, string.Empty); - } - - result.TargetPage.PageName = sanitizedName; - } - else - { - result.TargetPage.PageName = targetPageName; - } - - // Map the page layout into a modern page structure - result.TargetPage.Sections.AddRange(MapPageWireframe(pageLayout, layout, webPartsToProcess)); - - // Transform the web parts - var webPartMappingProvider = serviceProvider.GetService(); - if (webPartMappingProvider != null) - { - var sourceWebParts = webPartsToProcess.Where(w => !w.IsClosed).ToList(); - - // Double-check if we have web parts to process - if (sourceWebParts == null || sourceWebParts.Count == 0) - { - // nothing to transform - logger.LogWarning( - SharePointTransformationResources.Warning_NothingToTransform - .CorrelateString(this.taskId)); - } - else - { - // Normalize row numbers as there can be gaps if the analyzed page contained wiki tables - int newRowOrder = 0; - int lastExistingRowOrder = -1; - foreach (var webPart in sourceWebParts.OrderBy(p => p.Row)) - { - if (lastExistingRowOrder < webPart.Row) - { - newRowOrder++; - lastExistingRowOrder = webPart.Row; - } - - webPart.Row = newRowOrder; - } - - // For every web part that is not closed - foreach (var webPart in sourceWebParts.OrderBy(p => p.Row).ThenBy(p => p.Column).ThenBy(p => p.Order)) - { - // Process the actual mapping - var webPartInput = new SharePointWebPartMappingProviderInput(input.Context, sourceContext) - { - WebPart = webPart, - IsCrossSiteTransformation = crossSiteTransformation - }; - var output = await webPartMappingProvider - .MapWebPartAsync(webPartInput, token) - .ConfigureAwait(false) as SharePointWebPartMappingProviderOutput; - - //TODO: What if the output is null?, setting includeTitleWebPart blows this up. - ManageCombinedMapping(result.TargetPage, webPart, output.Mapping); - } - } - } - - #region Text/Section/Column cleanup - - // Drop "empty" text parts. Wiki pages tend to have a lot of text parts just containing div's and BR's...no point in keep those as they generate to much whitespace - RemoveEmptyTextParts(result.TargetPage); - - // Remove empty sections and columns to optimize screen real estate - if (spOptions.Value.RemoveEmptySectionsAndColumns) - { - RemoveEmptySectionsAndColumns(result.TargetPage); - } - - #endregion - - #region Handle page metadata - - if (pageOptions.Value.CopyPageMetadata) - { - // Copy the source page metadata - var metadata = await LoadSourcePageMetadataAsync(pageItem).ConfigureAwait(false); - foreach (var m in metadata) - { - result.Metadata.Add(m.Key, m.Value); - } - } - - #endregion - - #region Handle page permissions - - if (pageOptions.Value.KeepPageSpecificPermissions) - { - // Copy the source page item level permissions - result.Permissions = GetItemLevelPermissions(sourceContext, pageItem); - } - - #endregion - - #region Temporary code, most likely to be removed - - //var htmlMappingProvider = serviceProvider.GetService(); - //if (htmlMappingProvider != null) - //{ - // var htmlInput = new HtmlMappingProviderInput(input.Context, "TODO"); - // var output = await htmlMappingProvider - // .MapHtmlAsync(htmlInput, token) - // .ConfigureAwait(false); - //} - - //var metadataMappingProvider = serviceProvider.GetService(); - //if (metadataMappingProvider != null) - //{ - // var metadataInput = new MetadataMappingProviderInput(input.Context); - // var output = await metadataMappingProvider - // .MapMetadataFieldAsync(metadataInput, token) - // .ConfigureAwait(false); - //} - - //var urlMappingProvider = serviceProvider.GetService(); - //if (urlMappingProvider != null) - //{ - // var metadataInput = new UrlMappingProviderInput(input.Context, string.Empty); - // var output = await urlMappingProvider - // .MapUrlAsync(metadataInput, token) - // .ConfigureAwait(false); - //} - - ////var pageLayoutMappingProvider = serviceProvider.GetService(); - ////if (pageLayoutMappingProvider != null) - ////{ - //// var pageLayoutInput = new PageLayoutMappingProviderInput(input.Context); - //// var output = await pageLayoutMappingProvider - //// .MapPageLayoutAsync(pageLayoutInput, token) - //// .ConfigureAwait(false); - ////} - - //var taxonomyMappingProvider = serviceProvider.GetService(); - //if (taxonomyMappingProvider != null) - //{ - // var taxonomyInput = new TaxonomyMappingProviderInput(input.Context, ""); - // var output = await taxonomyMappingProvider - // .MapTermAsync(taxonomyInput, token) - // .ConfigureAwait(false); - //} - - //var userMappingProvider = serviceProvider.GetService(); - //if (userMappingProvider != null) - //{ - // var userInput = new UserMappingProviderInput(input.Context, ""); - // var output = await userMappingProvider - // .MapUserAsync(userInput, token) - // .ConfigureAwait(false); - //} - - #endregion - - return result; - } - - private ListItemPermission GetItemLevelPermissions(ClientContext sourceContext, ListItem pageItem) - { - // Prepare result variable - ListItemPermission lip = new ListItemPermission(); - - // Prepare supporting data - var pagesLibrary = pageItem.ParentList; - pagesLibrary.EnsureProperty(l => l.EffectiveBasePermissions); - pageItem.EnsureProperty(p => p.HasUniqueRoleAssignments); - - if (pageItem.IsPropertyAvailable("HasUniqueRoleAssignments") && pageItem.HasUniqueRoleAssignments) - { - // You need to have the ManagePermissions permission before item level permissions can be copied - if (pagesLibrary.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) - { - // Copy the unique permissions from source to target - // Get the unique permissions - sourceContext.Load(pageItem, - a => a.EffectiveBasePermissions, - a => a.RoleAssignments.Include( - roleAsg => roleAsg.Member.LoginName, - roleAsg => roleAsg.RoleDefinitionBindings.Include( - roleDef => roleDef.Id, - roleDef => roleDef.Name))); - sourceContext.ExecuteQueryRetry(); - - if (pageItem.EffectiveBasePermissions.Has(PermissionKind.ManagePermissions)) - { - // Load the site groups - sourceContext.Load(sourceContext.Web.SiteGroups, p => p.Include(g => g.LoginName)); - sourceContext.ExecuteQueryRetry(); - - // TODO: We need to manage this information - // lip.RoleAssignments = pageItem.RoleAssignments; - - // Apply new permissions - foreach (var roleAssignment in pageItem.RoleAssignments) - { - var roles = new List(); - - foreach (var role in roleAssignment.RoleDefinitionBindings) - { - roles.Add(role.Name); - } - - if (!lip.Members.ContainsKey(roleAssignment.Member.LoginName)) - { - lip.Members.Add(roleAssignment.Member.LoginName, roles.ToArray()); - } - else - { - lip.Members[roleAssignment.Member.LoginName] = lip.Members[roleAssignment.Member.LoginName].Union(roles.ToArray()).ToArray(); - } - } - } - } - else - { - logger.LogWarning( - SharePointTransformationResources.Warning_TransformGetItemPermissionsAccessDenied - .CorrelateString(this.taskId)); - return lip; - } - } - - logger.LogInformation( - SharePointTransformationResources.Info_GetItemLevelPermissions - .CorrelateString(this.taskId)); - - return lip; - } - - private async Task> LoadSourcePageMetadataAsync(ListItem pageItem) - { - var result = new Dictionary(); - - // Determine the list of fields to copy - var listFields = await GetFieldsToCopyAsync(pageItem).ConfigureAwait(false); - - foreach (var field in pageItem.FieldValues) - { - // Search for the corresponding field in the list - var listField = listFields.FirstOrDefault(f => f.Name == field.Key); - - // If any - if (listField != null) - { - // Collect field information and value - result.Add(field.Key, new FieldData - { - Id = listField != null ? listField.Id : Guid.Empty, - Name = listField?.Name, - Type = listField?.Type, - Value = field.Value - }); - } - } - - return result; - } - - /// - /// Defines the list of fields to copy from source item - /// - /// The source page item - /// Defines whether the page is a blog page or not - /// The list of fields to copy - private async Task> GetFieldsToCopyAsync(ListItem pageItem, bool isBlog = false) - { - // Ensure the parent list ID, web ID, site ID - var parentList = pageItem.ParentList; - pageItem.Context.Load(parentList, l => l.Id); - var parentWeb = (pageItem.Context as ClientContext)?.Web; - pageItem.Context.Load(parentWeb, w => w.Id); - var parentSite = (pageItem.Context as ClientContext)?.Site; - pageItem.Context.Load(parentSite, s => s.Id); - await pageItem.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - - // Create the cache item key - var fieldsCacheKey = $"{parentSite.Id}|{parentWeb.Id}|{parentList.Id}"; - - List result; - - // Try to get the result from cache - if (!memoryCache.TryGetValue(fieldsCacheKey, out result)) - { - // If not available in cache, prepare a fresh new result - result = new List(); - - var listFields = pageItem.ParentList.Fields; - pageItem.Context.Load(listFields); - await pageItem.Context.ExecuteQueryRetryAsync().ConfigureAwait(false); - - foreach (var sourceField in listFields.Where(f => !f.Hidden).ToList()) - { - // Skip OOB fields and Site Pages built in fields - if (!IsBuiltInField(isBlog, sourceField.Id) && - !IsSitePagesBuiltInField(sourceField.Id)) - { - // copy metadata for this field - FieldData fieldToAdd = new FieldData() - { - Name = sourceField.StaticName, - Id = sourceField.Id, - Type = sourceField.TypeAsString, - }; - - result.Add(fieldToAdd); - } - } - - // Put the result in cache for future usage - memoryCache.Set(fieldsCacheKey, result); - } - - return result; - } - - /// - /// Determines whether a field is built in or not - /// - /// Defines whether the page is a blog page or not - /// The ID of the field to check - /// A boolean stating if the field is built in - private static bool IsBuiltInField(bool isBlog, Guid fieldId) - { - if (Utilities.BuiltInFieldIds.Contains(fieldId)) - { - if (isBlog) - { - // Always allow the PostCategory field - if (fieldId.Equals(Utilities.BuiltInFieldIds.PostCategory)) - { - return false; - } - else - { - return true; - } - } - else - { - return true; - } - } - else - { - return false; - } - } - - /// - /// Determines whether a field is built in Site Pages field or not - /// - /// The ID of the field to check - /// A boolean stating if the field is built in for the Site Pages library - private static bool IsSitePagesBuiltInField(Guid fieldId) - { - return Utilities.SitePagesBuiltInFieldIds.Contains(fieldId); - } - - /// - /// Removes empty web parts from the page content - /// - /// The target page to cleanup - internal void RemoveEmptyTextParts(Page targetPage) - { - foreach (var section in targetPage.Sections) - { - foreach (var column in section.Columns) - { - foreach (var cc in column.Controls.Where(i => i.ControlType == CanvasControlType.ClientSideText).ToList()) - { - HtmlParser parser = new HtmlParser(new HtmlParserOptions() { IsEmbedded = true }); - - using (var document = parser.ParseDocument(cc["Text"] as string)) - { - if (document.FirstChild != null && string.IsNullOrEmpty(document.FirstChild.TextContent)) - { - logger.LogInformation( - SharePointTransformationResources.Info_TransformRemovingEmptyWebPart - .CorrelateString(this.taskId)); - - // Drop text part - column.Controls.Remove(cc); - } - } - } - } - } - } - - internal void RemoveEmptySectionsAndColumns(Page targetPage) - { - foreach (var section in targetPage.Sections.ToList()) - { - // First remove all empty sections - if (section.Columns.Count == 0) - { - targetPage.Sections.Remove(section); - } - } - - // Remove empty columns - foreach (var section in targetPage.Sections) - { - if (section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumn || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnLeft || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnRight || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnVerticalSection || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnLeftVerticalSection || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnRightVerticalSection) - { - // NOTE: We never set IsVerticalSectionColumn, how is it possible? - - var emptyColumn = section.Columns.FirstOrDefault(p => p.Controls.Count == 0); // && !p.IsVerticalSectionColumn); - if (emptyColumn != null) - { - // drop the empty column and change to single column section - section.Columns.Remove(emptyColumn); - - if (section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnVerticalSection || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnLeftVerticalSection || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.TwoColumnRightVerticalSection) - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.OneColumnVerticalSection; - } - else - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.OneColumn; - } - - // NOTE: This should be done by the generator (on the target side) so we don't need it here, I guess - // (section.Columns.First() as PnPCore.CanvasColumn).ResetColumn(0, 12); - } - } - else if (section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.ThreeColumn || - section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.ThreeColumnVerticalSection) - { - - // NOTE: Same note as before about IsVerticalSectionColumn - - var emptyColumns = section.Columns.Where(p => p.Controls.Count == 0); // && !p.IsVerticalSectionColumn); - if (emptyColumns != null) - { - if (emptyColumns.Count() == 2) - { - // drop the two empty columns and change to single column section - foreach (var emptyColumn in emptyColumns.ToList()) - { - section.Columns.Remove(emptyColumn); - } - - if (section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.ThreeColumnVerticalSection) - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.OneColumnVerticalSection; - } - else - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.OneColumn; - } - - // NOTE: This should be done by the generator (on the target side) so we don't need it here, I guess - // (section.Columns.First() as PnPCoreModelSP.CanvasColumn).ResetColumn(0, 12); - } - if (emptyColumns.Count() == 1) - { - // Remove the empty column and change to two column section - section.Columns.Remove(emptyColumns.First()); - - if (section.CanvasTemplate == PnPCoreModelSP.CanvasSectionTemplate.ThreeColumnVerticalSection) - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.TwoColumnVerticalSection; - } - else - { - section.CanvasTemplate = PnPCoreModelSP.CanvasSectionTemplate.TwoColumn; - } - - // NOTE: This should be done by the generator (on the target side) so we don't need it here, I guess - //int i = 0; - //foreach (var column in section.Columns.Where(p => !p.IsVerticalSectionColumn)) - //{ - // (column as PnPCore.CanvasColumn).ResetColumn(i, 6); - // i++; - //} - } - } - } - } - } - - private void ManageCombinedMapping(Page targetPage, WebPartEntity webPart, Mapping mapping) - { - // Use the mapping data => make one list of Text and WebParts to allow for correct ordering - logger.LogDebug( - SharePointTransformationResources.Debug_CombiningMappingData - .CorrelateString(this.taskId)); - var combinedMappinglist = new List(); - if (mapping.ClientSideText != null) - { - foreach (var map in mapping.ClientSideText.OrderBy(p => p.Order)) - { - if (!Int32.TryParse(map.Order, out Int32 mapOrder)) - { - mapOrder = 0; - } - - combinedMappinglist.Add(new CombinedMapping { ClientSideText = map, ClientSideWebPart = null, Order = mapOrder }); - } - } - if (mapping.ClientSideWebPart != null) - { - foreach (var map in mapping.ClientSideWebPart.OrderBy(p => p.Order)) - { - if (!Int32.TryParse(map.Order, out Int32 mapOrder)) - { - mapOrder = 0; - } - - combinedMappinglist.Add(new CombinedMapping { ClientSideText = null, ClientSideWebPart = map, Order = mapOrder }); - } - } - - // Get the order of the last inserted control in this column - int order = LastColumnOrder(targetPage, webPart.Row - 1, webPart.Column - 1); - // Interate the controls for this mapping using their order - foreach (var map in combinedMappinglist.OrderBy(p => p.Order)) - { - order++; - - var control = new CanvasControl(); - control.Order = order; - - if (map.ClientSideText != null) - { - control.ControlType = CanvasControlType.ClientSideText; - - // Parse the Text to support custom tokens - control["Text"] = this.tokenParser.ReplaceSourceTokens(map.ClientSideText.Text, webPart.Properties); - - logger.LogInformation( - SharePointTransformationResources.Info_AddedClientSideTextWebPart - .CorrelateString(this.taskId)); - } - else if (map.ClientSideWebPart != null) - { - control["JsonControlData"] = map.ClientSideWebPart.JsonControlData; - - if (map.ClientSideWebPart.Type == ClientSideWebPartType.Custom) - { - control.ControlType = CanvasControlType.CustomClientSideWebPart; - - // Parse the control ID to support generic web part placement scenarios - control["ControlId"] = this.tokenParser.ReplaceSourceTokens(map.ClientSideWebPart.ControlId, webPart.Properties); - - logger.LogInformation( - SharePointTransformationResources.Info_UsingCustomModernWebPart - .CorrelateString(this.taskId)); - } - else - { - control.ControlType = CanvasControlType.DefaultWebPart; - - // Save the web part name - control["WebPartType"] = map.ClientSideWebPart.Type.ToString(); - control["Title"] = webPart.Title; - control["Properties"] = webPart.Properties; - - logger.LogInformation( - SharePointTransformationResources.Info_ContentUsingModernWebPart - .CorrelateString(this.taskId), - map.ClientSideWebPart.Type); - } - } - - // We add the component to the temporary abstract page - targetPage.Sections[webPart.Row - 1].Columns[webPart.Column - 1].Controls.Add(control); - } - } - - private Int32 LastColumnOrder(Page targetPage, int row, int col) - { - var lastControl = targetPage.Sections[row].Columns[col].Controls.OrderBy(p => p.Order).LastOrDefault(); - if (lastControl != null) - { - return lastControl.Order; - } - else - { - return -1; - } - } - - private List
MapPageWireframe(PublishingMapping.PageLayout pageLayout, PageLayout layout, List webPartsToProcess) - { - var result = new List
(); - - switch (layout) - { - // In case of a custom layout let's stick with one column as model - case PageLayout.Wiki_OneColumn: - case PageLayout.WebPart_FullPageVertical: - case PageLayout.Wiki_Custom: - case PageLayout.WebPart_Custom: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - break; - case PageLayout.Wiki_TwoColumns: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Columns = GetColumns(2) - }); - break; - case PageLayout.Wiki_ThreeColumns: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumn, - Columns = GetColumns(3) - }); - break; - case PageLayout.Wiki_TwoColumnsWithSidebar: - case PageLayout.WebPart_2010_TwoColumnsLeft: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeft, - Columns = GetColumns(2) - }); - break; - case PageLayout.WebPart_HeaderRightColumnBody: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeft, - Columns = GetColumns(2) - }); - break; - case PageLayout.WebPart_HeaderLeftColumnBody: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnRight, - Columns = GetColumns(2) - }); - break; - case PageLayout.Wiki_TwoColumnsWithHeader: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Columns = GetColumns(2) - }); - break; - case PageLayout.Wiki_TwoColumnsWithHeaderAndFooter: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Columns = GetColumns(2) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - break; - case PageLayout.Wiki_ThreeColumnsWithHeader: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumn, - Columns = GetColumns(3) - }); - break; - case PageLayout.Wiki_ThreeColumnsWithHeaderAndFooter: - case PageLayout.WebPart_HeaderFooterThreeColumns: - case PageLayout.WebPart_HeaderFooter4ColumnsTopRow: - case PageLayout.WebPart_HeaderFooter2Columns4Rows: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumn, - Columns = GetColumns(3) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - break; - case PageLayout.WebPart_LeftColumnHeaderFooterTopRow3Columns: - case PageLayout.WebPart_RightColumnHeaderFooterTopRow3Columns: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumn, - Columns = GetColumns(3) - }); - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - break; - case PageLayout.PublishingPage_AutoDetect: - case PageLayout.PublishingPage_AutoDetectWithVerticalColumn: - // For Publishing Pages we rely on an external algorithm - var publishingLayoutTransformator = this.serviceProvider.GetService(); - result = publishingLayoutTransformator.Transform(pageLayout, layout, webPartsToProcess); - break; - default: - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1) - }); - break; - } - - return result; - - List GetColumns(int size) - { - return (from n in Enumerable.Range(0, size) select new Column()).ToList(); - } - } - - private string DeterminePageTitle(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, ListItem pageItem, SourcePageInformation sourcePage, List webPartsToProcess) - { - string pageTitle = string.Empty; - - switch (sourcePage.PageType) - { - case SourcePageType.WikiPage: - pageTitle = ExtractPageTitleFromFileName(pageItem); - break; - case SourcePageType.BlogPage: - string titleValue = null; - if (pageItem.TryGetFieldValue(SharePointConstants.TitleField, out titleValue)) - { - pageTitle = titleValue ?? string.Empty; - } - break; - case SourcePageType.WebPartPage: - var titleWebPart = webPartsToProcess.FirstOrDefault(w => w.Type == WebParts.TitleBar); - if (titleWebPart != null && - titleWebPart.Properties.ContainsKey(SharePointConstants.HeaderTitle) && - !string.IsNullOrEmpty(titleWebPart.Properties[SharePointConstants.HeaderTitle])) - { - pageTitle = titleWebPart.Properties[SharePointConstants.HeaderTitle]; - } - else - { - pageTitle = ExtractPageTitleFromFileName(pageItem); - } - break; - case SourcePageType.PublishingPage: - string publishingTitleValue = null; - if (pageItem.TryGetFieldValue(SharePointConstants.TitleField, out publishingTitleValue)) - { - pageTitle = publishingTitleValue ?? string.Empty; - } - else - { - pageTitle = ExtractPageTitleFromFileName(pageItem); - } - break; - default: - break; - } - - logger.LogInformation( - SharePointTransformationResources.Info_TransformPageModernTitle - .CorrelateString(this.taskId), pageFile.ServerRelativeUrl, pageTitle); - - return pageTitle; - } - - private static string ExtractPageTitleFromFileName(ListItem pageItem) - { - string pageTitle = null; - - string fileLeafRefValue = null; - if (pageItem.TryGetFieldValue(SharePointConstants.FileLeafRefField, out fileLeafRefValue)) - { - pageTitle = Path.GetFileNameWithoutExtension(fileLeafRefValue); - if (!string.IsNullOrEmpty(pageTitle)) - { - pageTitle = pageTitle.First().ToString().ToUpper() + pageTitle.Substring(1); - } - } - - return pageTitle; - } - - private static (string, string) DeterminePageFolder(ClientContext sourceContext, SourcePageInformation sourcePage, ListItem pageItem, Uri targetPageUri) - { - string pageFolder = null; - string targetPageName = targetPageUri.Segments[targetPageUri.Segments.Length - 1]; - - string fileRefFieldValue; - if (pageItem.TryGetFieldValue(SharePointConstants.FileDirRefField, out fileRefFieldValue)) - { - if (sourcePage.IsRootPage) - { - pageFolder = "Root/"; - if (string.IsNullOrEmpty(targetPageName)) - { - targetPageName = fileRefFieldValue; - } - } - else if (sourcePage.PageType == SourcePageType.BlogPage) - { - var blogsListName = sourceContext.Web.GetLocalizedListName(ListType.Blogs, "posts"); - if (fileRefFieldValue.ContainsIgnoringCasing($"/lists/{blogsListName}")) - { - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl.TrimEnd(new[] { '/' })}/Lists/{blogsListName}", "", StringComparison.InvariantCultureIgnoreCase).Trim(); - } - else - { - // Page was living in another list, leave the list name as that will be the folder hosting the modern file in SitePages. - // This convention is used to avoid naming conflicts - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl}", "").Trim(); - } - } - else if (sourcePage.PageType == SourcePageType.PublishingPage) - { - var pagesLibraryName = sourceContext.Web.GetLocalizedListName(ListType.PublishingPages, "pages"); - if (fileRefFieldValue.ContainsIgnoringCasing($"/{pagesLibraryName}")) - { - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl.TrimEnd(new[] { '/' })}/{pagesLibraryName}", "", StringComparison.InvariantCultureIgnoreCase).Trim(); - } - else - { - // Page was living in another list, leave the list name as that will be the folder hosting the modern file in SitePages. - // This convention is used to avoid naming conflicts - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl}", "").Trim(); - } - } - else - { - if (fileRefFieldValue.ContainsIgnoringCasing("/sitepages")) - { - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl.TrimEnd(new[] { '/' })}/SitePages", "").Trim(); - } - else - { - // Page was living in another list, leave the list name as that will be the folder hosting the modern file in SitePages. - // This convention is used to avoid naming conflicts - pageFolder = fileRefFieldValue.Replace($"{sourceContext.Web.ServerRelativeUrl}", "").Trim(); - } - } - - // Ensure that the page Folder does not start with "/" and does end with "/" - pageFolder = pageFolder.TrimStart('/').TrimEnd('/') + "/"; - } - - return (pageFolder, targetPageName); - } - - private static bool IsCrossSiteTransformation(MappingProviderInput input, SharePointSourceItem sourceItem) - { - var sourceUrl = sourceItem.SourceContext.Url; - var targetUrl = input.Context.TargetPageUri.AbsoluteUri; - - return !targetUrl.StartsWith(sourceUrl, StringComparison.InvariantCulture); - } - - private static bool IsCrossTenantTransformation(MappingProviderInput input, SharePointSourceItem sourceItem) - { - var sourceUrl = sourceItem.SourceContext.Url; - var targetUrl = input.Context.TargetPageUri.AbsoluteUri; - - var sourceTenant = new Uri(sourceUrl).Host; - var targetTenant = new Uri(targetUrl).Host; - - return sourceTenant != targetTenant; - } - - private SourcePageType EvaluatePageType(Microsoft.SharePoint.Client.File pageFile, ListItem pageItem, bool crossSiteTransformation, bool crossTenantTransformation) - { - SourcePageType result; - - if (pageFile != null && pageItem == null) - { - logger.LogInformation( - SharePointTransformationResources.Info_PageLivesOutsideOfALibrary - .CorrelateString(this.taskId), pageFile.ServerRelativeUrl); - - // This is always a web part page - result = SourcePageType.WebPartPage; - - // Item level permission copy makes no sense here - this.pageOptions.Value.KeepPageSpecificPermissions = false; - - // Same for swap pages, we don't support this as the pages live in a different location - this.pageOptions.Value.TargetPageTakesSourcePageName = false; - } - else - { - // Validate page item existence - if (pageItem == null) - { - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_SourcePageNotFound, pageFile.ServerRelativeUrl); - logger.LogInformation(errorMessage.CorrelateString(this.taskId)); - throw new ArgumentNullException(errorMessage); - } - - // Validate page and it's eligibility for transformation - if (!pageItem.FieldExistsAndUsed(SharePointConstants.FileRefField) || - !pageItem.FieldExistsAndUsed(SharePointConstants.FileLeafRefField)) - { - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_PageNotValidMissingFileRef, pageFile.ServerRelativeUrl); - logger.LogInformation(errorMessage.CorrelateString(this.taskId)); - throw new ArgumentNullException(errorMessage); - } - - // Store the page type - result = pageItem.PageType(); - - logger.LogInformation( - SharePointTransformationResources.Info_TransformationMode - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl, - result.FormatAsFriendlyTitle()); - - ValidatePageType(pageFile, result, crossSiteTransformation, crossTenantTransformation); - } - - return result; - } - - private void ValidatePageType(Microsoft.SharePoint.Client.File pageFile, SourcePageType pageType, bool crossSiteTransformation, bool crossTenantTransformation) - { - string pageTypeExceptionMessage = null; - - if (pageType == SourcePageType.ClientSidePage) - { - pageTypeExceptionMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_SourcePageIsModern, - pageFile.ServerRelativeUrl); - } - else if (pageType == SourcePageType.AspxPage) - { - pageTypeExceptionMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_BasicASPXPageCannotTransform, - pageFile.ServerRelativeUrl); - } - else if (pageType == SourcePageType.BlogPage && !crossSiteTransformation) - { - pageTypeExceptionMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_BlogPageTransformationHasToBeCrossSite, - pageFile.ServerRelativeUrl); - } - - if (!string.IsNullOrEmpty(pageTypeExceptionMessage)) - { - logger.LogError( - pageTypeExceptionMessage - .CorrelateString(this.taskId)); - throw new ArgumentNullException(pageTypeExceptionMessage); - } - } - - /// - /// Analyzes the current page to provide information about web parts and page layout - /// - /// The page layout and the list of web parts - internal async Task>> AnalyzePageAsync( - PageTransformationContext context, - Microsoft.SharePoint.Client.File pageFile, - ListItem pageItem, - SourcePageInformation page) - { - // Prepare the result variables - PublishingMapping.PageLayout pageLayout = null; - Model.PageLayout layout = Model.PageLayout.Unknown; - List webparts = null; - - switch (page.PageType) - { - case SourcePageType.WebPartPage when page.SourceVersion != SPVersion.SPO: - (layout, webparts) = await AnalyzeWebPartPageOnPremisesAsync(context, pageFile, page).ConfigureAwait(false); - break; - case SourcePageType.WebPartPage: - (layout, webparts) = await AnalyzeWebPartPageAsync(context, pageFile, page).ConfigureAwait(false); - break; - case SourcePageType.WikiPage: - (layout, webparts) = await AnalyzeWikiPageAsync(context, pageFile, pageItem, page).ConfigureAwait(false); - break; - case SourcePageType.BlogPage: - (layout, webparts) = await AnalyzeWikiPageAsync(context, pageFile, pageItem, page).ConfigureAwait(false); - break; - case SourcePageType.PublishingPage when page.SourceVersion != SPVersion.SPO: - (pageLayout, layout, webparts) = await AnalyzePublishingPageOnPremisesAsync(context, pageFile, pageItem, page).ConfigureAwait(false); - break; - case SourcePageType.PublishingPage: - (pageLayout, layout, webparts) = await AnalyzePublishingPageAsync(context, pageFile, pageItem, page).ConfigureAwait(false); - break; - } - - if (page.PageType == SourcePageType.WebPartPage || - page.PageType == SourcePageType.WikiPage || - page.PageType == SourcePageType.PublishingPage) - { - var clientContext = pageFile.Context as ClientContext; - var wikiHtmlTransformator = serviceProvider.GetService(); - var processedWebparts = wikiHtmlTransformator.TransformPlusSplit(clientContext, webparts); - - return new Tuple>(pageLayout, layout, processedWebparts); - } - else - { - return new Tuple>(pageLayout, layout, webparts); - } - } - - private async Task>> AnalyzeWebPartPageAsync(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, SourcePageInformation page) - { - // Prepare the result variable - List webparts = new List(); - - // Get a reference to the current source context - var clientContext = pageFile.Context; - - // Load web parts on web part page - // Note: Web parts placed outside of a web part zone using SPD are not picked up by the web part manager. There's no API that will return those, - // only possible option to add parsing of the raw page aspx file. - var limitedWPManager = pageFile.GetLimitedWebPartManager(PersonalizationScope.Shared); - clientContext.Load(limitedWPManager); - - // Load page properties - var pageProperties = pageFile.Properties; - clientContext.Load(pageProperties); - - // Load the web parts properties - IEnumerable webParts; - - webParts = clientContext.LoadQuery( - limitedWPManager.WebParts.IncludeWithDefaultProperties( - wp => wp.Id, - wp => wp.ZoneId, - wp => wp.WebPart.ExportMode, - wp => wp.WebPart.Title, - wp => wp.WebPart.ZoneIndex, - wp => wp.WebPart.IsClosed, - wp => wp.WebPart.Hidden, - wp => wp.WebPart.Properties)); - - await clientContext.ExecuteQueryAsync().ConfigureAwait(false); - - // Check page type - var layout = GetLayout(pageProperties); - - if (webParts.Any()) - { - List webPartsToRetrieve = new List(); - - foreach (var foundWebPart in webParts) - { - webPartsToRetrieve.Add(new WebPartPlaceHolder() - { - WebPartDefinition = foundWebPart, - WebPartXml = null, - WebPartType = "", - }); - } - - bool isDirty = false; - foreach (var foundWebPart in webPartsToRetrieve) - { - // Skip Microsoft.SharePoint.WebPartPages.TitleBarWebPart webpart in TitleBar zone - if (foundWebPart.WebPartDefinition.ZoneId.Equals("TitleBar", StringComparison.InvariantCultureIgnoreCase)) - { - if (!spOptions.Value.IncludeTitleBarWebPart) - { - continue; - } - } - - if (foundWebPart.WebPartDefinition.WebPart.ExportMode == WebPartExportMode.All) - { - foundWebPart.WebPartXml = limitedWPManager.ExportWebPart(foundWebPart.WebPartDefinition.Id); - isDirty = true; - } - } - if (isDirty) - { - await clientContext.ExecuteQueryAsync().ConfigureAwait(false); - } - - // TODO: Let's see if we can optimize this foreach and the previous one merging them into a unique one - foreach (var foundWebPart in webPartsToRetrieve) - { - // Skip Microsoft.SharePoint.WebPartPages.TitleBarWebPart webpart in TitleBar zone - if (foundWebPart.WebPartDefinition.ZoneId.Equals("TitleBar", StringComparison.InvariantCultureIgnoreCase)) - { - if (!spOptions.Value.IncludeTitleBarWebPart) - { - continue; - } - } - - if (foundWebPart.WebPartDefinition.WebPart.ExportMode != WebPartExportMode.All) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - foundWebPart.WebPartType = GetTypeFromProperties(foundWebPart.WebPartDefinition.WebPart.Properties.FieldValues); - } - else - { - foundWebPart.WebPartType = GetType(foundWebPart.WebPartXml.Value); - } - - logger.LogInformation( - SharePointTransformationResources.Info_ContentTransformFoundSourceWebParts - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl, - foundWebPart.WebPartDefinition.WebPart.Title, - foundWebPart.WebPartType.GetTypeShort()); - - webparts.Add(new WebPartEntity() - { - Title = foundWebPart.WebPartDefinition.WebPart.Title, - Type = foundWebPart.WebPartType, - Id = foundWebPart.WebPartDefinition.Id, - ServerControlId = foundWebPart.WebPartDefinition.Id.ToString(), - Row = GetRow(foundWebPart.WebPartDefinition.ZoneId, layout), - Column = GetColumn(foundWebPart.WebPartDefinition.ZoneId, layout), - Order = foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - ZoneId = foundWebPart.WebPartDefinition.ZoneId, - ZoneIndex = (uint)foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = foundWebPart.WebPartDefinition.WebPart.IsClosed, - Hidden = foundWebPart.WebPartDefinition.WebPart.Hidden, - WebPartXml = foundWebPart.WebPartXml == null ? "" : foundWebPart.WebPartXml.Value, - Properties = ExtractProperties(foundWebPart.WebPartDefinition.WebPart.Properties.FieldValues, - foundWebPart.WebPartType, foundWebPart.WebPartXml == null ? "" : foundWebPart.WebPartXml.Value), - }); - } - } - else - { - logger.LogInformation( - SharePointTransformationResources.Info_NoWebPartsFound - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl); - } - - return new Tuple>(layout, webparts); - } - - private async Task>> AnalyzeWebPartPageOnPremisesAsync(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, SourcePageInformation page) - { - // Prepare the result variable - List webparts = new List(); - - // Get a reference to the current source context - var clientContext = pageFile.Context; - - // Load web parts on web part page - // Note: Web parts placed outside of a web part zone using SPD are not picked up by the web part manager. There's no API that will return those, - // only possible option to add parsing of the raw page aspx file. - var limitedWPManager = pageFile.GetLimitedWebPartManager(PersonalizationScope.Shared); - clientContext.Load(limitedWPManager); - - // Load the web parts properties - IEnumerable webParts; - - webParts = clientContext.LoadQuery(limitedWPManager.WebParts.IncludeWithDefaultProperties( - wp => wp.Id, - wp => wp.ZoneId, - //wp => wp.WebPart.ExportMode, - wp => wp.WebPart.Title, - wp => wp.WebPart.ZoneIndex, - wp => wp.WebPart.IsClosed, - wp => wp.WebPart.Hidden, - wp => wp.WebPart.Properties)); - - await clientContext.ExecuteQueryAsync().ConfigureAwait(false); - - // Check page type - var layout = GetLayoutFromWebServices(pageFile); - - if (webParts.Any()) - { - List webPartsToRetrieve = new List(); - - foreach (var foundWebPart in webParts) - { - webPartsToRetrieve.Add(new WebPartPlaceHolder() - { - WebPartDefinition = foundWebPart, - WebPartXml = null, - WebPartType = "", - }); - } - - foreach (var foundWebPart in webPartsToRetrieve) - { - // Skip Microsoft.SharePoint.WebPartPages.TitleBarWebPart webpart in TitleBar zone - if (foundWebPart.WebPartDefinition.ZoneId.Equals("TitleBar", StringComparison.InvariantCultureIgnoreCase)) - { - if (!spOptions.Value.IncludeTitleBarWebPart) - { - continue; - } - } - - var webPartXml = ExportWebPartXmlWorkaround(pageFile, foundWebPart.WebPartDefinition.Id.ToString()); - foundWebPart.WebPartXmlOnPremises = webPartXml; - } - - foreach (var foundWebPart in webPartsToRetrieve) - { - Dictionary webPartProperties = null; - - string zoneId = foundWebPart.WebPartDefinition.ZoneId; - webPartProperties = foundWebPart.WebPartDefinition.WebPart.Properties.FieldValues; - - // TODO: Let's see if we can optimize this foreach and the previous one merging them into a unique one - if (foundWebPart.WebPartDefinition.ZoneId.Equals("TitleBar", StringComparison.InvariantCultureIgnoreCase)) - { - if (!spOptions.Value.IncludeTitleBarWebPart) - { - continue; - } - } - - if (string.IsNullOrEmpty(foundWebPart.WebPartXmlOnPremises)) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - foundWebPart.WebPartType = GetTypeFromProperties(webPartProperties, true); - } - else - { - foundWebPart.WebPartType = GetType(foundWebPart.WebPartXmlOnPremises); - } - - logger.LogInformation( - SharePointTransformationResources.Info_ContentTransformFoundSourceWebParts - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl, - foundWebPart.WebPartDefinition.WebPart.Title, - foundWebPart.WebPartType.GetTypeShort()); - - webparts.Add(new WebPartEntity() - { - Title = foundWebPart.WebPartDefinition.WebPart.Title, - Type = foundWebPart.WebPartType, - Id = foundWebPart.WebPartDefinition.Id, - ServerControlId = foundWebPart.WebPartDefinition.Id.ToString(), - Row = GetRow(foundWebPart.WebPartDefinition.ZoneId, layout), - Column = GetColumn(foundWebPart.WebPartDefinition.ZoneId, layout), - Order = foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - ZoneId = foundWebPart.WebPartDefinition.ZoneId, - ZoneIndex = (uint)foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = foundWebPart.WebPartDefinition.WebPart.IsClosed, - Hidden = foundWebPart.WebPartDefinition.WebPart.Hidden, - Properties = ExtractProperties(foundWebPart.WebPartDefinition.WebPart.Properties.FieldValues, - foundWebPart.WebPartType, foundWebPart.WebPartXmlOnPremises == null ? "" : foundWebPart.WebPartXmlOnPremises), - }); - } - } - else - { - logger.LogInformation( - SharePointTransformationResources.Info_NoWebPartsFound - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl); - } - - return new Tuple>(layout, webparts); - } - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - private async Task>> AnalyzeWikiPageAsync(PageTransformationContext context, Microsoft.SharePoint.Client.File wikiPage, ListItem pageItem, SourcePageInformation page) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - List webparts = new List(); - string pageContents = null; - - if (page.PageType == SourcePageType.BlogPage) - { - pageContents = pageItem.FieldValues[SharePointConstants.BodyField].ToString(); - if (string.IsNullOrEmpty(pageContents)) - { - throw new ApplicationException(string.Format( - System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_InvalidOrMissingBlogContent, - wikiPage.ServerRelativeUrl)); - } - } - else if (page.PageType == SourcePageType.WikiPage) - { - pageContents = pageItem.FieldValues[SharePointConstants.WikiField].ToString(); - if (string.IsNullOrEmpty(pageContents)) - { - throw new ApplicationException(string.Format( - System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_InvalidOrMissingWikiContent, - wikiPage.ServerRelativeUrl)); - } - } - - HtmlParser parser = new HtmlParser(); - - var layout = Model.PageLayout.Wiki_OneColumn; - - if (!string.IsNullOrEmpty(pageContents)) - { - var htmlDoc = parser.ParseDocument(pageContents); - layout = GetWikiLayout(htmlDoc); - - List webPartsToRetrieve = new List(); - - var rows = htmlDoc.All.Where(p => p.LocalName == "tr"); - int rowCount = 0; - - foreach (var row in rows) - { - rowCount++; - var columns = row.Children.Where(p => p.LocalName == "td" && p.Parent == row); - - int colCount = 0; - foreach (var column in columns) - { - colCount++; - var contentHost = column.Children.Where(p => p.LocalName == "div" && (p.ClassName != null && p.ClassName.Equals("ms-rte-layoutszone-outer", StringComparison.InvariantCultureIgnoreCase))).FirstOrDefault(); - - // Check if this element is nested in another already processed element...this needs to be skipped to avoid content duplication and possible processing errors - if (contentHost != null && contentHost.FirstElementChild != null && !IsNestedLayoutsZoneOuter(contentHost)) - { - var content = contentHost.FirstElementChild; - AnalyzeWikiContentBlock(parser, webparts, htmlDoc, webPartsToRetrieve, - rowCount, colCount, 0, content); - } - } - } - - // Bulk load the needed web part information - if (webPartsToRetrieve.Count > 0) - { - if (page.SourceVersion == SPVersion.Unknown || - page.SourceVersion == SPVersion.Unsupported) - { - var unsupportedError = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_UnsupportedSourceVersion, - wikiPage.ServerRelativeUrl); - logger.LogError(unsupportedError - .CorrelateString(this.taskId)); - - throw new ApplicationException(unsupportedError); - } - else if (page.SourceVersion == SPVersion.SP2013Legacy || - page.SourceVersion == SPVersion.SP2016Legacy) - { - LoadWebPartsInWikiContentFromOnPremisesServer(webparts, wikiPage, webPartsToRetrieve); - } - else - { - LoadWebPartsInWikiContentFromServer(webparts, wikiPage, webPartsToRetrieve); - } - } - else - { - logger.LogError( - SharePointTransformationResources.Error_AnalysingNoWebPartsFound - .CorrelateString(this.taskId), - wikiPage.ServerRelativeUrl); - } - - // Somehow the wiki was not standard formatted, so lets wrap its contents in a text block - if (webparts.Count == 0 && !String.IsNullOrEmpty(htmlDoc.Source.Text)) - { - webparts.Add(CreateWikiTextPart(htmlDoc.Source.Text, 1, 1, 1)); - } - } - - return new Tuple>(layout, webparts); - } - - private async Task>> AnalyzePublishingPageAsync(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, ListItem pageItem, SourcePageInformation page) - { - // Prepare the result variable - List webparts = new List(); - - // Prepare the HTML parser - HtmlParser parser = new HtmlParser(); - - // Get a reference to the current source context - var sourceContext = pageFile.Context as ClientContext; - - PublishingMapping.PageLayout publishingPageTransformationModel = await DeterminePublishingPageLayout(context, pageItem, page).ConfigureAwait(false); - - // Map layout - bool includeVerticalColumn = false; - if (publishingPageTransformationModel.IncludeVerticalColumnSpecified) - { - includeVerticalColumn = publishingPageTransformationModel.IncludeVerticalColumn; - } - - PageLayout layout = MapToLayout(publishingPageTransformationModel.PageLayoutTemplate, includeVerticalColumn); - - ProcessPublishingPageWebParts(context, pageFile, pageItem, webparts, sourceContext, parser, publishingPageTransformationModel); - - ProcessPublishingPageFields(webparts, sourceContext, publishingPageTransformationModel); - - await ProcessPublishingPageWebPartsInZonesOnline(pageFile, webparts, sourceContext, publishingPageTransformationModel).ConfigureAwait(false); - - ProcessPublishingPageWebPartsMappings(webparts, sourceContext, publishingPageTransformationModel); - - return new Tuple>(publishingPageTransformationModel, layout, webparts); ; - } - - private async Task>> AnalyzePublishingPageOnPremisesAsync(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, ListItem pageItem, SourcePageInformation page) - { - // Prepare the result variable - List webparts = new List(); - - // Prepare the HTML parser - HtmlParser parser = new HtmlParser(); - - // Get a reference to the current source context - var sourceContext = pageFile.Context as ClientContext; - - PublishingMapping.PageLayout publishingPageTransformationModel = await DeterminePublishingPageLayout(context, pageItem, page).ConfigureAwait(false); - - // Map layout - bool includeVerticalColumn = false; - if (publishingPageTransformationModel.IncludeVerticalColumnSpecified) - { - includeVerticalColumn = publishingPageTransformationModel.IncludeVerticalColumn; - } - - PageLayout layout = MapToLayout(publishingPageTransformationModel.PageLayoutTemplate, includeVerticalColumn); - - ProcessPublishingPageWebParts(context, pageFile, pageItem, webparts, sourceContext, parser, publishingPageTransformationModel); - - ProcessPublishingPageFields(webparts, sourceContext, publishingPageTransformationModel); - - await ProcessPublishingPageWebPartsInZonesOnPremises(pageFile, webparts, sourceContext, publishingPageTransformationModel, parser).ConfigureAwait(false); - - ProcessPublishingPageWebPartsMappings(webparts, sourceContext, publishingPageTransformationModel); - - return new Tuple>(publishingPageTransformationModel, layout, webparts); ; - } - - private async Task DeterminePublishingPageLayout(PageTransformationContext context, ListItem pageItem, SourcePageInformation page) - { - // Determine the source page layout - FieldUrlValue pageLayoutUrl = null; - if (pageItem.TryGetFieldValue(SharePointConstants.PublishingPageLayoutField, out pageLayoutUrl)) - { - page.PageLayout = System.IO.Path.GetFileNameWithoutExtension(pageLayoutUrl.Url); - } - - // Here we need to invoke the Page Layout Mapping Provider - // to determine the target page layout - SharePointPageLayoutMappingProviderOutput pageLayoutOutput = null; - var pageLayoutMappingProvider = serviceProvider.GetService(); - if (pageLayoutMappingProvider != null) - { - var pageLayoutInput = new PageLayoutMappingProviderInput(context); - pageLayoutInput.PageLayout = page.PageLayout; - var output = await pageLayoutMappingProvider - .MapPageLayoutAsync(pageLayoutInput) - .ConfigureAwait(false); - - pageLayoutOutput = output as SharePointPageLayoutMappingProviderOutput; - } - - // Still no layout...can't continue... - if (pageLayoutOutput == null) - { - var noPageLayoutError = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_NoPageLayoutTransformationModel, - page.PageLayout); - - logger.LogError(noPageLayoutError - .CorrelateString(this.taskId)); - throw new Exception(noPageLayoutError); - } - - // Otherwise get the publishing page transformation model - var publishingPageTransformationModel = pageLayoutOutput.PageLayout; - return publishingPageTransformationModel; - } - - private void ProcessPublishingPageWebParts(PageTransformationContext context, Microsoft.SharePoint.Client.File pageFile, ListItem pageItem, List webparts, ClientContext sourceContext, HtmlParser parser, PublishingMapping.PageLayout publishingPageTransformationModel) - { - #region Process fields that become web parts - if (publishingPageTransformationModel.WebParts != null) - { - #region Publishing Html column processing - // Converting to WikiTextPart is a special case as we'll need to process the html - var wikiTextWebParts = publishingPageTransformationModel.WebParts.Where(p => p.TargetWebPart.Equals(WebParts.WikiText, StringComparison.InvariantCultureIgnoreCase)); - List webPartsToRetrieve = new List(); - foreach (var wikiTextPart in wikiTextWebParts) - { - string pageContents = pageItem.GetFieldValueAs(wikiTextPart.Name); - this.publishingFunctionProcessor.Init(context, sourceContext, pageItem); - - if (wikiTextPart.Property.Length > 0) - { - foreach (var fieldWebPartProperty in wikiTextPart.Property) - { - if (fieldWebPartProperty.Name.Equals("Text", StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(fieldWebPartProperty.Functions)) - { - // execute function - var evaluatedField = this.publishingFunctionProcessor.Process(fieldWebPartProperty.Functions, fieldWebPartProperty.Name, MapToFunctionProcessorFieldType(fieldWebPartProperty.Type)); - if (!string.IsNullOrEmpty(evaluatedField.Item1)) - { - pageContents = evaluatedField.Item2; - } - } - } - } - - if (pageContents != null && !string.IsNullOrEmpty(pageContents)) - { - var htmlDoc = parser.ParseDocument(pageContents); - - // Analyze the html block (which is a wiki block) - var content = htmlDoc.FirstElementChild.LastElementChild; - AnalyzeWikiContentBlock(parser, webparts, htmlDoc, webPartsToRetrieve, - wikiTextPart.Row, wikiTextPart.Column, - GetNextOrder(wikiTextPart.Row, wikiTextPart.Column, wikiTextPart.Order, webparts), - content); - } - else - { - logger.LogWarning( - SharePointTransformationResources.Warning_CannotRetrieveFieldValue - .CorrelateString(this.taskId)); - } - } - - // Bulk load the needed web part information - if (webPartsToRetrieve.Count > 0) - { - LoadWebPartsInWikiContentFromServer(webparts, pageFile, webPartsToRetrieve); - } - #endregion - - #region Generic processing of the other 'webpart' fields - var fieldWebParts = publishingPageTransformationModel.WebParts.Where(p => !p.TargetWebPart.Equals(WebParts.WikiText, StringComparison.InvariantCultureIgnoreCase)); - foreach (var fieldWebPart in fieldWebParts.OrderBy(p => p.Row).OrderBy(p => p.Column)) - { - // In publishing scenarios it's common to not have all fields defined in the page layout mapping filled. By default we'll not map empty fields as that will result in empty web parts - // which impact the page look and feel. Using the RemoveEmptySectionsAndColumns flag this behaviour can be turned off. - if (this.spOptions.Value.RemoveEmptySectionsAndColumns) - { - var fieldContents = pageItem.GetFieldValueAs(fieldWebPart.Name); - - if (string.IsNullOrEmpty(fieldContents)) - { - logger.LogWarning( - SharePointTransformationResources.Warning_SkippedWebPartDueToEmptyInSourcee - .CorrelateString(this.taskId), - fieldWebPart.TargetWebPart, fieldWebPart.Name); - continue; - } - } - - Dictionary properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - foreach (var fieldWebPartProperty in fieldWebPart.Property) - { - if (!string.IsNullOrEmpty(fieldWebPartProperty.Functions)) - { - // execute function - var evaluatedField = this.publishingFunctionProcessor.Process(fieldWebPartProperty.Functions, fieldWebPartProperty.Name, MapToFunctionProcessorFieldType(fieldWebPartProperty.Type)); - if (!string.IsNullOrEmpty(evaluatedField.Item1) && !properties.ContainsKey(evaluatedField.Item1)) - { - properties.Add(evaluatedField.Item1, evaluatedField.Item2); - } - } - else - { - var webPartName = pageItem.FieldValues[fieldWebPart.Name]?.ToString().Trim(); - if (webPartName != null) - { - properties.Add(fieldWebPartProperty.Name, pageItem.FieldValues[fieldWebPart.Name].ToString().Trim()); - } - } - } - - var wpEntity = new WebPartEntity() - { - Title = fieldWebPart.Name, - Type = fieldWebPart.TargetWebPart, - Id = Guid.Empty, - Row = fieldWebPart.Row, - Column = fieldWebPart.Column, - Order = GetNextOrder(fieldWebPart.Row, fieldWebPart.Column, fieldWebPart.Order, webparts), - Properties = properties, - }; - - webparts.Add(wpEntity); - } - #endregion - } - #endregion - } - - private void ProcessPublishingPageFields(List webparts, ClientContext sourceContext, PublishingMapping.PageLayout publishingPageTransformationModel) - { - #region Process fields that become metadata as they might result in the creation of page properties web part - if (publishingPageTransformationModel.MetaData != null && publishingPageTransformationModel.MetaData.ShowPageProperties) - { - List pagePropertiesFields = new List(); - - var fieldsToProcess = publishingPageTransformationModel.MetaData.Field.Where(p => p.ShowInPageProperties == true && !string.IsNullOrEmpty(p.TargetFieldName)); - - if (fieldsToProcess.Any()) - { - // NOTE: In the new architecture we cannot loop over fields in the target site - // so we loop on the fields of the source context and we will take care of the - // corresponding fields in the target while generating the target page - - // Loop over the fields that are defined to be shown in the page properties and that have a target field name set - foreach (var fieldToProcess in fieldsToProcess) - { - var fieldInstance = sourceContext.Web.GetFieldByInternalName(fieldToProcess.TargetFieldName, true) ?? - sourceContext.Web.GetListByUrl("SitePages").Fields.GetFieldByInternalName(fieldToProcess.TargetFieldName); - - if (fieldInstance != null) - { - if (!pagePropertiesFields.Contains(fieldInstance.Id.ToString())) - { - pagePropertiesFields.Add(fieldInstance.Id.ToString()); - } - } - } - - if (pagePropertiesFields.Count > 0) - { - string propertyString = ""; - foreach (var propertyField in pagePropertiesFields) - { - if (!string.IsNullOrEmpty(propertyField)) - { - propertyString = $"{propertyString},\"{propertyField.ToString()}\""; - } - } - - if (!string.IsNullOrEmpty(propertyString)) - { - propertyString = propertyString.TrimStart(new char[] { ',' }); - } - - if (!string.IsNullOrEmpty(propertyString)) - { - Dictionary properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase) - { - { "SelectedFields", propertyString } - }; - - var wpEntity = new WebPartEntity() - { - Type = WebParts.PageProperties, - Id = Guid.Empty, - Row = publishingPageTransformationModel.MetaData.PagePropertiesRow, - Column = publishingPageTransformationModel.MetaData.PagePropertiesColumn, - Order = GetNextOrder(publishingPageTransformationModel.MetaData.PagePropertiesRow, publishingPageTransformationModel.MetaData.PagePropertiesColumn, publishingPageTransformationModel.MetaData.PagePropertiesOrder, webparts), - Properties = properties, - }; - - webparts.Add(wpEntity); - } - } - } - } - #endregion - } - - private async Task ProcessPublishingPageWebPartsInZonesOnline(Microsoft.SharePoint.Client.File pageFile, List webparts, ClientContext sourceContext, PublishingMapping.PageLayout publishingPageTransformationModel) - { - #region Web Parts in webpart zone handling - // Load web parts put in web part zones on the publishing page - // Note: Web parts placed outside of a web part zone using SPD are not picked up by the web part manager. - var limitedWPManager = pageFile.GetLimitedWebPartManager(PersonalizationScope.Shared); - sourceContext.Load(limitedWPManager); - - IEnumerable webPartsViaManager = sourceContext.LoadQuery(limitedWPManager.WebParts.IncludeWithDefaultProperties(wp => wp.Id, wp => wp.ZoneId, wp => wp.WebPart.ExportMode, wp => wp.WebPart.Title, wp => wp.WebPart.ZoneIndex, wp => wp.WebPart.IsClosed, wp => wp.WebPart.Hidden, wp => wp.WebPart.Properties)); - await sourceContext.ExecuteQueryRetryAsync().ConfigureAwait(false); - - if (webPartsViaManager.Any()) - { - List webPartsToRetrieve = new List(); - - foreach (var foundWebPart in webPartsViaManager) - { - // Remove the web parts which we've already picked up by analyzing the wiki content block - if (webparts.Where(p => p.Id.Equals(foundWebPart.Id)).FirstOrDefault() != null) - { - continue; - } - - webPartsToRetrieve.Add(new WebPartPlaceHolder() - { - WebPartDefinition = foundWebPart, - WebPartXml = null, - WebPartType = "", - }); - } - - bool isDirty = false; - foreach (var foundWebPart in webPartsToRetrieve) - { - if (foundWebPart.WebPartDefinition.WebPart.ExportMode == WebPartExportMode.All) - { - foundWebPart.WebPartXml = limitedWPManager.ExportWebPart(foundWebPart.WebPartDefinition.Id); - isDirty = true; - } - } - if (isDirty) - { - await sourceContext.ExecuteQueryRetryAsync().ConfigureAwait(false); - } - - List webPartZoneLayoutMap = new List(); - foreach (var foundWebPart in webPartsToRetrieve.OrderBy(p => p.WebPartDefinition.WebPart.ZoneIndex)) - { - Dictionary webPartProperties = foundWebPart.WebPartDefinition.WebPart.Properties.FieldValues; - - if (foundWebPart.WebPartDefinition.WebPart.ExportMode != WebPartExportMode.All) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - foundWebPart.WebPartType = GetTypeFromProperties(webPartProperties); - } - else - { - foundWebPart.WebPartType = GetType(foundWebPart.WebPartXml.Value); - } - - string zoneId = foundWebPart.WebPartDefinition.ZoneId; - - - int wpInZoneRow = 1; - int wpInZoneCol = 1; - int wpStartOrder = 0; - // Determine location based upon the location given to the web part zone in the mapping - if (publishingPageTransformationModel.WebPartZones != null) - { - var wpZoneFromTemplate = publishingPageTransformationModel.WebPartZones.Where(p => p.ZoneId.Equals(zoneId, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - if (wpZoneFromTemplate != null) - { - // Was there a webpart zone layout specified? If so then use that information to correctly position the webparts on the target page - if (wpZoneFromTemplate.WebPartZoneLayout != null && wpZoneFromTemplate.WebPartZoneLayout.Length > 0) - { - // Did we already map a web part of this type? - var webPartZoneLayoutMapEntry = webPartZoneLayoutMap.Where(p => p.ZoneId.Equals(wpZoneFromTemplate.ZoneId, StringComparison.InvariantCultureIgnoreCase) && - p.Type.Equals(foundWebPart.WebPartType, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - // What's the expected occurance for this web part in the mapping? - int webPartOccuranceInZoneLayout = 1; - if (webPartZoneLayoutMapEntry != null) - { - webPartOccuranceInZoneLayout += webPartZoneLayoutMapEntry.Occurances; - } - - // Get the webpart from the webpart zone layout mapping - int occuranceCounter = 0; - bool occuranceFound = false; - foreach (var wpInWebPartZoneLayout in wpZoneFromTemplate.WebPartZoneLayout.Where(p => p.Type.Equals(foundWebPart.WebPartType, StringComparison.InvariantCultureIgnoreCase))) - { - occuranceCounter++; - - if (occuranceCounter == webPartOccuranceInZoneLayout) - { - occuranceFound = true; - wpInZoneRow = wpInWebPartZoneLayout.Row; - wpInZoneCol = wpInWebPartZoneLayout.Column; - wpStartOrder = wpInWebPartZoneLayout.Order; - break; - } - } - - if (occuranceFound) - { - // Update the WebPartZoneLayoutMap - if (webPartZoneLayoutMapEntry != null) - { - webPartZoneLayoutMapEntry.Occurances = webPartOccuranceInZoneLayout; - } - else - { - webPartZoneLayoutMap.Add(new WebPartZoneLayoutMap() { ZoneId = wpZoneFromTemplate.ZoneId, Type = foundWebPart.WebPartType, Occurances = webPartOccuranceInZoneLayout }); - } - } - else - { - // fall back to the defaults from the zone definition - wpInZoneRow = wpZoneFromTemplate.Row; - wpInZoneCol = wpZoneFromTemplate.Column; - wpStartOrder = wpZoneFromTemplate.Order; - } - } - else - { - wpInZoneRow = wpZoneFromTemplate.Row; - wpInZoneCol = wpZoneFromTemplate.Column; - wpStartOrder = wpZoneFromTemplate.Order; - } - } - } - - // Determine order already taken - int wpInZoneOrderUsed = GetNextOrder(wpInZoneRow, wpInZoneCol, wpStartOrder, webparts); - - string webPartXmlForPropertiesMethod = foundWebPart.WebPartXml == null ? "" : foundWebPart.WebPartXml.Value; - - webparts.Add(new WebPartEntity() - { - Title = foundWebPart.WebPartDefinition.WebPart.Title, - Type = foundWebPart.WebPartType, - Id = foundWebPart.WebPartDefinition.Id, - ServerControlId = foundWebPart.WebPartDefinition.Id.ToString(), - Row = wpInZoneRow, - Column = wpInZoneCol, - Order = wpInZoneOrderUsed + foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - ZoneId = zoneId, - ZoneIndex = (uint)foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = foundWebPart.WebPartDefinition.WebPart.IsClosed, - Hidden = foundWebPart.WebPartDefinition.WebPart.Hidden, - Properties = ExtractProperties(webPartProperties, foundWebPart.WebPartType, webPartXmlForPropertiesMethod), - }); - } - } - else - { - logger.LogInformation( - SharePointTransformationResources.Info_AnalysingNoWebPartsFound - .CorrelateString(this.taskId)); - } - - #endregion - } - - private async Task ProcessPublishingPageWebPartsInZonesOnPremises(Microsoft.SharePoint.Client.File pageFile, List webparts, ClientContext sourceContext, PublishingMapping.PageLayout publishingPageTransformationModel, HtmlParser parser) - { - #region Web Parts in webpart zone handling - // Load web parts put in web part zones on the publishing page - // Note: Web parts placed outside of a web part zone using SPD are not picked up by the web part manager. - var limitedWPManager = pageFile.GetLimitedWebPartManager(PersonalizationScope.Shared); - sourceContext.Load(limitedWPManager); - - //Properties, ExportMode and ZoneId - not Supported in 2010, Web Services are used to compensate for the missing properties - IEnumerable webPartsViaManager = sourceContext.LoadQuery(limitedWPManager.WebParts.IncludeWithDefaultProperties(wp => wp.Id, wp => wp.WebPart.Title, wp => wp.WebPart.ZoneIndex, wp => wp.WebPart.IsClosed, wp => wp.WebPart.Hidden)); - await sourceContext.ExecuteQueryRetryAsync().ConfigureAwait(false); - - List webServiceWebPartEntities = LoadPublishingPageFromWebServices(sourceContext, pageFile.EnsureProperty(p => p.ServerRelativeUrl), parser); - - if (webPartsViaManager.Any()) - { - List webPartsToRetrieve = new List(); - - foreach (var foundWebPart in webPartsViaManager) - { - // Remove the web parts which we've already picked up by analyzing the wiki content block - if (webparts.Where(p => p.Id.Equals(foundWebPart.Id)).FirstOrDefault() != null) - { - continue; - } - - webPartsToRetrieve.Add(new WebPartPlaceHolder() - { - WebPartDefinition = foundWebPart, - WebPartXml = null, - WebPartType = "", - }); - } - - foreach (var foundWebPart in webPartsToRetrieve) - { - // If the web service call includes the export mode value then set the export options - var wsWp = webServiceWebPartEntities.FirstOrDefault(o => o.Id == foundWebPart.WebPartDefinition.Id); - var wsExportMode = wsWp.Properties.FirstOrDefault(o => o.Key.Equals("exportmode", StringComparison.InvariantCultureIgnoreCase)); - if (!string.IsNullOrEmpty(wsExportMode.Value) && wsExportMode.Value.Equals("all", StringComparison.InvariantCultureIgnoreCase)) - { - var webPartXml = ExportWebPartXmlWorkaround(sourceContext, pageFile, foundWebPart.WebPartDefinition.Id.ToString()); - foundWebPart.WebPartXmlOnPremises = webPartXml; - } - } - - List webPartZoneLayoutMap = new List(); - foreach (var foundWebPart in webPartsToRetrieve.OrderBy(p => p.WebPartDefinition.WebPart.ZoneIndex)) - { - bool isExportable = false; - - Dictionary webPartProperties = null; - - // If the web service call includes the export mode value then set the export options - var wsWp = webServiceWebPartEntities.FirstOrDefault(o => o.Id == foundWebPart.WebPartDefinition.Id); - webPartProperties = wsWp.PropertiesAsStringObjectDictionary(); - - var wsExportMode = wsWp.Properties.FirstOrDefault(o => o.Key.Equals("exportmode", StringComparison.InvariantCultureIgnoreCase)); - if (!string.IsNullOrEmpty(wsExportMode.Value) && wsExportMode.Value.ToString().Equals("all", StringComparison.InvariantCultureIgnoreCase)) - { - isExportable = true; - } - - if (!isExportable) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - foundWebPart.WebPartType = GetTypeFromProperties(webPartProperties, true); - } - else - { - foundWebPart.WebPartType = GetType(foundWebPart.WebPartXmlOnPremises); - } - - string zoneId = string.Empty; - var wsZoneId = wsWp.Properties.FirstOrDefault(o => o.Key.Equals("zoneid", StringComparison.InvariantCultureIgnoreCase)); - if (!string.IsNullOrEmpty(wsZoneId.Value)) - { - zoneId = wsZoneId.Value; - } - - int wpInZoneRow = 1; - int wpInZoneCol = 1; - int wpStartOrder = 0; - // Determine location based upon the location given to the web part zone in the mapping - if (publishingPageTransformationModel.WebPartZones != null) - { - var wpZoneFromTemplate = publishingPageTransformationModel.WebPartZones.Where(p => p.ZoneId.Equals(zoneId, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - if (wpZoneFromTemplate != null) - { - // Was there a webpart zone layout specified? If so then use that information to correctly position the webparts on the target page - if (wpZoneFromTemplate.WebPartZoneLayout != null && wpZoneFromTemplate.WebPartZoneLayout.Length > 0) - { - // Did we already map a web part of this type? - var webPartZoneLayoutMapEntry = webPartZoneLayoutMap.Where(p => p.ZoneId.Equals(wpZoneFromTemplate.ZoneId, StringComparison.InvariantCultureIgnoreCase) && - p.Type.Equals(foundWebPart.WebPartType, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - // What's the expected occurance for this web part in the mapping? - int webPartOccuranceInZoneLayout = 1; - if (webPartZoneLayoutMapEntry != null) - { - webPartOccuranceInZoneLayout += webPartZoneLayoutMapEntry.Occurances; - } - - // Get the webpart from the webpart zone layout mapping - int occuranceCounter = 0; - bool occuranceFound = false; - foreach (var wpInWebPartZoneLayout in wpZoneFromTemplate.WebPartZoneLayout.Where(p => p.Type.Equals(foundWebPart.WebPartType, StringComparison.InvariantCultureIgnoreCase))) - { - occuranceCounter++; - - if (occuranceCounter == webPartOccuranceInZoneLayout) - { - occuranceFound = true; - wpInZoneRow = wpInWebPartZoneLayout.Row; - wpInZoneCol = wpInWebPartZoneLayout.Column; - wpStartOrder = wpInWebPartZoneLayout.Order; - break; - } - } - - if (occuranceFound) - { - // Update the WebPartZoneLayoutMap - if (webPartZoneLayoutMapEntry != null) - { - webPartZoneLayoutMapEntry.Occurances = webPartOccuranceInZoneLayout; - } - else - { - webPartZoneLayoutMap.Add(new WebPartZoneLayoutMap() { ZoneId = wpZoneFromTemplate.ZoneId, Type = foundWebPart.WebPartType, Occurances = webPartOccuranceInZoneLayout }); - } - } - else - { - // fall back to the defaults from the zone definition - wpInZoneRow = wpZoneFromTemplate.Row; - wpInZoneCol = wpZoneFromTemplate.Column; - wpStartOrder = wpZoneFromTemplate.Order; - } - } - else - { - wpInZoneRow = wpZoneFromTemplate.Row; - wpInZoneCol = wpZoneFromTemplate.Column; - wpStartOrder = wpZoneFromTemplate.Order; - } - } - } - - // Determine order already taken - int wpInZoneOrderUsed = GetNextOrder(wpInZoneRow, wpInZoneCol, wpStartOrder, webparts); - - string webPartXmlForPropertiesMethod = foundWebPart.WebPartXmlOnPremises; - - webparts.Add(new WebPartEntity() - { - Title = foundWebPart.WebPartDefinition.WebPart.Title, - Type = foundWebPart.WebPartType, - Id = foundWebPart.WebPartDefinition.Id, - ServerControlId = foundWebPart.WebPartDefinition.Id.ToString(), - Row = wpInZoneRow, - Column = wpInZoneCol, - Order = wpInZoneOrderUsed + foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - ZoneId = zoneId, - ZoneIndex = (uint)foundWebPart.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = foundWebPart.WebPartDefinition.WebPart.IsClosed, - Hidden = foundWebPart.WebPartDefinition.WebPart.Hidden, - Properties = ExtractProperties(webPartProperties, foundWebPart.WebPartType, webPartXmlForPropertiesMethod), - }); - } - } - else - { - logger.LogInformation( - SharePointTransformationResources.Info_AnalysingNoWebPartsFound - .CorrelateString(this.taskId)); - } - - #endregion - } - - private void ProcessPublishingPageWebPartsMappings(List webparts, ClientContext sourceContext, PublishingMapping.PageLayout publishingPageTransformationModel) - { - #region Fixed webparts mapping - if (publishingPageTransformationModel.FixedWebParts != null) - { - foreach (var fixedWebpart in publishingPageTransformationModel.FixedWebParts) - { - int wpFixedOrderUsed = GetNextOrder(fixedWebpart.Row, fixedWebpart.Column, fixedWebpart.Order, webparts); - - webparts.Add(new WebPartEntity() - { - Title = GetFixedWebPartProperty(sourceContext, fixedWebpart, "Title", ""), - Type = fixedWebpart.Type, - Id = Guid.NewGuid(), - Row = fixedWebpart.Row, - Column = fixedWebpart.Column, - Order = wpFixedOrderUsed, - ZoneId = "", - ZoneIndex = 0, - IsClosed = GetFixedWebPartProperty(sourceContext, fixedWebpart, "__designer:IsClosed", false), - Hidden = false, - Properties = CastAsPropertiesDictionary(fixedWebpart), - }); - - } - } - #endregion - } - - /// - /// Loads and Parses Web Part Page from Web Services - /// - /// The source context - /// The full URL of the page - /// The HTML parser - internal List LoadPublishingPageFromWebServices(ClientContext sourceContext, string fullUrl, HtmlParser parser) - { - var webParts = new List(); - var wsWebParts = ExtractWebPartDocumentViaWebServicesFromPage(sourceContext, fullUrl); - - if (!string.IsNullOrEmpty(wsWebParts.Item2)) - { - var doc = parser.ParseDocument(wsWebParts.Item2); - - List> prefixesAndNameSpaces = ExtractWebPartPrefixesFromNamespaces(doc, parser); - List> possibleWebPartsUsed = new List>(); - - prefixesAndNameSpaces.ForEach(p => - { - var possibleParts = WebParts.GetListOfWebParts(p.Item2); - foreach (var part in possibleParts) - { - var webPartName = part.Substring(0, part.IndexOf(",")).Replace($"{p.Item2}.", ""); - possibleWebPartsUsed.Add(new Tuple(webPartName, part)); - } - }); - - var xmlBlock = wsWebParts.Item2; - var tag = ""; - var marker = xmlBlock.IndexOf(tag); - if (marker > -1) - { - xmlBlock = xmlBlock.Substring(marker).Replace(tag, ""); - } - - // Clean prefixes - xmlBlock = xmlBlock - .Replace("__designer:", "Designer") - .Replace(" o.Item1.ToUpper() == nodeToExtractProperties.LocalName.ToUpper()); - - if (matchWebPart != default) - { - webPart.Type = matchWebPart.Item2; - } - - var wpId = nodeToExtractProperties.Attributes.GetNamedItem("__WebPartId"); - webPart.Id = Guid.Parse(wpId?.Value); - - // In the case of Content Editor web parts - if (node.HasChildNodes && node.FirstChild.LocalName == "WebPart") - { - // Some web parts store properties as child nodes - nodeToExtractProperties = node.FirstChild; - - foreach (XmlNode wpChildNodes in nodeToExtractProperties.ChildNodes) - { - var property = wpChildNodes.LocalName; - var propertyValue = wpChildNodes.InnerText; - - // Rewrite "old" frametype modelling to newer chromeType based modelling - if (wpChildNodes.LocalName.Equals("FrameType")) - { - property = "ChromeType"; - if (propertyValue.Equals("None")) - { - propertyValue = "2"; - } - else if (propertyValue.Equals("Standard")) - { - propertyValue = "1"; - } - else if (propertyValue.Equals("TitleBarOnly")) - { - propertyValue = "3"; - } - else if (propertyValue.Equals("Default")) - { - propertyValue = "0"; - } - else if (propertyValue.Equals("BorderOnly")) - { - propertyValue = "4"; - } - } - - webPart.Properties.Add(property, propertyValue); - } - } - else - { - // Some web parts store properties by attributes - foreach (XmlAttribute attr in nodeToExtractProperties.Attributes) - { - webPart.Properties.Add(attr.Name, attr.Value); - } - } - - webParts.Add(webPart); - } - } - } - } - - return webParts; - } - - /// - /// Call SharePoint Web Services to extract web part properties not exposed by CSOM - /// - /// - internal Tuple ExtractWebPartDocumentViaWebServicesFromPage(ClientContext sourceContext, string fullDocumentUrl) - { - try - { - logger.LogInformation( - SharePointTransformationResources.Info_CallingWebServicesToExtractWebPartsFromPage - .CorrelateString(this.taskId)); - - string webUrl = sourceContext.Web.EnsureProperty(w => w.Url); - string webServiceUrl = webUrl + "/_vti_bin/WebPartPages.asmx"; - - StringBuilder soapEnvelope = new StringBuilder(); - - soapEnvelope.Append(""); - soapEnvelope.Append(""); - - soapEnvelope.Append(String.Format( - "" + - "" + - "{0}" + - "Version3" + - "" + - "" - , fullDocumentUrl)); - - soapEnvelope.Append(""); - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webServiceUrl); - //request.Credentials = cc.Credentials; - request.AddAuthenticationData(sourceContext); - request.Method = "POST"; - request.ContentType = "text/xml; charset=\"utf-8\""; - request.Accept = "text/xml"; - request.Headers.Add("SOAPAction", "\"http://microsoft.com/sharepoint/webpartpages/GetWebPartPage\""); - - using (System.IO.Stream stream = request.GetRequestStream()) - { - using (System.IO.StreamWriter writer = new System.IO.StreamWriter(stream)) - { - writer.Write(soapEnvelope.ToString()); - } - } - - var response = request.GetResponse(); - using (var dataStream = response.GetResponseStream()) - { - XmlDocument xDoc = new XmlDocument(); - xDoc.Load(dataStream); - - if (xDoc.DocumentElement != null && xDoc.DocumentElement.InnerText.Length > 0) - { - var webPartPageContents = xDoc.DocumentElement.InnerText; - //Remove the junk from the result - var tag = ""; - var marker = webPartPageContents.IndexOf(tag); - var partDocument = string.Empty; - if (marker > -1) - { - partDocument = webPartPageContents.Substring(marker).Replace(tag, ""); - } - - return new Tuple(webPartPageContents, partDocument); - } - } - } - catch (WebException ex) - { - logger.LogError( - SharePointTransformationResources.Error_CallingWebServicesToExtractWebPartsFromPage - .CorrelateString(this.taskId), - ex.Message); - } - - return new Tuple(string.Empty, string.Empty); - } - - /// - /// Gets the tag prefixes from the document - /// - /// - /// Html parser to use - /// - internal List> ExtractWebPartPrefixesFromNamespaces(IHtmlDocument webPartPage, HtmlParser parser) - { - var tagPrefixes = new List>(); - - Regex regex = new Regex("<%@(.*?)%>", RegexOptions.IgnoreCase | RegexOptions.Multiline); - var aspxHeader = webPartPage.All.Where(o => o.TagName == "HTML").FirstOrDefault(); - var results = regex.Matches(aspxHeader?.InnerHtml); - - StringBuilder blockHtml = new StringBuilder(); - foreach (var match in results) - { - var matchString = match.ToString().Replace("<%@ ", "<").Replace("%>", " />"); - blockHtml.AppendLine(matchString); - } - - var fullBlock = blockHtml.ToString(); - using (var subDocument = parser.ParseDocument(fullBlock)) - { - var registers = subDocument.All.Where(o => o.TagName == "REGISTER"); - - foreach (var register in registers) - { - var prefix = register.GetAttribute("Tagprefix"); - var nameSpace = register.GetAttribute("Namespace"); - var className = nameSpace.InferClassNameFromNameSpace(); - tagPrefixes.Add(new Tuple(prefix, nameSpace)); - tagPrefixes.Add(new Tuple(className, nameSpace)); - } - - } - - return tagPrefixes; - } - - /// - /// Exports Web Part XML via an older workround - /// - /// - /// - /// - /// - /// TODO: Possible Duplicate Code ExportWebPartXmlWorkaround method - internal string ExportWebPartXmlWorkaround(ClientContext sourceContext, Microsoft.SharePoint.Client.File pageFile, string webPartGuid) - { - // Issue hints and Credit: - // https://blog.mastykarz.nl/export-web-parts-csom/ - // https://sharepoint.stackexchange.com/questions/30865/missing-export-option-for-sharepoint-2010-webparts - // https://github.com/SharePoint/PnP-Sites-Core/pull/908/files - - try - { - logger.LogInformation( - SharePointTransformationResources.Info_RetreivingExportWebPartXmlWorkaround - .CorrelateString(this.taskId), - webPartGuid, pageFile.ServerRelativeUrl); - - var pageItem = pageFile.EnsureProperty(p => p.ListItemAllFields); - var pageUrl = pageItem[SharePointConstants.FileRefField].ToString(); - - string webPartXml = string.Empty; - string serverRelativeUrl = sourceContext.Web.EnsureProperty(w => w.ServerRelativeUrl); - var uri = new Uri(sourceContext.Site.Url); - - var fullWebUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}{serverRelativeUrl}"; - var fullPageUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}{pageUrl}"; - - if (!fullWebUrl.EndsWith("/")) - { - fullWebUrl = fullWebUrl + "/"; - } - - string webServiceUrl = string.Format("{0}_vti_bin/exportwp.aspx?pageurl={1}&guidstring={2}", fullWebUrl, fullPageUrl, webPartGuid); - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webServiceUrl); //hack to force webpart zones to render - //request.Credentials = cc.Credentials; - request.AddAuthenticationData(sourceContext); - request.Method = "GET"; - - var response = request.GetResponse(); - using (var dataStream = response.GetResponseStream()) - { - XmlDocument xDoc = new XmlDocument(); - xDoc.Load(dataStream); - - if (xDoc.DocumentElement != null && xDoc.DocumentElement.OuterXml.Length > 0) - { - webPartXml = xDoc.DocumentElement.OuterXml; - - // Not sure what causes the web parts to switch from singular to multiple - if (xDoc.DocumentElement.LocalName == "webParts") - { - webPartXml = xDoc.DocumentElement.InnerXml; - } - - return webPartXml; - } - } - } - catch (WebException ex) - { - logger.LogError( - SharePointTransformationResources.Error_WebPartXmlNotExported - .CorrelateString(this.taskId), - webPartGuid, pageFile.ServerRelativeUrl, ex.Message); - } - - return string.Empty; - } - - /// - /// Check if this element is nested in another already processed element...this needs to be skipped to avoid content duplication and possible processing errors - /// - /// element to check - /// true if embedded in a already processed element - private static bool IsNestedLayoutsZoneOuter(IElement contentHost) - { - if (contentHost == null) - { - return false; - } - - var elementToInspect = contentHost.ParentElement; - if (elementToInspect == null) - { - return false; - } - - while (elementToInspect != null) - { - if (elementToInspect.LocalName == "div" && (elementToInspect.ClassName != null && elementToInspect.ClassName.Equals("ms-rte-layoutszone-outer", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - else - { - elementToInspect = elementToInspect.ParentElement; - } - } - - return false; - } - - private static void AnalyzeWikiContentBlock(HtmlParser parser, List webparts, - IHtmlDocument htmlDoc, List webPartsToRetrieve, - int rowCount, int colCount, int startOrder, IElement content) - { - // Drop elements which we anyhow can't transform and/or which are stripped out from RTE - CleanHtml(content, htmlDoc); - - StringBuilder textContent = new StringBuilder(); - int order = startOrder; - foreach (var node in content.ChildNodes) - { - // Do we find a web part inside... - if (((node as IHtmlElement) != null) && ContainsWebPart(parser, node as IHtmlElement)) - { - var extraText = StripWebPart(parser, node as IHtmlElement); - string extraTextAfterWebPart = null; - string extraTextBeforeWebPart = null; - if (!string.IsNullOrEmpty(extraText)) - { - // Should be, but checking anyhow - int webPartMarker = extraText.IndexOf(webPartMarkerString); - if (webPartMarker > -1) - { - extraTextBeforeWebPart = extraText.Substring(0, webPartMarker); - extraTextAfterWebPart = extraText.Substring(webPartMarker + webPartMarkerString.Length); - - // there could have been multiple web parts in a row (we don't support text inbetween them for now)...strip the remaining markers - extraTextBeforeWebPart = extraTextBeforeWebPart.Replace(webPartMarkerString, ""); - extraTextAfterWebPart = extraTextAfterWebPart.Replace(webPartMarkerString, ""); - } - } - - if (!string.IsNullOrEmpty(extraTextBeforeWebPart)) - { - textContent.AppendLine(extraTextBeforeWebPart); - } - - // first insert text part (if it was available) - if (!string.IsNullOrEmpty(textContent.ToString())) - { - order++; - webparts.Add(CreateWikiTextPart(textContent.ToString(), rowCount, colCount, order)); - textContent.Clear(); - } - - // then process the web part - order++; - Regex regexClientIds = new Regex(@"id=\""div_(?(\w|\-)+)"); - if (regexClientIds.IsMatch((node as IHtmlElement).OuterHtml)) - { - foreach (Match webPartMatch in regexClientIds.Matches((node as IHtmlElement).OuterHtml)) - { - // Store the web part we need, will be retrieved afterwards to optimize performance - string serverSideControlId = webPartMatch.Groups["ControlId"].Value; - var serverSideControlIdToSearchFor = $"g_{serverSideControlId.Replace("-", "_")}"; - webPartsToRetrieve.Add(new WebPartPlaceHolder() { ControlId = serverSideControlIdToSearchFor, Id = serverSideControlId, Row = rowCount, Column = colCount, Order = order }); - } - } - - // Process the extra text that was positioned after the web part (if any) - if (!string.IsNullOrEmpty(extraTextAfterWebPart)) - { - textContent.AppendLine(extraTextAfterWebPart); - } - } - else - { - if (!string.IsNullOrEmpty(node.TextContent.Trim()) && node.TextContent.Trim() == "\n") - { - // ignore, this one is typically added after a web part - } - else - { - if (node.HasChildNodes) - { - textContent.AppendLine((node as IHtmlElement).OuterHtml); - } - else - { - if (!string.IsNullOrEmpty(node.TextContent.Trim())) - { - textContent.AppendLine(node.TextContent); - } - else - { - if (node.NodeName.Equals("br", StringComparison.InvariantCultureIgnoreCase)) - { - textContent.AppendLine("
"); - } - // given that wiki html can contain embedded images and videos while not having child nodes we need include these. - // case: img/iframe tag as "only" element to evaluate (e.g. first element in the contenthost) - else if (node.NodeName.Equals("img", StringComparison.InvariantCultureIgnoreCase) || - node.NodeName.Equals("iframe", StringComparison.InvariantCultureIgnoreCase)) - { - textContent.AppendLine((node as IHtmlElement).OuterHtml); - } - } - } - } - } - } - - // there was only one text part - if (!string.IsNullOrEmpty(textContent.ToString())) - { - // insert text part to the web part collection - order++; - webparts.Add(CreateWikiTextPart(textContent.ToString(), rowCount, colCount, order)); - } - } - - private static void CleanHtml(IElement element, IHtmlDocument document) - { - foreach (var node in element.QuerySelectorAll("*").ToList()) - { - if (node.ParentElement != null && IsUntransformableBlockElement(node)) - { - // create new div node and add all current children to it - var div = document.CreateElement("div"); - foreach (var child in node.ChildNodes.ToList()) - { - div.AppendChild(child); - } - // replace the unsupported node with the new div - node.ParentElement.ReplaceChild(div, node); - } - } - } - - private static bool IsUntransformableBlockElement(IElement element) - { - var tag = element.TagName.ToLower(); - if (tag == "article" || - tag == "address" || - tag == "aside" || - tag == "canvas" || - tag == "dd" || - tag == "dl" || - tag == "dt" || - tag == "fieldset" || - tag == "figcaption" || - tag == "figure" || - tag == "footer" || - tag == "form" || - tag == "header" || - //tag == "hr" || // will be replaced at in the html transformator - tag == "main" || - tag == "nav" || - tag == "noscript" || - tag == "output" || - tag == "pre" || - tag == "section" || - tag == "tfoot" || - tag == "video" || - tag == "aside") - { - return true; - } - - return false; - } - - /// - /// Does the tree of nodes somewhere contain a web part? - /// - /// Html parser - /// Html content to analyze - /// True if it contains a web part - private static bool ContainsWebPart(HtmlParser parser, IHtmlElement element) - { - var doc = parser.ParseDocument(element.OuterHtml); - var nodes = doc.All.Where(p => p.LocalName == "div"); - foreach (var node in nodes) - { - if (((node as IHtmlElement) != null) && (node as IHtmlElement).ClassList.Contains("ms-rte-wpbox")) - { - return true; - } - } - return false; - } - - /// - /// Strips the div holding the web part from the html - /// - /// Html parser - /// Html element holding one or more web part divs - /// Cleaned html with a placeholder for the web part div - private static string StripWebPart(HtmlParser parser, IHtmlElement element) - { - IElement copy = element.Clone(true) as IElement; - var doc = parser.ParseDocument(copy.OuterHtml); - var nodes = doc.All.Where(p => p.LocalName == "div"); - if (nodes.Any()) - { - foreach (var node in nodes.ToList()) - { - if (((node as IHtmlElement) != null) && (node as IHtmlElement).ClassList.Contains("ms-rte-wpbox")) - { - var newElement = doc.CreateTextNode(webPartMarkerString); - node.Parent.ReplaceChild(newElement, node); - } - } - - if (doc.DocumentElement.Children[1].FirstElementChild != null && - doc.DocumentElement.Children[1].FirstElementChild is IHtmlDivElement) - { - return doc.DocumentElement.Children[1].FirstElementChild.InnerHtml; - } - else - { - return doc.DocumentElement.Children[1].InnerHtml; - } - } - else - { - return null; - } - } - - /// - /// Stores text content as a fake web part - /// - /// Text to store - /// Row of the fake web part - /// Column of the fake web part - /// Order inside the row/column - /// A web part entity to add to the collection - private static WebPartEntity CreateWikiTextPart(string wikiTextPartContent, int row, int col, int order) - { - var result = new WebPartEntity() - { - Title = "WikiText", - Type = "SharePointPnP.Modernization.WikiTextPart", - Id = Guid.Empty, - Row = row, - Column = col, - Order = order - }; - - if (result.Properties == null) - { - result.Properties = new Dictionary(); - } - result.Properties.Add("Text", wikiTextPartContent.Trim().Replace("\r\n", string.Empty)); - - return result; - } - - /// - /// Exports Web Part XML via an older workround - /// - /// The file of the page - /// The ID of the web part - /// The XML of the web part - /// TODO: Possible Duplicate Code ExportWebPartXmlWorkaround method - internal string ExportWebPartXmlWorkaround(Microsoft.SharePoint.Client.File pageFile, string webPartGuid) - { - // Issue hints and Credit: - // https://blog.mastykarz.nl/export-web-parts-csom/ - // https://sharepoint.stackexchange.com/questions/30865/missing-export-option-for-sharepoint-2010-webparts - // https://github.com/SharePoint/PnP-Sites-Core/pull/908/files - - var context = pageFile.Context as ClientContext; - if (context == null) - { - throw new ApplicationException(SharePointTransformationResources.Error_InvalidSourceContext); - } - - try - { - logger.LogInformation( - SharePointTransformationResources.Info_RetreivingExportWebPartXmlWorkaround - .CorrelateString(this.taskId), - webPartGuid, pageFile.ServerRelativeUrl); - - string webPartXml = string.Empty; - string serverRelativeUrl = context.Web.EnsureProperty(w => w.ServerRelativeUrl); - var uri = new Uri(context.Site.EnsureProperty(s => s.Url)); - - var fullWebUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}{serverRelativeUrl}"; - var fullPageUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}{pageFile.ServerRelativeUrl}"; - - if (!fullWebUrl.EndsWith("/")) - { - fullWebUrl = fullWebUrl + "/"; - } - - string webServiceUrl = $"{fullWebUrl}_vti_bin/exportwp.aspx?pageurl={fullPageUrl}&guidstring={webPartGuid}"; - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webServiceUrl); //hack to force webpart zones to render - //request.Credentials = context.Credentials; - request.AddAuthenticationData(context); - request.Method = "GET"; - - var response = request.GetResponse(); - using (var dataStream = response.GetResponseStream()) - { - XmlDocument xDoc = new XmlDocument(); - xDoc.Load(dataStream); - - if (xDoc.DocumentElement != null && xDoc.DocumentElement.OuterXml.Length > 0) - { - webPartXml = xDoc.DocumentElement.OuterXml; - - // Not sure what causes the web parts to switch from singular to multiple - if (xDoc.DocumentElement.LocalName == "webParts") - { - webPartXml = xDoc.DocumentElement.InnerXml; - } - - return webPartXml; - } - } - } - catch (WebException ex) - { - logger.LogInformation( - SharePointTransformationResources.Error_WebPartXmlNotExported - .CorrelateString(this.taskId), - webPartGuid, pageFile.ServerRelativeUrl, ex.Message); - } - - return string.Empty; - } - - /// - /// Determines the used web part page layout - /// - /// Properties of the web part page file - /// Used layout - private static PageLayout GetLayout(PropertyValues pageProperties) - { - if (pageProperties.FieldValues.ContainsKey("vti_setuppath")) - { - var setupPath = pageProperties["vti_setuppath"].ToString(); - if (!string.IsNullOrEmpty(setupPath)) - { - if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd1.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_FullPageVertical; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd2.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_HeaderFooterThreeColumns; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd3.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_HeaderLeftColumnBody; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd4.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_HeaderRightColumnBody; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd5.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_HeaderFooter2Columns4Rows; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd6.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_HeaderFooter4ColumnsTopRow; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd7.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_LeftColumnHeaderFooterTopRow3Columns; - } - else if (setupPath.IndexOf(@"\STS\doctemp\smartpgs\spstd8.aspx", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.WebPart_RightColumnHeaderFooterTopRow3Columns; - } - else if (setupPath.Equals(@"SiteTemplates\STS\default.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - return PageLayout.WebPart_2010_TwoColumnsLeft; - } - } - } - - return PageLayout.WebPart_Custom; - } - - /// - /// Analyzes the wiki page to determine which layout was used - /// - /// html object - /// Layout of the wiki page - private static PageLayout GetWikiLayout(IHtmlDocument doc) - { - string spanValue = ""; - var spanTags = doc.All.Where(p => p.LocalName == "span" && p.HasAttribute("id")); - if (spanTags.Any()) - { - foreach (var span in spanTags) - { - if (span.GetAttribute("id").Equals("layoutsdata", StringComparison.InvariantCultureIgnoreCase)) - { - spanValue = span.InnerHtml.ToLower(); - - if (spanValue == "false,false,1") - { - return PageLayout.Wiki_OneColumn; - } - else if (spanValue == "false,false,2") - { - var tdTag = doc.All.Where(p => p.LocalName == "td" && p.HasAttribute("style")).FirstOrDefault(); - if (tdTag != null) - { - if (tdTag.GetAttribute("style").IndexOf("width:49.95%;", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.Wiki_TwoColumns; - } - else if (tdTag.GetAttribute("style").IndexOf("width:66.6%;", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.Wiki_TwoColumnsWithSidebar; - } - else - { - return PageLayout.Wiki_TwoColumns; - } - } - } - else if (spanValue == "true,false,2") - { - return PageLayout.Wiki_TwoColumnsWithHeader; - } - else if (spanValue == "true,true,2") - { - return PageLayout.Wiki_TwoColumnsWithHeaderAndFooter; - } - else if (spanValue == "false,false,3") - { - return PageLayout.Wiki_ThreeColumns; - } - else if (spanValue == "true,false,3") - { - return PageLayout.Wiki_ThreeColumnsWithHeader; - } - else if (spanValue == "true,true,3") - { - return PageLayout.Wiki_ThreeColumnsWithHeaderAndFooter; - } - } - } - } - - // Oops, we're still here...let's try to deduct a layout as some pages (e.g. from community template) do not add the proper span value - if (spanValue.StartsWith("false,false,") || spanValue.StartsWith("true,true,") || spanValue.StartsWith("true,false,")) - { - // false,false,{0} case..let's try to count the columns via the TD tag data - var tdTags = doc.All.Where(p => p.LocalName == "td" && p.HasAttribute("style")); - if (spanValue.StartsWith("false,false,")) - { - if (tdTags.Count() == 1) - { - return PageLayout.Wiki_OneColumn; - } - else if (tdTags.Count() == 2) - { - if (tdTags.First().GetAttribute("style").IndexOf("width:49.95%;", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.Wiki_TwoColumns; - } - else if (tdTags.First().GetAttribute("style").IndexOf("width:66.6%;", StringComparison.InvariantCultureIgnoreCase) > -1) - { - return PageLayout.Wiki_TwoColumnsWithSidebar; - } - else - { - return PageLayout.Wiki_TwoColumns; - } - } - else if (tdTags.Count() == 3) - { - return PageLayout.Wiki_ThreeColumns; - } - } - else if (spanValue.StartsWith("true,true,")) - { - if (tdTags.Count() == 2) - { - return PageLayout.Wiki_TwoColumnsWithHeaderAndFooter; - } - else if (tdTags.Count() == 3) - { - return PageLayout.Wiki_ThreeColumnsWithHeaderAndFooter; - } - } - else if (spanValue.StartsWith("true,false,")) - { - if (tdTags.Count() == 2) - { - return PageLayout.Wiki_TwoColumnsWithHeader; - } - else if (tdTags.Count() == 3) - { - return PageLayout.Wiki_ThreeColumnsWithHeader; - } - } - } - - return PageLayout.Wiki_Custom; - } - - /// - /// Gets and parses the layout from the web services URL - /// - /// The page file - /// - private PageLayout GetLayoutFromWebServices(Microsoft.SharePoint.Client.File pageFile) - { - var wsPageDocument = ExtractWebPartDocumentViaWebServicesFromPage(pageFile); - - if (!string.IsNullOrEmpty(wsPageDocument.Item1)) - { - //Example fragment from WS - //
  • vti_setuppath - //
  • SR|1033\STS\doctemp\smartpgs\spstd2.aspx - //
  • vti_generator - - var fullDocument = wsPageDocument.Item1; - - if (!string.IsNullOrEmpty(fullDocument)) - { - if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd1.aspx")) - { - return PageLayout.WebPart_FullPageVertical; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd2.aspx")) - { - return PageLayout.WebPart_HeaderFooterThreeColumns; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd3.aspx")) - { - return PageLayout.WebPart_HeaderLeftColumnBody; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd4.aspx")) - { - return PageLayout.WebPart_HeaderRightColumnBody; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd5.aspx")) - { - return PageLayout.WebPart_HeaderFooter2Columns4Rows; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd6.aspx")) - { - return PageLayout.WebPart_HeaderFooter4ColumnsTopRow; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd7.aspx")) - { - return PageLayout.WebPart_LeftColumnHeaderFooterTopRow3Columns; - } - else if (fullDocument.ContainsIgnoringCasing(@"STS\doctemp\smartpgs\spstd8.aspx")) - { - return PageLayout.WebPart_RightColumnHeaderFooterTopRow3Columns; - } - else if (fullDocument.ContainsIgnoringCasing(@"SiteTemplates\STS\default.aspx")) - { - return PageLayout.WebPart_2010_TwoColumnsLeft; - } - } - - } - - return PageLayout.WebPart_Custom; - } - - /// - /// Call SharePoint Web Services to extract web part properties not exposed by CSOM - /// - /// - private Tuple ExtractWebPartDocumentViaWebServicesFromPage(Microsoft.SharePoint.Client.File pageFile) - { - var pageUrl = pageFile.EnsureProperty(p => p.ServerRelativeUrl); - var context = pageFile.Context as ClientContext; - - try - { - logger.LogInformation( - SharePointTransformationResources.Info_CallingWebServicesToExtractWebPartsFromPage - .CorrelateString(taskId), - pageFile.ServerRelativeUrl); - - string webUrl = context.Web.EnsureProperty(p => p.Url); - string webServiceUrl = webUrl + "/_vti_bin/WebPartPages.asmx"; - - StringBuilder soapEnvelope = new StringBuilder(); - - soapEnvelope.Append(""); - soapEnvelope.Append(""); - - soapEnvelope.Append(String.Format( - "" + - "" + - "{0}" + - "Version3" + - "" + - "" - , pageUrl)); - - soapEnvelope.Append(""); - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(webServiceUrl); - //request.Credentials = cc.Credentials; - request.AddAuthenticationData(context); - request.Method = "POST"; - request.ContentType = "text/xml; charset=\"utf-8\""; - request.Accept = "text/xml"; - request.Headers.Add("SOAPAction", "\"http://microsoft.com/sharepoint/webpartpages/GetWebPartPage\""); - - using (Stream stream = request.GetRequestStream()) - { - using (StreamWriter writer = new System.IO.StreamWriter(stream)) - { - writer.Write(soapEnvelope.ToString()); - } - } - - var response = request.GetResponse(); - using (var dataStream = response.GetResponseStream()) - { - XmlDocument xDoc = new XmlDocument(); - xDoc.Load(dataStream); - - if (xDoc.DocumentElement != null && xDoc.DocumentElement.InnerText.Length > 0) - { - var webPartPageContents = xDoc.DocumentElement.InnerText; - //Remove the junk from the result - var tag = ""; - var marker = webPartPageContents.IndexOf(tag); - var partDocument = string.Empty; - if (marker > -1) - { - partDocument = webPartPageContents.Substring(marker).Replace(tag, ""); - } - - return new Tuple(webPartPageContents, partDocument); - } - } - } - catch (WebException) - { - logger.LogError( - SharePointTransformationResources.Error_CallingWebServicesToExtractWebPartsFromPage - .CorrelateString(taskId), - pageFile.ServerRelativeUrl); - } - - return new Tuple(string.Empty, string.Empty); - } - - /// - /// Gets the type of the web part by detecting it from the available properties - /// - /// Web part properties to analyze - /// If true tries additional webpart types used in legacy versions - /// Type of the web part as fully qualified name - private static string GetTypeFromProperties(Dictionary properties, bool isLegacy = false) - { - if (properties == null) - { - throw new ArgumentNullException(nameof(properties)); - } - - // Check for XSLTListView web part - string[] xsltWebPart = new string[] { "ListUrl", "ListId", "Xsl", "JSLink", "ShowTimelineIfAvailable" }; - if (CheckWebPartProperties(xsltWebPart, properties)) - { - return WebParts.XsltListView; - } - - // Check for ListView web part - string[] listWebPart = new string[] { "ListViewXml", "ListName", "ListId", "ViewContentTypeId", "PageType" }; - if (CheckWebPartProperties(listWebPart, properties)) - { - return WebParts.ListView; - } - - // Check for Media web part - string[] mediaWebPart = new string[] { "AutoPlay", "MediaSource", "Loop", "IsPreviewImageSourceOverridenForVideoSet", "PreviewImageSource" }; - if (CheckWebPartProperties(mediaWebPart, properties)) - { - return WebParts.Media; - } - - // Check for SlideShow web part - string[] slideShowWebPart = new string[] { "LibraryGuid", "Layout", "Speed", "ShowToolbar", "ViewGuid" }; - if (CheckWebPartProperties(slideShowWebPart, properties)) - { - return WebParts.PictureLibrarySlideshow; - } - - // Check for Chart web part - string[] chartWebPart = new string[] { "ConnectionPointEnabled", "ChartXml", "DataBindingsString", "DesignerChartTheme" }; - if (CheckWebPartProperties(chartWebPart, properties)) - { - return WebParts.Chart; - } - - // Check for Site Members web part - string[] membersWebPart = new string[] { "NumberLimit", "DisplayType", "MembershipGroupId", "Toolbar" }; - if (CheckWebPartProperties(membersWebPart, properties)) - { - return WebParts.Members; - } - - // Check for Silverlight web part - string[] silverlightWebPart = new string[] { "MinRuntimeVersion", "WindowlessMode", "CustomInitParameters", "Url", "ApplicationXml" }; - if (CheckWebPartProperties(silverlightWebPart, properties)) - { - return WebParts.Silverlight; - } - - // Check for Add-in Part web part - string[] addinPartWebPart = new string[] { "FeatureId", "ProductWebId", "ProductId" }; - if (CheckWebPartProperties(addinPartWebPart, properties)) - { - return WebParts.Client; - } - - if (isLegacy) - { - // Content Editor Web Part - string[] contentEditorWebPart = new string[] { "Content", "ContentLink", "PartStorage" }; - if (CheckWebPartProperties(contentEditorWebPart, properties)) - { - return WebParts.ContentEditor; - } - - // Image Viewer Web Part - string[] imageViewerWebPart = new string[] { "ImageLink", "AlternativeText", "VerticalAlignment", "HorizontalAlignment" }; - if (CheckWebPartProperties(imageViewerWebPart, properties)) - { - return WebParts.Image; - } - - // Title Bar - if (properties.ContainsKey("TypeName") && properties["TypeName"].ToString() == "Microsoft.SharePoint.WebPartPages.TitleBarWebPart") - { - return WebParts.TitleBar; - } - - // Check for ListView web part - string[] legacyListWebPart = new string[] { "ListViewXml", "ListName", "ListId", "ViewContentTypeId" }; - if (CheckWebPartProperties(legacyListWebPart, properties)) - { - return WebParts.ListView; - } - - string[] legacyXsltWebPart = new string[] { "ListUrl", "ListId", "ListName", "CatalogIconImageUrl" }; - if (CheckWebPartProperties(legacyXsltWebPart, properties)) - { - // Too Many Lists are showing here, so extra filters are required - // Not the cleanest method, but options limited to filter list type without extra calls to SharePoint - var iconsToCheck = new string[]{ - "images/itdl.png", "images/itissue.png", "images/itgen.png" }; - var iconToRepresent = properties["CatalogIconImageUrl"]; - foreach (var iconPath in iconsToCheck) - { - if (iconToRepresent.ToString().ContainsIgnoringCasing(iconPath)) - { - return WebParts.XsltListView; - } - } - } - } - - // Check for Script Editor web part - string[] scriptEditorWebPart = new string[] { "Content" }; - if (CheckWebPartProperties(scriptEditorWebPart, properties)) - { - return WebParts.ScriptEditor; - } - - // This needs to be last, but we still pages with sandbox user code web parts on them - string[] sandboxWebPart = new string[] { "CatalogIconImageUrl", "AllowEdit", "TitleIconImageUrl", "ExportMode" }; - if (CheckWebPartProperties(sandboxWebPart, properties)) - { - return WebParts.SPUserCode; - } - - return "Unsupported Web Part Type"; - } - - private static bool CheckWebPartProperties(string[] propertiesToCheck, Dictionary properties) - { - return properties.Keys.Intersect(propertiesToCheck).Count() == propertiesToCheck.Length; - } - - /// - /// Gets the type of the web part - /// - /// Web part xml to analyze - /// Type of the web part as fully qualified name - private static string GetType(string webPartXml) - { - string type = "Unknown"; - - if (!string.IsNullOrEmpty(webPartXml)) - { - var xml = XElement.Parse(webPartXml); - var xmlns = xml.XPathSelectElement("*").GetDefaultNamespace(); - if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v3", StringComparison.InvariantCultureIgnoreCase)) - { - type = xml.Descendants(xmlns + "type").FirstOrDefault().Attribute("name").Value; - } - else if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v2", StringComparison.InvariantCultureIgnoreCase)) - { - type = $"{xml.Descendants(xmlns + "TypeName").FirstOrDefault().Value}, {xml.Descendants(xmlns + "Assembly").FirstOrDefault().Value}"; - } - } - - return type; - } - - /// - /// Translates the given zone value and page layout to a column number - /// - /// Web part zone id - /// Layout of the web part page - /// Column value - private static int GetColumn(string zoneId, PageLayout layout) - { - switch (layout) - { - case PageLayout.WebPart_HeaderFooterThreeColumns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("MiddleColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_FullPageVertical: - { - return 1; - } - case PageLayout.WebPart_HeaderLeftColumnBody: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("Body", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - break; - } - case PageLayout.WebPart_HeaderRightColumnBody: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Body", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - break; - } - case PageLayout.WebPart_HeaderFooter2Columns4Rows: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("Row1", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row2", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row3", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row4", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_HeaderFooter4ColumnsTopRow: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_LeftColumnHeaderFooterTopRow3Columns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("CenterColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_RightColumnHeaderFooterTopRow3Columns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("CenterColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_2010_TwoColumnsLeft: - { - if (zoneId.Equals("Left", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("Right", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - break; - } - case PageLayout.WebPart_Custom: - { - return 1; - } - default: - return 1; - } - - return 1; - } - - /// - /// Translates the given zone value and page layout to a row number - /// - /// Web part zone id - /// Layout of the web part page - /// Row value - private static int GetRow(string zoneId, PageLayout layout) - { - switch (layout) - { - case PageLayout.WebPart_HeaderFooterThreeColumns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("MiddleColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_FullPageVertical: - case PageLayout.WebPart_2010_TwoColumnsLeft: - { - return 1; - } - case PageLayout.WebPart_HeaderLeftColumnBody: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Body", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - break; - } - case PageLayout.WebPart_HeaderRightColumnBody: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Body", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - break; - } - case PageLayout.WebPart_HeaderFooter2Columns4Rows: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row1", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row2", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row3", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("Row4", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_HeaderFooter4ColumnsTopRow: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - break; - } - case PageLayout.WebPart_LeftColumnHeaderFooterTopRow3Columns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("LeftColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - else if (zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 4; - } - break; - } - case PageLayout.WebPart_RightColumnHeaderFooterTopRow3Columns: - { - if (zoneId.Equals("Header", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("RightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 1; - } - else if (zoneId.Equals("TopRow", StringComparison.InvariantCultureIgnoreCase)) - { - return 2; - } - else if (zoneId.Equals("CenterLeftColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterColumn", StringComparison.InvariantCultureIgnoreCase) || - zoneId.Equals("CenterRightColumn", StringComparison.InvariantCultureIgnoreCase)) - { - return 3; - } - else if (zoneId.Equals("Footer", StringComparison.InvariantCultureIgnoreCase)) - { - return 4; - } - break; - } - case PageLayout.WebPart_Custom: - { - return 1; - } - default: - return 1; - } - - return 1; - } - - /// - /// Loads the telemetry and properties for the client objects - /// - /// - private static void LoadClientObjects(ClientContext clientContext) - { - if (clientContext != null) - { - clientContext.ClientTag = $"SPDev:PageTransformator"; - - // Load all web properties needed further one - clientContext.Load(clientContext.Web, w => w.Id, w => w.Url, w => w.ServerRelativeUrl, w => w.RootFolder.WelcomePage, w => w.Language); - clientContext.Load(clientContext.Site, s => s.RootWeb.ServerRelativeUrl, s => s.Id, s => s.Url); - // Use regular ExecuteQuery as we want to send our custom clienttag - clientContext.ExecuteQuery(); - } - } - - private SourcePageInformation ReadSourcePageInformation(ListItem sourcePage) - { - var result = new SourcePageInformation(); - - result.Author = sourcePage[SharePointConstants.CreatedByField] as FieldUserValue; - result.Editor = sourcePage[SharePointConstants.ModifiedByField] as FieldUserValue; - - // Ensure to interprete time correctly: SPO stores in UTC, but we'll need to push back in local - if (DateTime.TryParse(sourcePage[SharePointConstants.CreatedField].ToString(), out DateTime created)) - { - DateTime createdIsUtc = DateTime.SpecifyKind(created, DateTimeKind.Utc); - result.Created = createdIsUtc.ToLocalTime(); - } - if (DateTime.TryParse(sourcePage[SharePointConstants.ModifiedField].ToString(), out DateTime modified)) - { - DateTime modifiedIsUtc = DateTime.SpecifyKind(modified, DateTimeKind.Utc); - result.Modified = modifiedIsUtc.ToLocalTime(); - } - - // Determine if the page is the home page in the source site - // Check if the transformed page is the web's home page - var sourceClientContext = sourcePage.Context as ClientContext; - - if (sourceClientContext.Web.RootFolder.IsPropertyAvailable("WelcomePage") && !string.IsNullOrEmpty(sourceClientContext.Web.RootFolder.WelcomePage)) - { - logger.LogInformation( - SharePointTransformationResources.Info_WelcomePageSettingsIsPresent - .CorrelateString(this.taskId)); - - var homePageUrl = sourceClientContext.Web.RootFolder.WelcomePage; - var homepageName = Path.GetFileName(sourceClientContext.Web.RootFolder.WelcomePage); - string fileLeafRef = null; - if (sourcePage.TryGetFieldValue(SharePointConstants.FileLeafRefField, out fileLeafRef)) - { - if (homepageName.Equals(fileLeafRef, StringComparison.InvariantCultureIgnoreCase)) - { - logger.LogInformation( - SharePointTransformationResources.Info_TransformSourcePageIsHomePage - .CorrelateString(this.taskId)); - result.IsHomePage = true; - } - else - { - logger.LogInformation( - SharePointTransformationResources.Info_TransformSourcePageIsNotHomePage - .CorrelateString(this.taskId)); - result.IsHomePage = false; - } - } - } - - return result; - } - - /// - /// Load Web Parts from Wiki Content page on On-Premises Server - /// - /// - /// - /// - private void LoadWebPartsInWikiContentFromOnPremisesServer(List webparts, Microsoft.SharePoint.Client.File wikiPage, List webPartsToRetrieve) - { - var context = wikiPage.Context as ClientContext; - - // Load web part manager and use it to load each web part - LimitedWebPartManager limitedWPManager = wikiPage.GetLimitedWebPartManager(PersonalizationScope.Shared); - context.Load(limitedWPManager); - - // Don't load export mode as it's not available in on-premises, we'll try to complement this data afterwards with data retrieved via the web service call - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - // Check if the web part was loaded when we loaded the web parts collection via the web part manager - if (!Guid.TryParse(webPartToRetrieve.Id, out Guid webPartToRetrieveGuid)) - { - // Skip since guid is not valid - continue; - } - - // Sometimes the returned wiki html contains web parts which are not anymore on the page...using the ExceptionHandlingScope - // we can handle these errors server side while just doing a single roundtrip - var scope = new ExceptionHandlingScope(context); - using (scope.StartScope()) - { - using (scope.StartTry()) - { - webPartToRetrieve.WebPartDefinition = limitedWPManager.WebParts.GetByControlId(webPartToRetrieve.ControlId); - context.Load(webPartToRetrieve.WebPartDefinition, wp => wp.Id, wp => wp.WebPart.Title, wp => wp.WebPart.ZoneIndex, wp => wp.WebPart.IsClosed, wp => wp.WebPart.Hidden, wp => wp.WebPart.Properties); - } - using (scope.StartCatch()) - { - - } - } - } - - context.ExecuteQueryRetry(); - - // Load the web part XML for the web parts that do allow it - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - if (webPartToRetrieve.WebPartDefinition != null) - { - // Important to only process the web parts that did not return an error in the previous server call - if (webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.HasValue && webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.Value == false) - { - // Let's try to retrieve the web part XML...this will fail for web parts that are not exportable - var webPartXml = ExportWebPartXmlWorkaround(wikiPage, webPartToRetrieve.WebPartDefinition.Id.ToString()); - webPartToRetrieve.WebPartXmlOnPremises = webPartXml; - } - } - } - - // Determine the web part type and store it in the web parts array - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - if (webPartToRetrieve.WebPartDefinition != null && (webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.HasValue && webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.Value == false)) - { - Dictionary webPartProperties = null; - webPartProperties = webPartToRetrieve.WebPartDefinition.WebPart.Properties.FieldValues; - - if (string.IsNullOrEmpty(webPartToRetrieve.WebPartXmlOnPremises)) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - webPartToRetrieve.WebPartType = GetTypeFromProperties(webPartProperties, true); - } - else - { - webPartToRetrieve.WebPartType = GetType(webPartToRetrieve.WebPartXmlOnPremises); - } - - logger.LogInformation( - SharePointTransformationResources.Info_ContentTransformFoundSourceWebParts - .CorrelateString(this.taskId), - wikiPage.ServerRelativeUrl, - webPartToRetrieve.WebPartDefinition.WebPart.Title, - webPartToRetrieve.WebPartType.GetTypeShort()); - - webparts.Add(new WebPartEntity() - { - Title = webPartToRetrieve.WebPartDefinition.WebPart.Title, - Type = webPartToRetrieve.WebPartType, - Id = webPartToRetrieve.WebPartDefinition.Id, - ServerControlId = webPartToRetrieve.Id, - Row = webPartToRetrieve.Row, - Column = webPartToRetrieve.Column, - Order = webPartToRetrieve.Order, - ZoneId = "", - ZoneIndex = (uint)webPartToRetrieve.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = webPartToRetrieve.WebPartDefinition.WebPart.IsClosed, - Hidden = webPartToRetrieve.WebPartDefinition.WebPart.Hidden, - // Properties = Properties(webPartProperties, webPartToRetrieve.WebPartType, - // webPartToRetrieve.WebPartXmlOnPremises), - }); - } - } - } - - /// - /// Load Web Parts from Wiki Content page on Online Server - /// - /// - /// - /// - internal void LoadWebPartsInWikiContentFromServer(List webparts, Microsoft.SharePoint.Client.File wikiPage, List webPartsToRetrieve) - { - var context = wikiPage.Context as ClientContext; - - // Load web part manager and use it to load each web part - LimitedWebPartManager limitedWPManager = wikiPage.GetLimitedWebPartManager(PersonalizationScope.Shared); - context.Load(limitedWPManager); - - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - // Check if the web part was loaded when we loaded the web parts collection via the web part manager - if (!Guid.TryParse(webPartToRetrieve.Id, out Guid webPartToRetrieveGuid)) - { - // Skip since guid is not valid - continue; - } - - // Sometimes the returned wiki html contains web parts which are not anymore on the page...using the ExceptionHandlingScope - // we can handle these errors server side while just doing a single roundtrip - var scope = new ExceptionHandlingScope(context); - using (scope.StartScope()) - { - using (scope.StartTry()) - { - webPartToRetrieve.WebPartDefinition = limitedWPManager.WebParts.GetByControlId(webPartToRetrieve.ControlId); - context.Load(webPartToRetrieve.WebPartDefinition, wp => wp.Id, wp => wp.WebPart.ExportMode, wp => wp.WebPart.Title, wp => wp.WebPart.ZoneIndex, wp => wp.WebPart.IsClosed, wp => wp.WebPart.Hidden, wp => wp.WebPart.Properties); - } - using (scope.StartCatch()) - { - - } - } - } - context.ExecuteQueryRetry(); - - - // Load the web part XML for the web parts that do allow it - bool isDirty = false; - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - // Important to only process the web parts that did not return an error in the previous server call - if (webPartToRetrieve.WebPartDefinition != null && (webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.HasValue && webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.Value == false)) - { - // Retry to load the properties, sometimes they're not retrieved - webPartToRetrieve.WebPartDefinition.EnsureProperty(wp => wp.Id); - webPartToRetrieve.WebPartDefinition.WebPart.EnsureProperties(wp => wp.ExportMode, wp => wp.Title, wp => wp.ZoneIndex, wp => wp.IsClosed, wp => wp.Hidden, wp => wp.Properties); - - if (webPartToRetrieve.WebPartDefinition.WebPart.ExportMode == WebPartExportMode.All) - { - webPartToRetrieve.WebPartXml = limitedWPManager.ExportWebPart(webPartToRetrieve.WebPartDefinition.Id); - isDirty = true; - } - } - } - if (isDirty) - { - context.ExecuteQueryRetry(); - } - - // Determine the web part type and store it in the web parts array - foreach (var webPartToRetrieve in webPartsToRetrieve) - { - if (webPartToRetrieve.WebPartDefinition != null && (webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.HasValue && webPartToRetrieve.WebPartDefinition.ServerObjectIsNull.Value == false)) - { - // Important to only process the web parts that did not return an error in the previous server call - if (webPartToRetrieve.WebPartDefinition.WebPart.ExportMode != WebPartExportMode.All) - { - // Use different approach to determine type as we can't export the web part XML without indroducing a change - webPartToRetrieve.WebPartType = GetTypeFromProperties(webPartToRetrieve.WebPartDefinition.WebPart.Properties.FieldValues); - } - else - { - webPartToRetrieve.WebPartType = GetType(webPartToRetrieve.WebPartXml.Value); - } - - logger.LogInformation( - SharePointTransformationResources.Info_ContentTransformFoundSourceWebParts - .CorrelateString(this.taskId), - wikiPage.ServerRelativeUrl, - webPartToRetrieve.WebPartDefinition.WebPart.Title, - webPartToRetrieve.WebPartType.GetTypeShort()); - - webparts.Add(new WebPartEntity() - { - Title = webPartToRetrieve.WebPartDefinition.WebPart.Title, - Type = webPartToRetrieve.WebPartType, - Id = webPartToRetrieve.WebPartDefinition.Id, - ServerControlId = webPartToRetrieve.Id, - Row = webPartToRetrieve.Row, - Column = webPartToRetrieve.Column, - Order = webPartToRetrieve.Order, - ZoneId = "", - ZoneIndex = (uint)webPartToRetrieve.WebPartDefinition.WebPart.ZoneIndex, - IsClosed = webPartToRetrieve.WebPartDefinition.WebPart.IsClosed, - Hidden = webPartToRetrieve.WebPartDefinition.WebPart.Hidden, - Properties = ExtractProperties(webPartToRetrieve.WebPartDefinition.WebPart.Properties.FieldValues, - webPartToRetrieve.WebPartType, webPartToRetrieve.WebPartXml == null ? "" : - webPartToRetrieve.WebPartXml.Value), - }); - } - } - } - - /// - /// Checks the PageTransformation XML data to know which properties need to be kept for the given web part and collects their values - /// - /// Properties collection retrieved when we loaded the web part - /// Type of the web part - /// Web part XML - /// Collection of the requested property/value pairs - internal Dictionary ExtractProperties(Dictionary properties, string webPartType, string webPartXml) - { - var webPartMappingProvider = serviceProvider.GetService(); - - // Load the mapping configuration - WebPartMapping mappingFile = ((SharePointWebPartMappingProvider)webPartMappingProvider) - .LoadMappingFile(this.spOptions.Value.WebPartMappingFile); - - Dictionary propertiesToKeep = new Dictionary(); - - List propertiesToRetrieve = mappingFile.BaseWebPart.Properties.ToList(); - - //For older versions of SharePoint the type in the mapping would not match. Use the TypeShort Comparison. - var webPartProperties = mappingFile.WebParts.Where(p => p.Type.GetTypeShort().Equals(webPartType.GetTypeShort(), StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (webPartProperties != null && webPartProperties.Properties != null) - { - foreach (var p in webPartProperties.Properties.ToList()) - { - if (!propertiesToRetrieve.Contains(p)) - { - propertiesToRetrieve.Add(p); - } - } - } - - if (string.IsNullOrEmpty(webPartXml)) - { - if (webPartType.GetTypeShort() == WebParts.Client.GetTypeShort()) - { - // Special case since we don't know upfront which properties are relevant here...so let's take them all - foreach (var prop in properties) - { - if (!propertiesToKeep.ContainsKey(prop.Key)) - { - propertiesToKeep.Add(prop.Key, prop.Value != null ? prop.Value.ToString() : ""); - } - } - } - else - { - // Special case where we did not have export rights for the web part XML, assume this is a V3 web part - foreach (var property in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(property.Name) && properties.ContainsKey(property.Name)) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, properties[property.Name] != null ? properties[property.Name].ToString() : ""); - } - } - } - } - } - else - { - var xml = XElement.Parse(webPartXml); - var xmlns = xml.XPathSelectElement("*").GetDefaultNamespace(); - if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v3", StringComparison.InvariantCultureIgnoreCase)) - { - if (webPartType.GetTypeShort() == WebParts.Client.GetTypeShort()) - { - // Special case since we don't know upfront which properties are relevant here...so let's take them all - foreach (var prop in properties) - { - if (!propertiesToKeep.ContainsKey(prop.Key)) - { - propertiesToKeep.Add(prop.Key, prop.Value != null ? prop.Value.ToString() : ""); - } - } - } - else - { - // the retrieved properties are sufficient - foreach (var property in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(property.Name) && properties.ContainsKey(property.Name)) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, properties[property.Name] != null ? properties[property.Name].ToString() : ""); - } - } - } - } - } - else if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v2", StringComparison.InvariantCultureIgnoreCase)) - { - foreach (var property in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(property.Name)) - { - if (properties.ContainsKey(property.Name)) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, properties[property.Name] != null ? properties[property.Name].ToString() : ""); - } - } - else - { - // check XMl for property - var v2Element = xml.Descendants(xmlns + property.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, v2Element.Value); - } - } - - // Some properties do have their own namespace defined - if (webPartType.GetTypeShort() == WebParts.SimpleForm.GetTypeShort() && property.Name.Equals("Content", StringComparison.InvariantCultureIgnoreCase)) - { - // Load using the http://schemas.microsoft.com/WebPart/v2/SimpleForm namespace - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/SimpleForm"; - v2Element = xml.Descendants(xmlcontentns + property.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, v2Element.Value); - } - } - } - else if (webPartType.GetTypeShort() == WebParts.ContentEditor.GetTypeShort()) - { - if (property.Name.Equals("ContentLink", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("Content", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("PartStorage", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/ContentEditor"; - v2Element = xml.Descendants(xmlcontentns + property.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, v2Element.Value); - } - } - } - } - else if (webPartType.GetTypeShort() == WebParts.Xml.GetTypeShort()) - { - if (property.Name.Equals("XMLLink", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("XML", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("XSLLink", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("XSL", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("PartStorage", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/Xml"; - v2Element = xml.Descendants(xmlcontentns + property.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, v2Element.Value); - } - } - } - } - else if (webPartType.GetTypeShort() == WebParts.SiteDocuments.GetTypeShort()) - { - if (property.Name.Equals("UserControlledNavigation", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("ShowMemberships", StringComparison.InvariantCultureIgnoreCase) || - property.Name.Equals("UserTabs", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "urn:schemas-microsoft-com:sharepoint:portal:sitedocumentswebpart"; - v2Element = xml.Descendants(xmlcontentns + property.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(property.Name)) - { - propertiesToKeep.Add(property.Name, v2Element.Value); - } - } - } - } - } - } - } - } - } - - return propertiesToKeep; - } - - internal PublishingFunctionProcessor.FieldType MapToFunctionProcessorFieldType(PublishingMapping.WebPartProperyType propertyType) - { - switch (propertyType) - { - case PublishingMapping.WebPartProperyType.@string: return PublishingFunctionProcessor.FieldType.String; - case PublishingMapping.WebPartProperyType.@bool: return PublishingFunctionProcessor.FieldType.Bool; - case PublishingMapping.WebPartProperyType.guid: return PublishingFunctionProcessor.FieldType.Guid; - case PublishingMapping.WebPartProperyType.integer: return PublishingFunctionProcessor.FieldType.Integer; - case PublishingMapping.WebPartProperyType.datetime: return PublishingFunctionProcessor.FieldType.DateTime; - } - - return PublishingFunctionProcessor.FieldType.String; - } - - internal int GetNextOrder(int row, int col, int order, List webparts) - { - // do we already have web parts in the same row and column - var wp = webparts.Where(p => p.Row == row && p.Column == col); - - if (order > 0) - { - // Multiply with 100 to leave space for possible multiple web parts living in an ordered web part zone - return order * 100; - } - else - { - if (wp != null && wp.Any()) - { - var lastWp = wp.OrderBy(p => p.Order).Last(); - return lastWp.Order + 1; - } - else - { - return 1; - } - } - } - - internal T GetFixedWebPartProperty(ClientContext sourceContext, PublishingMapping.FixedWebPart webPart, string name, T defaultValue) - { - var property = webPart.Property.Where(p => p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (property != null) - { - if (property.Value.StartsWith("$Resources:")) - { - property.Value = GetResourceString(sourceContext, property.Value); - } - - if (property.Value is T) - { - return (T)(object)property.Value; - } - try - { - return (T)Convert.ChangeType(property.Value, typeof(T)); - } - catch (InvalidCastException) - { - return defaultValue; - } - } - - return defaultValue; - } - - internal Dictionary CastAsPropertiesDictionary(PublishingMapping.FixedWebPart webPart) - { - Dictionary props = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - foreach (var prop in webPart.Property) - { - props.Add(prop.Name, prop.Value); - } - - return props; - } - - /// - /// Returns the translated value for a resource string - /// - /// Context of the site - /// Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) - /// Translated string - internal string GetResourceString(ClientContext context, string resource) - { - int lcid = (int)context.Web.EnsureProperty(p => p.Language); - - var resourceString = resource.Replace("$Resources:", "").Replace(";", ""); - var splitResourceString = resourceString.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - string resourceFile = "core"; - string resourceKey = null; - if (splitResourceString.Length == 2) - { - resourceFile = splitResourceString[0]; - resourceKey = splitResourceString[1]; - } - else - { - resourceKey = splitResourceString[0]; - } - - ClientResult result = Microsoft.SharePoint.Client.Utilities.Utility.GetLocalizedString(context, $"$Resources:{resourceKey}", resourceFile, int.Parse(lcid.ToString())); - context.ExecuteQueryRetry(); - - if (result == null) - { - return resource; - } - - return result.Value; - } - - internal PageLayout MapToLayout(PublishingMapping.PageLayoutPageLayoutTemplate layoutFromTemplate, bool includeVerticalColumn) - { - switch (layoutFromTemplate) - { - case PublishingMapping.PageLayoutPageLayoutTemplate.OneColumn: return PageLayout.Wiki_OneColumn; - case PublishingMapping.PageLayoutPageLayoutTemplate.TwoColumns: return PageLayout.Wiki_TwoColumns; - case PublishingMapping.PageLayoutPageLayoutTemplate.TwoColumnsWithSidebarLeft: return PageLayout.Wiki_TwoColumnsWithSidebar; - case PublishingMapping.PageLayoutPageLayoutTemplate.TwoColumnsWithSidebarRight: return PageLayout.Wiki_TwoColumnsWithSidebar; - case PublishingMapping.PageLayoutPageLayoutTemplate.TwoColumnsWithHeader: return PageLayout.Wiki_TwoColumnsWithHeader; - case PublishingMapping.PageLayoutPageLayoutTemplate.TwoColumnsWithHeaderAndFooter: return PageLayout.Wiki_TwoColumnsWithHeaderAndFooter; - case PublishingMapping.PageLayoutPageLayoutTemplate.ThreeColumns: return PageLayout.Wiki_ThreeColumns; - case PublishingMapping.PageLayoutPageLayoutTemplate.ThreeColumnsWithHeader: return PageLayout.Wiki_ThreeColumnsWithHeader; - case PublishingMapping.PageLayoutPageLayoutTemplate.ThreeColumnsWithHeaderAndFooter: return PageLayout.Wiki_ThreeColumnsWithHeaderAndFooter; - case PublishingMapping.PageLayoutPageLayoutTemplate.AutoDetect: - { - if (includeVerticalColumn) - { - return PageLayout.PublishingPage_AutoDetectWithVerticalColumn; - } - else - { - return PageLayout.PublishingPage_AutoDetect; - } - } - default: return PageLayout.Wiki_OneColumn; - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMetadataMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMetadataMappingProvider.cs deleted file mode 100644 index d0c8df6115..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointMetadataMappingProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointMetadataMappingProvider : IMetadataMappingProvider - { - private ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointMetadataMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - } - - /// - /// Maps a Metadata Field Value from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity - public Task MapMetadataFieldAsync(MetadataMappingProviderInput input, CancellationToken token = default) - { - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapMetadataFieldAsync" - .CorrelateString(input.Context.Task.Id)); - return Task.FromResult(new MetadataMappingProviderOutput()); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProvider.cs deleted file mode 100644 index 58478f1538..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProvider.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.MappingFiles.Publishing; -using PnP.Core.Transformation.SharePoint.Publishing; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using System.Xml.Schema; -using System.Xml.Serialization; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointPageLayoutMappingProvider : IPageLayoutMappingProvider - { - private ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - private readonly IMemoryCache memoryCache; - - private Guid taskId; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointPageLayoutMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - } - - /// - /// Maps a classic Page Layout into a modern Page Layout - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity - public async Task MapPageLayoutAsync(PageLayoutMappingProviderInput input, CancellationToken token = default) - { - this.taskId = input.Context.Task.Id; - - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapPageLayoutAsync" - .CorrelateString(this.taskId)); - - // Check that we have input data - if (input == null) throw new ArgumentNullException(nameof(input)); - - // Validate source item - var sourceItem = input.Context.SourceItem as SharePointSourceItem; - if (sourceItem == null) throw new ApplicationException(SharePointTransformationResources.Error_MissiningSharePointInputItem); - - // Get a reference to the file and item that we need to transform - var sourceContext = sourceItem.SourceContext; - var pageFile = sourceItem.SourceContext.Web.GetFileByServerRelativeUrl(sourceItem.Id.ServerRelativeUrl); - var pageItem = pageFile.ListItemAllFields; - sourceContext.Load(pageFile); - sourceContext.Load(pageItem); - await sourceContext.ExecuteQueryAsync().ConfigureAwait(false); - - // Load the mapping configuration - PublishingPageTransformation mapping = LoadMappingFile(this.options.Value.PageLayoutMappingFile); - - // Retrieve the mapping page layout from the mapping file, if any - var publishingPageTransformationModel = mapping.PageLayouts.FirstOrDefault(p => p.Name.Equals(input.PageLayout, StringComparison.InvariantCultureIgnoreCase)); - - // No dedicated layout mapping found, let's see if there's an other page layout mapping that also applies for this page layout - if (publishingPageTransformationModel == null) - { - // Fill a list of additional page layout mappings that can be used - Dictionary additionalMappings = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var pageLayout in mapping.PageLayouts.Where(p => !String.IsNullOrEmpty(p.AlsoAppliesTo))) - { - var possiblePageLayouts = pageLayout.AlsoAppliesTo.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); - if (possiblePageLayouts.Length > 0) - { - foreach (var possiblePageLayout in possiblePageLayouts) - { - // Only add the first possible page layout mapping, if a given page layout is defined multiple times then the first reference wins - if (!additionalMappings.ContainsKey(possiblePageLayout)) - { - additionalMappings.Add(possiblePageLayout, pageLayout.Name); - } - } - } - } - - if (additionalMappings.Count > 0) - { - if (additionalMappings.ContainsKey(input.PageLayout)) - { - publishingPageTransformationModel = mapping.PageLayouts.FirstOrDefault(p => p.Name.Equals(additionalMappings[input.PageLayout], StringComparison.InvariantCultureIgnoreCase)); - } - } - } - - // No layout provided via either the default mapping or custom mapping file provided - if (publishingPageTransformationModel == null) - { - publishingPageTransformationModel = GeneratePageLayout(pageItem); - - logger.LogInformation( - SharePointTransformationResources.Info_PageLayoutMappingGeneration - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl, input.PageLayout); - } - - // Still no layout...can't continue... - if (publishingPageTransformationModel == null) - { - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_NoPageLayoutTransformationModel, input.PageLayout, pageFile.ServerRelativeUrl); - logger.LogInformation(errorMessage.CorrelateString(this.taskId)); - throw new Exception(errorMessage); - } - - logger.LogInformation( - SharePointTransformationResources.Info_PageLayoutMappingBeingUsed - .CorrelateString(this.taskId), - pageFile.ServerRelativeUrl, input.PageLayout, publishingPageTransformationModel.Name); - - return new SharePointPageLayoutMappingProviderOutput { PageLayout = publishingPageTransformationModel }; - } - - private PageLayout GeneratePageLayout(Microsoft.SharePoint.Client.ListItem pageItem) - { - var pageLayoutFileUrl = pageItem.GetPageLayoutFileUrl(); - - // Let's try to generate a 'basic' model and use that...not optimal, but better than bailing out. - var pageLayoutAnalyzer = this.serviceProvider.GetService(); - var newPageLayoutMapping = pageLayoutAnalyzer.AnalysePageLayoutFromPublishingPage(pageItem, this.taskId); - - // Return to requestor - return newPageLayoutMapping; - } - - private PublishingPageTransformation LoadMappingFile(string mappingFilePath = null) - { - // Prepare the result variable - PublishingPageTransformation result = null; - - if (string.IsNullOrEmpty(mappingFilePath)) - { - mappingFilePath = "stream.pagelayoutmapping.xml"; - } - - // Check if we already have the mapping file in the in-memory cache - if (this.memoryCache.TryGetValue(mappingFilePath, out result)) - { - return result; - } - - // Create the xml mapping serializer - XmlSerializer xmlMapping = new XmlSerializer(typeof(PublishingPageTransformation)); - - // If we don't have the mapping file as an input - if (string.IsNullOrEmpty(mappingFilePath) || - !System.IO.File.Exists(mappingFilePath)) - { - // We use the default one, without validation - using (var mappingStream = this.GetType().Assembly.GetManifestResourceStream("PnP.Core.Transformation.SharePoint.MappingFiles.pagelayoutmapping.xml")) - { - using (var reader = XmlReader.Create(mappingStream)) - { - result = (PublishingPageTransformation)xmlMapping.Deserialize(reader); - } - } - } - else - { - using (Stream schema = this.GetType().Assembly.GetManifestResourceStream("PnP.Core.Transformation.SharePoint.MappingFiles.pagelayoutmapping.xsd")) - { - using (var mappingStream = new FileStream(mappingFilePath, FileMode.Open)) - { - // Ensure the provided file complies with the current schema - ValidateSchema(schema, mappingStream); - - using (var reader = XmlReader.Create(mappingStream)) - { - result = (PublishingPageTransformation)xmlMapping.Deserialize(reader); - } - } - } - } - - // Cache the mapping file into the in-memory cache - this.memoryCache.Set(mappingFilePath, result); - - return result; - } - - private void ValidateSchema(Stream schema, Stream stream) - { - // Load the template into an XDocument - XDocument xml = XDocument.Load(stream); - - using (var schemaReader = XmlReader.Create(schema)) - { - // Prepare the XML Schema Set - XmlSchemaSet schemas = new XmlSchemaSet(); - schemas.Add(SharePointConstants.PageLayoutMappingSchema, schemaReader); - - // Set stream back to start - stream.Seek(0, SeekOrigin.Begin); - - xml.Validate(schemas, (o, e) => - { - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_WebPartMappingSchemaValidation, e.Message); - this.logger.LogError(e.Exception, - errorMessage.CorrelateString(this.taskId)); - throw new ApplicationException(errorMessage); - }); - } - } - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProviderOutput.cs deleted file mode 100644 index 9a9b908735..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointPageLayoutMappingProviderOutput.cs +++ /dev/null @@ -1,13 +0,0 @@ -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingFiles.Publishing; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// Custom version of the PageLayoutMappingProviderOutput to manage a SharePoint specific output - /// - internal class SharePointPageLayoutMappingProviderOutput : PageLayoutMappingProviderOutput - { - public PageLayout PageLayout { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointTaxonomyMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointTaxonomyMappingProvider.cs deleted file mode 100644 index 7696d91d7b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointTaxonomyMappingProvider.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointTaxonomyMappingProvider : ITaxonomyMappingProvider - { - private ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointTaxonomyMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - } - - /// - /// Maps a Taxonomy Term from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity - public Task MapTermAsync(TaxonomyMappingProviderInput input, CancellationToken token = default) - { - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapTermAsync" - .CorrelateString(input.Context.Task.Id)); - return Task.FromResult(new TaxonomyMappingProviderOutput()); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUrlMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUrlMappingProvider.cs deleted file mode 100644 index 04deb12199..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUrlMappingProvider.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointUrlMappingProvider : IUrlMappingProvider - { - private ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointUrlMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - } - - /// - /// Maps a URL from classic to modern - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity - public async Task MapUrlAsync(UrlMappingProviderInput input, CancellationToken token = default) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapUrlAsync" - .CorrelateString(input.Context.Task.Id)); - - // Try cast - var sharePointSourceItem = input.Context.SourceItem as SharePointSourceItem; - if (sharePointSourceItem == null) - { - throw new ArgumentException($"Only source item of type {typeof(SharePointSourceItem)} is supported"); - } - - string pagesLibrary = await ResolveSitePagesLibraryAsync(sharePointSourceItem.SourceContext).ConfigureAwait(false); - - sharePointSourceItem.SourceContext.Load(sharePointSourceItem.SourceContext.Web, w => w.Url); - sharePointSourceItem.SourceContext.Load(sharePointSourceItem.SourceContext.Site, w => w.Url); - await sharePointSourceItem.SourceContext.ExecuteQueryAsync().ConfigureAwait(false); - - // Prepare result variable - var resultText = input?.Text ?? string.Empty; - - Uri sourceWebUrl = new Uri(sharePointSourceItem.SourceContext.Web.Url); - Uri sourceSiteUrl = new Uri(sharePointSourceItem.SourceContext.Site.Url); - - Uri origTargetWebUrl = sourceWebUrl; - Uri origSourceSiteUrl = sourceSiteUrl; - Uri targetWebUrl = input.Context.Task.TargetContext.Web.Url; - - bool isSubSite = origSourceSiteUrl.IsBaseOf(origTargetWebUrl); - - if (this.options.Value.UrlMappings != null && this.options.Value.UrlMappings.Count > 0) - { - foreach (var urlMapping in this.options.Value.UrlMappings) - { - resultText = RewriteUrl(resultText, - urlMapping.SourceUrl, - urlMapping.TargetUrl); - } - } - - if (!this.options.Value.SkipUrlRewrite) - { - // ******************************************** - // Default URL rewriting logic - // ******************************************** - // - // Root site collection URL rewriting: - // http://contoso.com/sites/portal -> https://contoso.sharepoint.com/sites/hr - // http://contoso.com/sites/portal/pages -> https://contoso.sharepoint.com/sites/hr/sitepages - // /sites/portal -> /sites/hr - // /sites/portal/pages -> /sites/hr/sitepages - // - // If site is a sub site then we also by rewrite the sub URL's - // http://contoso.com/sites/portal/hr -> https://contoso.sharepoint.com/sites/hr - // http://contoso.com/sites/portal/hr/pages -> https://contoso.sharepoint.com/sites/hr/sitepages - // /sites/portal/hr -> /sites/hr - // /sites/portal/hr/pages -> /sites/hr/sitepages - - // Rewrite url's from pages library to sitepages - if (!string.IsNullOrEmpty(pagesLibrary)) - { - var pagesSourceWebUrl = sourceWebUrl.Combine(pagesLibrary); - var sitePagesTargetWebUrl = targetWebUrl.Combine("sitepages"); - - if (pagesSourceWebUrl.Scheme == "https" || pagesSourceWebUrl.Scheme == "http") - { - resultText = RewriteUrl(resultText, - pagesSourceWebUrl.ToString(), - sitePagesTargetWebUrl.ToString()); - - // Make relative for next replacement attempt - pagesSourceWebUrl = new Uri(pagesSourceWebUrl.AbsolutePath, UriKind.Relative); - sitePagesTargetWebUrl = new Uri(sitePagesTargetWebUrl.AbsolutePath, UriKind.Relative); - } - - resultText = RewriteUrl(resultText, pagesSourceWebUrl.ToString(), sitePagesTargetWebUrl.ToString()); - } - - // Rewrite web urls - if (sourceWebUrl.Scheme == "https" || sourceWebUrl.Scheme == "http") - { - resultText = RewriteUrl(resultText, sourceWebUrl.ToString(), targetWebUrl.ToString()); - } - - // Make relative for next replacement attempt - resultText = RewriteUrl(resultText, - sourceWebUrl.AbsolutePath.TrimEnd('/'), - targetWebUrl.AbsolutePath.TrimEnd('/')); - - if (isSubSite) - { - // reset URLs - sourceSiteUrl = origSourceSiteUrl; - targetWebUrl = origTargetWebUrl; - - // Rewrite url's from pages library to sitepages - if (!string.IsNullOrEmpty(pagesLibrary)) - { - var pagesSourceSiteUrl = UrlUtility.Combine(sourceSiteUrl, pagesLibrary); - var sitePagesTargetWebUrl = UrlUtility.Combine(targetWebUrl, "sitepages"); - - if (pagesSourceSiteUrl.Scheme == "https" || pagesSourceSiteUrl.Scheme == "http") - { - resultText = RewriteUrl(resultText, - pagesSourceSiteUrl.ToString(), - sitePagesTargetWebUrl.ToString()); - } - - // Make relative for next replacement attempt - resultText = RewriteUrl(resultText, - pagesSourceSiteUrl.AbsolutePath.TrimEnd('/'), - sitePagesTargetWebUrl.AbsolutePath.TrimEnd('/')); - } - - // Rewrite root site urls - if (sourceSiteUrl.Scheme == "https" || sourceSiteUrl.Scheme == "http") - { - resultText = RewriteUrl(resultText, - sourceSiteUrl.ToString(), - targetWebUrl.ToString()); - } - - // Make relative for next replacement attempt - resultText = RewriteUrl(resultText, - sourceSiteUrl.AbsolutePath.TrimEnd('/'), - targetWebUrl.AbsolutePath.TrimEnd('/')); - } - } - - return new UrlMappingProviderOutput(resultText); - } - - private async Task ResolveSitePagesLibraryAsync(ClientContext context) - { - var lists = context.Web.Lists; - context.Load(lists, lists => lists.Include( - l => l.Title, - l => l.BaseTemplate, - l => l.RootFolder, - l => l.Fields.Include(f => f.InternalName))); - await context.ExecuteQueryRetryAsync().ConfigureAwait(false); - - foreach (var list in lists) - { - if (list.BaseTemplate == (int)ListTemplateType.WebPageLibrary) - { - // The site pages library has the CanvasContent1 column, - // using that to distinguish between Site Pages and other wiki page libraries - if (list.Fields.FirstOrDefault(f => f.InternalName == "CanvasContent1") != null) - { - return list.RootFolder.Name; - } - } - } - - // Fallback to the default "sitepages" value - return "sitepages"; - } - - private string RewriteUrl(string input, string from, string to) - { - // Do not replace this character - breaks HTML - if (from != "/" && !IsRoot(from)) - { - var regex = new Regex($"{Regex.Escape(from)}", RegexOptions.IgnoreCase); - if (regex.IsMatch(input)) - { - string before = input; - input = regex.Replace(input, to); - } - } - - return input; - } - - private bool IsRoot(string url) - { - var baseUrl = url.GetBaseUrl(); - if (baseUrl.Equals(url, StringComparison.InvariantCultureIgnoreCase)) - { - return true; - } - - return false; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUserMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUserMappingProvider.cs deleted file mode 100644 index dca44533fe..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointUserMappingProvider.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointUserMappingProvider : IUserMappingProvider - { - private ILogger logger; - private readonly IOptions options; - private readonly IServiceProvider serviceProvider; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// Service provider - public SharePointUserMappingProvider(ILogger logger, - IOptions options, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.serviceProvider = serviceProvider; - } - - /// - /// Maps a user UPN from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity - public async Task MapUserAsync(UserMappingProviderInput input, CancellationToken token = default) - { - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapUserAsync" - .CorrelateString(input.Context.Task.Id)); - - // Should never happen, but just in case - if (string.IsNullOrEmpty(input?.UserPrincipalName)) - { - return new UserMappingProviderOutput { UserPrincipalName = input?.UserPrincipalName }; - } - - // When transforming from SPO without explicit enabling spo to spo - if (!this.options.Value.ShouldMapUsers) - { - return new UserMappingProviderOutput { UserPrincipalName = input?.UserPrincipalName }; - } - - logger.LogDebug( - SharePointTransformationResources.Debug_UserTransformPrincipalInput - .CorrelateString(input.Context.Task.Id), - input.UserPrincipalName.GetUserName()); - - // In case we have a valid list of user mappings - if (this.options.Value.UserMappings != null && this.options.Value.UserMappings.Count > 0) - { - logger.LogInformation( - SharePointTransformationResources.Info_UserTransformDefaultMapping - .CorrelateString(input.Context.Task.Id), - input.UserPrincipalName.GetUserName()); - - // Try to find user mapping - // We don't like mulitple matches, the first match wins - var userNameToCheck = input.UserPrincipalName.GetUserName(); - var result = input.UserPrincipalName; - - var userMapping = this.options.Value.UserMappings.FirstOrDefault(o => o.SourceUser.Equals(userNameToCheck, StringComparison.InvariantCultureIgnoreCase)); - if (userMapping != null) - { - // Let's get the target user - result = userMapping.TargetUser; - - // Log successfull user mapping - logger.LogInformation( - SharePointTransformationResources.Info_UserTransformSuccess - .CorrelateString(input.Context.Task.Id), - userNameToCheck, result); - - // Ensure user in the target site collection if not yet done - await input.Context.Task.TargetContext.Web.EnsureUserAsync(result).ConfigureAwait(false); - } - else - { - // Log unsuccessfull user mapping - logger.LogInformation( - SharePointTransformationResources.Info_UserTransformMappingNotFound - .CorrelateString(input.Context.Task.Id), - userNameToCheck); - } - - return new UserMappingProviderOutput { UserPrincipalName = result }; - } - - //Returns original input to pass through where re-mapping is not required - return new UserMappingProviderOutput { UserPrincipalName = input.UserPrincipalName }; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointWebPartMappingProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointWebPartMappingProvider.cs deleted file mode 100644 index 913fd036e1..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/MappingProviders/SharePointWebPartMappingProvider.cs +++ /dev/null @@ -1,592 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.Functions; -using PnP.Core.Transformation.SharePoint.MappingFiles; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using PnP.Core.Transformation.SharePoint.Services.MappingProviders; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using System.Xml.Schema; -using System.Xml.Serialization; -using System.Xml.XPath; - -namespace PnP.Core.Transformation.SharePoint.MappingProviders -{ - /// - /// SharePoint implementation of - /// - public class SharePointWebPartMappingProvider : IWebPartMappingProvider - { - private ILogger logger; - private readonly IOptions spOptions; - private readonly IMemoryCache memoryCache; - private readonly FunctionProcessor functionProcessor; - private readonly IServiceProvider serviceProvider; - - private Guid taskId; - - /// - /// Main constructor for the mapping provider - /// - /// Logger for tracing activities - /// Configuration options - /// The SharePoint Function processor - /// Service provider - public SharePointWebPartMappingProvider(ILogger logger, - IOptions spOptions, - FunctionProcessor functionProcessor, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.spOptions = spOptions ?? throw new ArgumentNullException(nameof(spOptions)); - this.functionProcessor = functionProcessor ?? throw new ArgumentNullException(nameof(functionProcessor)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - } - - /// - /// Maps a classic Web Part into a modern Web Part - /// - /// The input for the mapping activity - /// The cancellation token to use, if any - /// The output of the mapping activity -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async Task MapWebPartAsync(WebPartMappingProviderInput input, CancellationToken token = default) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - // Check that we have input data - if (input == null) throw new ArgumentNullException(nameof(input)); - - this.taskId = input.Context.Task.Id; - - // Try to convert the mapping provider input into a typed version - var specializedInput = input as SharePointWebPartMappingProviderInput; - if (specializedInput == null) throw new ArgumentException(SharePointTransformationResources.Error_InvalidWebPartMappingProviderInput); - - // Configure the SharePoint Function service instance - this.functionProcessor.Init(specializedInput.Context, specializedInput.SourceContext); - - // Define the variable holding the input web part - var webPart = input.WebPart; - - // Prepare the output object - var result = new WebPartMappingProviderOutput(); - - logger.LogInformation( - $"Invoked: {this.GetType().Namespace}.{this.GetType().Name}.MapWebPartAsync" - .CorrelateString(this.taskId)); - logger.LogInformation( - SharePointTransformationResources.Info_ContentWebPartBeingTransformed - .CorrelateString(this.taskId), - webPart.Title, webPart.TypeShort()); - - // Title bar will never be migrated - if (input.WebPart.Type == WebParts.TitleBar) - { - logger.LogInformation( - SharePointTransformationResources.Info_NotTransformingTitleBar - .CorrelateString(this.taskId)); - return result; - } - - // Load the mapping configuration - WebPartMapping mappingFile = LoadMappingFile(this.spOptions.Value.WebPartMappingFile); - - var sourceItem = input.Context.SourceItem as SharePointSourceItem; - - if (sourceItem == null) - { - throw new ApplicationException(SharePointTransformationResources.Error_MissiningSharePointInputItem); - } - - // Find the default mapping, will be used for webparts for which the model does not contain a mapping - var defaultMapping = mappingFile.BaseWebPart.Mappings.Mapping.FirstOrDefault(p => p.Default == true); - if (defaultMapping == null) - { - logger.LogError( - SharePointTransformationResources.Error_NoDefaultMappingFound - .CorrelateString(this.taskId)); - throw new Exception(SharePointTransformationResources.Error_NoDefaultMappingFound); - } - - // Assign the default mapping, if we've a more specific mapping than that will overwrite this mapping - Mapping mapping = defaultMapping; - - // Does the web part have a mapping defined? - var webPartData = mappingFile.WebParts.FirstOrDefault(p => p.Type.GetTypeShort() == webPart.Type.GetTypeShort()); - - // Check for cross site transfer support - if (webPartData != null && input.IsCrossSiteTransformation) - { - if (!webPartData.CrossSiteTransformationSupported) - { - logger.LogWarning( - SharePointTransformationResources.Warning_CrossSiteNotSupported - .CorrelateString(this.taskId)); - return result; - } - } - - var globalTokens = PrepareGlobalTokens(this.spOptions.Value.MappingProperties); //TODO: Bug Here, the mappings are not included in the tokens. - - if (webPartData != null && webPartData.Mappings != null) - { - // Add site level (e.g. site) tokens to the web part properties and model so they can be used in the same manner as a web part property - UpdateWebPartDataProperties(webPart, webPartData, mappingFile, globalTokens); - - string selectorResult = null; - try - { - // The mapping can have a selector function defined, if so it will be executed. - // If a selector was executed the selectorResult will contain the name of the mapping to use - logger.LogDebug( - SharePointTransformationResources.Debug_ProcessingSelectorFunctions - .CorrelateString(this.taskId)); - selectorResult = this.functionProcessor.Process(ref webPartData, webPart); - } - catch (Exception ex) - { - // NotAvailableAtTargetException is used to "skip" a web part since it's not valid for the target site collection (only applies to cross site collection transfers) - if (ex.InnerException is NotAvailableAtTargetException) - { - logger.LogError( - SharePointTransformationResources.Error_NotValidForTargetSiteCollection - .CorrelateString(this.taskId)); - } - - if (ex.InnerException is MediaWebpartConfigurationException) - { - logger.LogError( - SharePointTransformationResources.Error_MediaWebpartConfiguration - .CorrelateString(this.taskId)); - } - - logger.LogError( - $"{SharePointTransformationResources.Error_AnErrorOccurredFunctions} - {ex.Message}" - .CorrelateString(this.taskId)); - throw; - } - - Mapping webPartMapping = null; - // Get the needed mapping: - // - use the mapping returned by the selector - // - if no selector then take the default mapping - // - if no mapping found we'll fall back to the default web part mapping - if (!string.IsNullOrEmpty(selectorResult)) - { - webPartMapping = webPartData.Mappings.Mapping.Where(p => p.Name.Equals(selectorResult, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - } - else - { - // If there's only one mapping let's take that one, even if not specified as default - if (webPartData.Mappings.Mapping.Length == 1) - { - webPartMapping = webPartData.Mappings.Mapping[0]; - } - else - { - webPartMapping = webPartData.Mappings.Mapping.FirstOrDefault(p => p.Default == true); - } - } - - if (webPartMapping != null) - { - mapping = webPartMapping; - } - else - { - logger.LogWarning( - SharePointTransformationResources.Warning_ContentWebPartMappingNotFound - .CorrelateString(this.taskId)); - } - - // Process mapping specific functions (if any) - if (!String.IsNullOrEmpty(mapping.Functions)) - { - try - { - logger.LogInformation( - SharePointTransformationResources.Info_ProcessingMappingFunctions - .CorrelateString(this.taskId)); - functionProcessor.ProcessMappingFunctions(ref webPartData, webPart, mapping); - } - catch (Exception ex) - { - // NotAvailableAtTargetException is used to "skip" a web part since it's not valid for the target site collection (only applies to cross site collection transfers) - if (ex.InnerException is NotAvailableAtTargetException) - { - logger.LogError( - SharePointTransformationResources.Error_NotValidForTargetSiteCollection - .CorrelateString(this.taskId)); - } - - logger.LogError( - $"{SharePointTransformationResources.Error_AnErrorOccurredFunctions} - {ex.Message}" - .CorrelateString(this.taskId)); - throw; - } - } - } - - return new SharePointWebPartMappingProviderOutput(mapping); - } - - /// - /// Prepares placeholders for global tokens - /// - /// - private Dictionary PrepareGlobalTokens(Dictionary mappingProperties) - { - Dictionary globalTokens = new Dictionary(5); - - // Add the fixed properties tokens - globalTokens.Add("Host", "{Host}"); - globalTokens.Add("Web", "{Web}"); - globalTokens.Add("SiteCollection", "{SiteCollection}"); - globalTokens.Add("WebId", "{WebId}"); - globalTokens.Add("SiteId", "{SiteId}"); - - - if (mappingProperties != null) - { - // Add the properties provided via configuration - foreach (var property in mappingProperties) - { - globalTokens.Add(property.Key, property.Value); - } - } - - return globalTokens; - } - - internal WebPartMapping LoadMappingFile(string mappingFilePath = null) - { - // Prepare the result variable - WebPartMapping result = null; - - if (string.IsNullOrEmpty(mappingFilePath)) - { - mappingFilePath = "stream.webpartmapping.xml"; - } - - // Check if we already have the mapping file in the in-memory cache - if (this.memoryCache.TryGetValue(mappingFilePath, out result)) - { - return result; - } - - // Create the xml mapping serializer - XmlSerializer xmlMapping = new XmlSerializer(typeof(WebPartMapping)); - - // If we don't have the mapping file as an input - if (string.IsNullOrEmpty(mappingFilePath) || - !System.IO.File.Exists(mappingFilePath)) - { - // We use the default one, without validation - using (var mappingStream = this.GetType().Assembly.GetManifestResourceStream("PnP.Core.Transformation.SharePoint.MappingFiles.webpartmapping.xml")) - { - using (var reader = XmlReader.Create(mappingStream)) - { - result = (WebPartMapping)xmlMapping.Deserialize(reader); - } - } - } - else - { - using (Stream schema = this.GetType().Assembly.GetManifestResourceStream("PnP.Core.Transformation.SharePoint.MappingFiles.webpartmapping.xsd")) - { - using (var mappingStream = new FileStream(mappingFilePath, FileMode.Open)) - { - // Ensure the provided file complies with the current schema - ValidateSchema(schema, mappingStream); - - using (var reader = XmlReader.Create(mappingStream)) - { - result = (WebPartMapping)xmlMapping.Deserialize(reader); - } - } - } - } - - // Cache the mapping file into the in-memory cache - this.memoryCache.Set(mappingFilePath, result); - - return result; - } - - private void ValidateSchema(Stream schema, Stream stream) - { - // Load the template into an XDocument - XDocument xml = XDocument.Load(stream); - - using (var schemaReader = XmlReader.Create(schema)) - { - // Prepare the XML Schema Set - XmlSchemaSet schemas = new XmlSchemaSet(); - schemas.Add(SharePointConstants.PageTransformationSchema, schemaReader); - - // Set stream back to start - stream.Seek(0, SeekOrigin.Begin); - - xml.Validate(schemas, (o, e) => - { - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - SharePointTransformationResources.Error_WebPartMappingSchemaValidation, e.Message); - this.logger.LogError(e.Exception, errorMessage - .CorrelateString(this.taskId)); - throw new ApplicationException(errorMessage); - }); - } - } - - private static Dictionary ExtractProperties(WebPartMappingProviderInput input, WebPartMapping mapping) - { - // Storage for properties to keep - Dictionary propertiesToKeep = new Dictionary(); - - // List of properties to retrieve - List propertiesToRetrieve = mapping.BaseWebPart.Properties.ToList(); - - // For older versions of SharePoint the type in the mapping would not match. Use the TypeShort Comparison. - var webPartProperties = mapping.WebParts.FirstOrDefault(p => p.Type.GetTypeShort().Equals(input.SourceComponentType.GetTypeShort(), StringComparison.InvariantCultureIgnoreCase)); - if (webPartProperties != null && webPartProperties.Properties != null) - { - foreach (var p in webPartProperties.Properties.ToList()) - { - if (!propertiesToRetrieve.Contains(p)) - { - propertiesToRetrieve.Add(p); - } - } - } - - // If we don't have the raw content - if (string.IsNullOrEmpty(input.SourceComponentRawContent)) - { - if (input.SourceComponentType.GetTypeShort() == WebParts.Client.GetTypeShort()) - { - // Special case since we don't know upfront which properties are relevant here...so let's take them all - foreach (var p in input.SourceProperties) - { - if (!propertiesToKeep.ContainsKey(p.Key)) - { - propertiesToKeep.Add(p.Key, p.Value != null ? p.Value.ToString() : string.Empty); - } - } - } - else - { - // Special case where we did not have export rights for the web part XML, assume this is a V3 web part - foreach (var p in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(p.Name) && - input.SourceProperties.ContainsKey(p.Name) && - !propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, input.SourceProperties[p.Name] != null ? - input.SourceProperties[p.Name].ToString() : string.Empty); - } - } - } - } - else - { - var xml = XElement.Parse(input.SourceComponentRawContent); - var xmlns = xml.XPathSelectElement("*").GetDefaultNamespace(); - if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v3", - StringComparison.InvariantCultureIgnoreCase)) - { - if (input.SourceComponentType.GetTypeShort() == WebParts.Client.GetTypeShort()) - { - // Special case since we don't know upfront which properties are relevant here...so let's take them all - foreach (var p in input.SourceProperties) - { - if (!propertiesToKeep.ContainsKey(p.Key)) - { - propertiesToKeep.Add(p.Key, p.Value != null ? p.Value.ToString() : string.Empty); - } - } - } - else - { - // the retrieved properties are sufficient - foreach (var p in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(p.Name) && - input.SourceProperties.ContainsKey(p.Name) && - !propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, input.SourceProperties[p.Name] != null ? - input.SourceProperties[p.Name].ToString() : string.Empty); - } - } - } - } - else if (xmlns.NamespaceName.Equals("http://schemas.microsoft.com/WebPart/v2", - StringComparison.InvariantCultureIgnoreCase)) - { - foreach (var p in propertiesToRetrieve) - { - if (!string.IsNullOrEmpty(p.Name)) - { - if (input.SourceProperties.ContainsKey(p.Name)) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, input.SourceProperties[p.Name] != null ? - input.SourceProperties[p.Name].ToString() : ""); - } - } - else - { - // check XMl for property - var v2Element = xml.Descendants(xmlns + p.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, v2Element.Value); - } - } - - // Some properties do have their own namespace defined - if (input.SourceComponentType.GetTypeShort() == WebParts.SimpleForm.GetTypeShort() && - p.Name.Equals("Content", StringComparison.InvariantCultureIgnoreCase)) - { - // Load using the http://schemas.microsoft.com/WebPart/v2/SimpleForm namespace - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/SimpleForm"; - v2Element = xml.Descendants(xmlcontentns + p.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, v2Element.Value); - } - } - } - else if (input.SourceComponentType.GetTypeShort() == WebParts.ContentEditor.GetTypeShort()) - { - if (p.Name.Equals("ContentLink", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("Content", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("PartStorage", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/ContentEditor"; - v2Element = xml.Descendants(xmlcontentns + p.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, v2Element.Value); - } - } - } - } - else if (input.SourceComponentType.GetTypeShort() == WebParts.Xml.GetTypeShort()) - { - if (p.Name.Equals("XMLLink", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("XML", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("XSLLink", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("XSL", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("PartStorage", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "http://schemas.microsoft.com/WebPart/v2/Xml"; - v2Element = xml.Descendants(xmlcontentns + p.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, v2Element.Value); - } - } - } - } - else if (input.SourceComponentType.GetTypeShort() == WebParts.SiteDocuments.GetTypeShort()) - { - if (p.Name.Equals("UserControlledNavigation", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("ShowMemberships", StringComparison.InvariantCultureIgnoreCase) || - p.Name.Equals("UserTabs", StringComparison.InvariantCultureIgnoreCase)) - { - XNamespace xmlcontentns = "urn:schemas-microsoft-com:sharepoint:portal:sitedocumentswebpart"; - v2Element = xml.Descendants(xmlcontentns + p.Name).FirstOrDefault(); - if (v2Element != null) - { - if (!propertiesToKeep.ContainsKey(p.Name)) - { - propertiesToKeep.Add(p.Name, v2Element.Value); - } - } - } - } - } - } - } - } - } - - return propertiesToKeep; - } - - private void UpdateWebPartDataProperties(WebPartEntity webPart, WebPart webPartData, WebPartMapping mapping, Dictionary globalProperties) - { - List tempList = new List(); - if (webPartData.Properties != null) - { - tempList.AddRange(webPartData.Properties); - } - - // Add properties listed on the Base web part - var baseProperties = mapping.BaseWebPart.Properties; - foreach (var baseProperty in baseProperties) - { - // Only add the base property once as the webPartData.Properties collection is reused across web parts and pages - if (!tempList.Any(p => p.Name.Equals(baseProperty.Name, StringComparison.InvariantCultureIgnoreCase))) - { - // Add parameter to model - tempList.Add(new Property() - { - Functions = baseProperty.Functions, - Name = baseProperty.Name, - Type = PropertyType.@string - }); - } - } - - // NOTE: We replaced Global Properties, with tokens - - // Add global properties - foreach (var token in globalProperties) - { - // Add property to web part - if (!webPart.Properties.ContainsKey(token.Key)) - { - webPart.Properties.Add(token.Key, token.Value); - } - - // Only add the global property once as the webPartData.Properties collection is reused across web parts and pages - var propAlreadyAdded = tempList.Where(p => p.Name.Equals(token.Key, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (propAlreadyAdded == null) - { - // Add parameter to model - tempList.Add(new Property() - { - Functions = "", - Name = token.Key, - Type = PropertyType.@string - }); - } - } - - webPartData.Properties = tempList.ToArray(); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/PageLayout.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/PageLayout.cs deleted file mode 100644 index 23e966e4cf..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/PageLayout.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Possible layouts used by wiki or webpart pages - /// - internal enum PageLayout - { - Wiki_OneColumn = 0, - Wiki_TwoColumns = 1, - Wiki_TwoColumnsWithSidebar = 2, - Wiki_TwoColumnsWithHeader = 3, - Wiki_TwoColumnsWithHeaderAndFooter = 4, - Wiki_ThreeColumns = 5, - Wiki_ThreeColumnsWithHeader = 6, - Wiki_ThreeColumnsWithHeaderAndFooter = 7, - Wiki_Custom = 8, - WebPart_HeaderFooterThreeColumns = 20, - WebPart_FullPageVertical = 21, - WebPart_HeaderLeftColumnBody = 22, - WebPart_HeaderRightColumnBody = 23, - WebPart_HeaderFooter2Columns4Rows = 24, - WebPart_HeaderFooter4ColumnsTopRow = 25, - WebPart_LeftColumnHeaderFooterTopRow3Columns = 26, - WebPart_RightColumnHeaderFooterTopRow3Columns = 27, - WebPart_Custom = 28, - WebPart_2010_TwoColumnsLeft = 29, - PublishingPage_AutoDetect = 40, - PublishingPage_AutoDetectWithVerticalColumn = 41, - Unknown = 1024, - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SPVersion.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/SPVersion.cs deleted file mode 100644 index 29dba20ba6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SPVersion.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Enum listing the SharePoint versions as used by the transformation engine - /// - internal enum SPVersion - { - /// - /// SharePoint Online - /// - SPO = 0, - /// - /// SharePoint 2019 (on-premises) - /// - SP2019 = 1, - /// - /// SharePoint 2016 (on-premises) - /// - SP2016 = 2, - /// - /// SharePoint 2016 legacy (on-premises) - /// - SP2016Legacy = 3, - /// - /// SharePoint 2013 (on-premises) - /// - SP2013 = 4, - /// - /// SharePoint 2013 legacy (on-premises) - /// - SP2013Legacy = 5, - /// - /// Unknown version - /// - Unknown = 100, - /// - /// Unsupported version - /// - Unsupported = 200 - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageInformation.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageInformation.cs deleted file mode 100644 index 81e8388c1b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageInformation.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.SharePoint.Client; -using System; - -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Class to hold information about the source SharePoint page to transform - /// - internal class SourcePageInformation - { - /// - /// SharePoint version of the source - /// - internal SPVersion SourceVersion { get; set; } - - /// - /// SharePoint version number of the source - /// - internal string SourceVersionNumber { get; set; } - - /// - /// The Author of the page - /// - public FieldUserValue Author { get; set; } - - /// - /// The last Editor of the page - /// - public FieldUserValue Editor { get; set; } - - /// - /// The Creation date time of the page - /// - public DateTime Created { get; set; } - - /// - /// The last Update date time of the page - /// - public DateTime Modified { get; set; } - - /// - /// The parent Folder of the page, if any - /// - public string Folder { get; set; } - - /// - /// Defines the Type of the source page - /// - public SourcePageType PageType { get; set; } - - /// - /// Defines whether the source page is a root page for the source site - /// - public bool IsRootPage { get; set; } - - /// - /// Defines whether the source page is the Home Page for the source site - /// - public bool IsHomePage { get; set; } - - /// - /// Defines the Page Layout for the publishing page, if any - /// - public string PageLayout { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageType.cs deleted file mode 100644 index c7100a0885..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/SourcePageType.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Defines the different types of pages supported - /// - internal enum SourcePageType - { - /// - /// Undefined page type - /// - Undefined, - /// - /// Classic web part page - /// - WebPartPage, - /// - /// Classic wiki page - /// - WikiPage, - /// - /// Classic blog page - /// - BlogPage, - /// - /// Classic publishing page - /// - PublishingPage, - /// - /// Delve blog page - /// - DelveBlogPage, - /// - /// ASP.NET (ASPX) page - /// - AspxPage, - /// - /// Modern client side page - /// - ClientSidePage, - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartPlaceHolder.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartPlaceHolder.cs deleted file mode 100644 index 09aa19c8a9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartPlaceHolder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.SharePoint.Client; -using Microsoft.SharePoint.Client.WebParts; - -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Represents a placeholder for a classic Web Part to be transformed - /// - internal class WebPartPlaceHolder - { - public string Id { get; set; } - public string ControlId { get; set; } - public int Row { get; set; } - public int Column { get; set; } - public int Order { get; set; } - public WebPartDefinition WebPartDefinition { get; set; } - public string WebPartXmlOnPremises { get; set; } - public ClientResult WebPartXml { get; set; } - - public string WebPartType { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartZoneLayoutMap.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartZoneLayoutMap.cs deleted file mode 100644 index e2ecef6586..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebPartZoneLayoutMap.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Model -{ - internal class WebPartZoneLayoutMap - { - public string ZoneId { get; set; } - public string Type { get; set; } - public int Occurances { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartEntity.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartEntity.cs deleted file mode 100644 index 767d5c1a11..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartEntity.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Entity to describe a web part on a wiki or webpart page called from web services - /// - internal class WebServiceWebPartEntity - { - /// - /// Default constructor - /// - public WebServiceWebPartEntity() - { - this.Properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - } - - /// - /// Type of the web part - /// - public string Type { get; set; } - - /// - /// Id of the web part - /// - public Guid Id { get; set; } - - /// - /// Dictionary with web part properties - /// - public Dictionary Properties { get; set; } - - /// - /// Returns the shortened web part type name - /// - /// Shortened web part type name - public string TypeShort() - { - string name = Type; - var typeSplit = Type.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - if (typeSplit.Length > 0) - { - name = typeSplit[0]; - } - - return $"{name}"; - } - - /// - /// Returns Dictionary with web part properties as string,object - /// - /// Dictionary with web part properties as string,object - public Dictionary PropertiesAsStringObjectDictionary() - { - Dictionary castedCollection = new Dictionary(); - - foreach (var item in this.Properties) - { - castedCollection.Add(item.Key, (object)item.Value); - } - - return castedCollection; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartProperties.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartProperties.cs deleted file mode 100644 index 83c3aac581..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Model/WebServiceWebPartProperties.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Model -{ - /// - /// Entity to describe a web part on a wiki or webpart page called from web services - /// - internal class WebServiceWebPartProperties - { - /// - /// Default constructor - /// - public WebServiceWebPartProperties() - { - this.Properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - } - - /// - /// Type of the web part - /// - public string Type { get; set; } - - /// - /// Id of the web part - /// - public Guid Id { get; set; } - - /// - /// Control id of the web part - /// - public string ControlId { get; set; } - - /// - /// Dictionary with web part properties - /// - public Dictionary Properties { get; set; } - - /// - /// Zone id of the web part - /// - public string ZoneId { get; set; } - - /// - /// Returns the shortened web part type name - /// - /// Shortened web part type name - public string TypeShort() - { - string name = Type; - var typeSplit = Type.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - if (typeSplit.Length > 0) - { - name = typeSplit[0]; - } - - return $"{name}"; - } - - /// - /// Returns Dictionary with web part properties as string,object - /// - /// Dictionary with web part properties as string,object - public Dictionary PropertiesAsStringObjectDictionary() - { - Dictionary castedCollection = new Dictionary(); - - foreach (var item in this.Properties) - { - castedCollection.Add(item.Key, (object)item.Value); - } - - return castedCollection; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/PageTransformatorExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/PageTransformatorExtensions.cs deleted file mode 100644 index 15440798f6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/PageTransformatorExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using System; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// Extensions for - /// - public static class PageTransformatorExtensions - { - /// - /// Transforms a SharePoint page uri - /// - /// The page transformator to use - /// The source context - /// The destination context - /// The source URI - /// The resulting URI - public static Task TransformSharePointAsync(this IPageTransformator pageTransformator, ClientContext sourceContext, PnPContext targetContext, Uri sourceUri) - { - if (pageTransformator == null) throw new ArgumentNullException(nameof(pageTransformator)); - if (sourceContext == null) throw new ArgumentNullException(nameof(sourceContext)); - if (targetContext == null) throw new ArgumentNullException(nameof(targetContext)); - if (sourceUri == null) throw new ArgumentNullException(nameof(sourceUri)); - - var sourceItemId = new SharePointSourceItemId(sourceUri); - - var sourceProvider = new SharePointSourceProvider(sourceContext); - return pageTransformator.TransformAsync(new PageTransformationTask(sourceProvider, sourceItemId, targetContext)); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/PnP.Core.Transformation.SharePoint.csproj b/src/sdk/PnP.Core.Transformation.SharePoint/PnP.Core.Transformation.SharePoint.csproj deleted file mode 100644 index 51c48b964e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/PnP.Core.Transformation.SharePoint.csproj +++ /dev/null @@ -1,107 +0,0 @@ - - - - netstandard2.0;net8.0;net9.0;net10.0 - 10.0 - 1.15.0 - false - PnP 2025 - PnP - PnP - [EXPERIMENTAL - Not yet officially released!] The PnP Core Transformation library provides a set of tools for the PnP Transformation Framework. Internally it is based on PnP.Core and PnP.Core.Auth. - https://aka.ms/pnp/coresdk - https://github.com/pnp/pnpcore - git - true - - - true - snupkg - true - true - Debug;Release - nugeticon.png - MIT - true - ..\pnp.core.snk - - - - TRACE - obj\Debug\PnP.Core.Transformation.SharePoint.xml - Nightly - - - True - - - - obj\Debug\PnP.Core.Transformation.SharePoint.xml - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - - - - True - \ - - - - - - - - - - True - True - SharePointTransformationResources.resx - - - - - - ResXFileCodeGenerator - SharePointTransformationResources.Designer.cs - - - - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutAnalyser.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutAnalyser.cs deleted file mode 100644 index 15f6b88cff..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutAnalyser.cs +++ /dev/null @@ -1,973 +0,0 @@ -using AngleSharp.Dom; -using AngleSharp.Html.Parser; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.MappingFiles.Publishing; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml.Serialization; - -namespace PnP.Core.Transformation.SharePoint.Publishing -{ - /// - /// Utility class to analyze a Page Layout - /// - internal class PageLayoutAnalyser - { - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly IMemoryCache memoryCache; - - /// - /// Simple entity for the extracted blocks of data - /// - internal class ExtractedHtmlBlocksEntity - { - internal ExtractedHtmlBlocksEntity() - { - WebPartFields = new List(); - WebPartZones = new List(); - FixedWebParts = new List(); - } - - internal List WebPartFields { get; set; } - internal List WebPartZones { get; set; } - internal List FixedWebParts { get; set; } - } - - private PublishingPageTransformation _mapping; - private string _defaultFileName = "PageLayoutMapping.xml"; - private HtmlParser parser; - private Dictionary _contentTypeFieldCache; - private Dictionary _pageLayoutFileCache; - - #region Construction - - /// - /// Analyse Page Layouts class constructor - /// - public PageLayoutAnalyser(ILogger logger, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - - _mapping = new PublishingPageTransformation(); - _contentTypeFieldCache = new Dictionary(); - _pageLayoutFileCache = new Dictionary(); - - parser = new HtmlParser(); - } - - #endregion - - #region Public interface - - ///// - ///// Main entry point into the class to analyse the page layouts - ///// - ///// Skip OOB page layouts - //public void AnalyseAll(ListItem pageLayoutItem, bool skipOOBPageLayouts = false) - //{ - // // Determine if ‘default’ layouts for the OOB page layouts - // // When there’s no layout we “generate” a best effort one and store it in cache. Generation can - // // be done by looking at the field types and inspecting the layout aspx file. This same generation - // // part can be used in point 2 for customers to generate a starting layout mapping file which they then can edit - // // Don't assume that you are in a top level site, you maybe in a sub site - - // using (ClientContext siteCollContext = EnsureSiteCollectionContext(pageLayoutItem.Context as ClientContext)) - // { - // var spPageLayouts = GetAllPageLayouts(siteCollContext); - - // if (spPageLayouts != null) - // { - // foreach (ListItem layout in spPageLayouts) - // { - // try - // { - // if (skipOOBPageLayouts) - // { - // // Check if this is an OOB page layout and skip if so - // string pageLayoutName = Path.GetFileNameWithoutExtension(layout[SharePointConstants.FileLeafRefField].ToString()); - // var oobPageLayoutFile = SharePointConstants.OobPublishingPageLayouts.Any(o => o.Equals(pageLayoutName, StringComparison.InvariantCultureIgnoreCase)); - // if (oobPageLayoutFile) - // { - // logger.LogInformation(this.correlationService.CorrelateString( - // taskId, - // SharePointTransformationResources.Info_OOBPageLayoutSkipped), pageLayoutName); - // continue; - // } - // } - - // AnalysePageLayout(layout); - // } - // catch (Exception) - // { - // logger.LogError(this.correlationService.CorrelateString( - // taskId, - // SharePointTransformationResources.Error_CannotProcessPageLayoutAnalyseAll)); - // } - - // } - // } - // else - // { - // logger.LogInformation(this.correlationService.CorrelateString( - // taskId, - // SharePointTransformationResources.Info_AnalyserNoLayoutsFound)); - // } - // } - //} - - /// - /// Analyses a single page layout from a provided file - /// - /// Page layout list item - /// ID of the transformation task - public PageLayout AnalysePageLayout(ListItem pageLayoutItem, Guid taskId) - { - try - { - // Get the associated page layout content type - string assocContentType = pageLayoutItem[SharePointConstants.PublishingAssociatedContentTypeField].ToString(); - var assocContentTypeParts = assocContentType.Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries); - - // Load content type fields in memory once - var contentTypeFields = LoadContentTypeFields(pageLayoutItem, assocContentTypeParts[1], taskId); - - // Extract page header - var extractedHeader = ExtractPageHeaderFromPageLayoutAssociatedContentType(contentTypeFields, taskId); - - // Analyze the pagelayout file content - var extractedHtmlBlocks = ExtractControlsFromPageLayoutHtml(pageLayoutItem, taskId); - extractedHtmlBlocks.WebPartFields = CleanExtractedWebPartFields(extractedHtmlBlocks.WebPartFields, contentTypeFields, taskId); - - // Detect the fields that will become metadata in the target site - var extractedMetaDataFields = ExtractMetaDataFromPageLayoutAssociatedContentType(contentTypeFields, extractedHtmlBlocks.WebPartFields, extractedHeader, taskId); - - var metaData = new MetaData - { - Field = extractedMetaDataFields - }; - - // Combine all data to a single PageLayout mapping - var layoutMapping = new PageLayout() - { - // Display name of the page layout - Name = Path.GetFileNameWithoutExtension(pageLayoutItem[SharePointConstants.FileLeafRefField].ToString()), - // Default to no page header for now - PageHeader = extractedHeader != null ? PageLayoutPageHeader.Custom : PageLayoutPageHeader.None, - // Default to autodetect layout model - PageLayoutTemplate = PageLayoutPageLayoutTemplate.AutoDetect, - // The content type to be used on the target modern page - AssociatedContentType = "", - // Set the header details (if any) - Header = extractedHeader, - // Fields that will become metadata fields for the target page - MetaData = metaData, - // Fields that will become web parts on the target page - WebParts = extractedHtmlBlocks.WebPartFields.Count > 0 ? extractedHtmlBlocks.WebPartFields.ToArray() : null, - // Web part zones that can hold zero or more web parts - WebPartZones = extractedHtmlBlocks.WebPartZones.Count > 0 ? extractedHtmlBlocks.WebPartZones.ToArray() : null, - // Fixed web parts, this are web parts which are 'hardcoded' in the pagelayout aspx file - FixedWebParts = extractedHtmlBlocks.FixedWebParts.Count > 0 ? extractedHtmlBlocks.FixedWebParts?.ToArray() : null, - }; - - // Add to mappings list - if (_mapping.PageLayouts != null) - { - var expandMappings = _mapping.PageLayouts.ToList(); - - // Prevent duplicate references to the same page layout - if (!expandMappings.Any(o => o.Name == layoutMapping.Name)) - { - expandMappings.Add(layoutMapping); - } - - _mapping.PageLayouts = expandMappings.ToArray(); - } - else - { - _mapping.PageLayouts = new[] { layoutMapping }; - } - - logger.LogInformation( - SharePointTransformationResources.Info_AnalyserMappingLayout - .CorrelateString(taskId), layoutMapping.Name); - - return layoutMapping; - - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_CannotProcessPageLayoutAnalyse - .CorrelateString(taskId)); - } - - return null; - } - - /// - /// Determine the page layout from a publishing page - /// - /// Publishing page to analyze the page layout for - /// ID of the transformation task - public PageLayout AnalysePageLayoutFromPublishingPage(ListItem publishingPage, Guid taskId) - { - ClientContext siteCollContext = EnsureSiteCollectionContext(publishingPage.Context as ClientContext); - - // NOTE: ListItemExtensions class contains this logic - reuse. - // TODO: Make more defensive, this could represent the wrong item - var pageLayoutFileUrl = publishingPage.GetPageLayoutFileUrl(); - - if (!string.IsNullOrEmpty(pageLayoutFileUrl)) - { - Uri uri = new Uri(pageLayoutFileUrl); - var host = $"{uri.Scheme}://{uri.Host}"; - var path = pageLayoutFileUrl.Replace(host, ""); - - var file = siteCollContext.Web.GetFileByServerRelativeUrl(path); - siteCollContext.Load(file, o => o.ListItemAllFields); - siteCollContext.ExecuteQueryRetry(); - - return AnalysePageLayout(file.ListItemAllFields, taskId); - } - else - { - logger.LogWarning( - SharePointTransformationResources.Warning_PageLayoutsCannotBeDetermined - .CorrelateString(taskId)); - } - - return null; - } - - /// - /// Generate the mapping file to output from the analysis - /// - /// Mapping file fully qualified path - public string GenerateMappingFile() - { - return GenerateMappingFile(Environment.CurrentDirectory, _defaultFileName); - } - - /// - /// Generate the mapping file to output from the analysis - /// - /// Folder to generate the file in - /// Mapping file fully qualified path - public string GenerateMappingFile(string folder) - { - return GenerateMappingFile(folder, _defaultFileName); - } - - /// - /// Generate the mapping file to output from the analysis - /// - /// Folder to generate the file in - /// name of the mapping file - /// Mapping file fully qualified path - public string GenerateMappingFile(string folder, string fileName) - { - try - { - XmlSerializer xmlMapping = new XmlSerializer(typeof(PublishingPageTransformation)); - - var mappingFileName = $"{folder}\\{fileName}"; - using (StreamWriter sw = new StreamWriter(mappingFileName, false)) - { - xmlMapping.Serialize(sw, _mapping); - } - - logger.LogInformation( - SharePointTransformationResources.Info_XmlMappingSavedAs, mappingFileName); - - return mappingFileName; - } - catch (Exception ex) - { - logger.LogError( - SharePointTransformationResources.Error_CannotWriteToXmlFile, - ex.Message, ex.StackTrace); - } - - return string.Empty; - } - #endregion - - #region Internal methods - /// - /// Determines the page layouts in the current web - /// - internal ListItemCollection GetAllPageLayouts(ClientContext siteCollContext) - { - try - { - var masterPageGallery = siteCollContext.Web.GetCatalog((int)ListTemplateType.MasterPageCatalog); - siteCollContext.Load(masterPageGallery, x => x.RootFolder.ServerRelativeUrl); - - var query = new CamlQuery - { - // Use query Scope='RecursiveAll' to iterate through sub folders of Master page library because we might have file in folder hierarchy - // Ensure that we are getting layouts with at least one published version, not hidden layouts - ViewXml = - $"" + - $"" + - $"" + - $"" + - $"" + - $"aspx" + - $"" + - $"" + - $"" + - $"" + - $"1.0" + - $"" + - $"" + - $"{SharePointConstants.PageLayoutBaseContentTypeId}" + - $"" + - $"" + - $"" + - $"" + - $"0" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" + - $"" - }; - - var galleryItems = masterPageGallery.GetItems(query); - siteCollContext.Load(masterPageGallery); - siteCollContext.Load(galleryItems); - siteCollContext.Load(galleryItems, i => i.Include(o => o.DisplayName), - i => i.Include(o => o.File), - i => i.Include(o => o.File.ServerRelativeUrl)); - - siteCollContext.ExecuteQueryRetry(); - - var galleryItemsCount = galleryItems.Count; - - logger.LogInformation( - SharePointTransformationResources.Info_AnalyserFoundItems, - galleryItemsCount); - - return galleryItemsCount > 0 ? galleryItems : null; - - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserCouldNotFindLayouts); - } - - return null; - } - - /// - /// Get Metadata mapping from the page layout associated content type - /// - private MetaDataField[] ExtractMetaDataFromPageLayoutAssociatedContentType(FieldCollection spFields, List webPartFields, Header extractedHeader, Guid taskId) - { - List fields = new List(); - - try - { - - // Get unique field types for which we've defined web part mapping defaults - List fieldTypesToSkip = new List(); - foreach (var defaultWebPartField in PublishingDefaults.WebPartFieldProperties) - { - if (!fieldTypesToSkip.Contains(defaultWebPartField.FieldType)) - { - fieldTypesToSkip.Add(defaultWebPartField.FieldType); - } - } - - // Skip hidden fields by default - foreach (var spField in spFields.Where(o => o.Hidden == false)) - { - if (!PublishingDefaults.IgnoreMetadataFields.Any(o => o.Equals(spField.InternalName, StringComparison.InvariantCultureIgnoreCase))) - { - // Was this field already defined as a field that will be mapped to a web part? If so it can't be a metadata field - if (webPartFields.Where(p => p.Name.Equals(spField.InternalName, StringComparison.InvariantCultureIgnoreCase)).Any()) - { - continue; - } - - // Was this field already defined as a field that will be mapped to a header property? If so it can't be a metadata field - if (extractedHeader != null) - { - if (extractedHeader.Field.Where(p => p.Name.Equals(spField.InternalName, StringComparison.InvariantCultureIgnoreCase)).Any()) - { - continue; - } - } - - // Any field of a type that by default has as target a web part typically is not meant as metadata field for users - if (fieldTypesToSkip.Contains(spField.TypeAsString)) - { - continue; - } - - // Load the default mapping information for this field - var defaultMapping = PublishingDefaults.MetaDataFieldToTargetMappings.FirstOrDefault(o => o.FieldName.Equals(spField.InternalName, StringComparison.InvariantCultureIgnoreCase)); - fields.Add(new MetaDataField() - { - Name = spField.InternalName, - Functions = defaultMapping?.Functions ?? "", - TargetFieldName = defaultMapping?.TargetFieldName ?? "", - }); - } - } - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserErrorOccurredExtractMetadata - .CorrelateString(taskId)); - } - - return fields.ToArray(); - } - - /// - /// Scan through the file to find the TagPrefixes in ASPX Header - /// - /// - /// ID of the transformation task - /// - /// List<Tuple<string, string>> - /// Item1 = tagprefix - /// Item2 = Namespace - /// - internal List> ExtractWebPartPrefixesFromNamespaces(ListItem pageLayout, Guid taskId) - { - var tagPrefixes = new List>(); - - try - { - pageLayout.EnsureProperties(o => o.File, o => o.File.ServerRelativeUrl); - var fileUrl = pageLayout.File.ServerRelativeUrl; - string fileHtml = LoadPageLayoutFile(pageLayout, fileUrl); - - using (var document = this.parser.ParseDocument(fileHtml)) - { - Regex regex = new Regex("<%@(.*?)%>", RegexOptions.IgnoreCase | RegexOptions.Multiline); - var aspxHeader = document.All.Where(o => o.TagName == "HTML").FirstOrDefault(); - var results = regex.Matches(aspxHeader?.InnerHtml); - - StringBuilder blockHtml = new StringBuilder(); - foreach (var match in results) - { - var matchString = match.ToString().Replace("<%@ ", "<").Replace("%>", " />"); - blockHtml.AppendLine(matchString); - } - - var fullBlock = blockHtml.ToString(); - using (var subDocument = this.parser.ParseDocument(fullBlock)) - { - var registers = subDocument.All.Where(o => o.TagName == "REGISTER"); - - foreach (var register in registers) - { - var prefix = register.GetAttribute("Tagprefix"); - var nameSpace = register.GetAttribute("Namespace"); - tagPrefixes.Add(new Tuple(prefix, nameSpace)); - } - - } - - } - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserErrorOccurredExtractNamespaces - .CorrelateString(taskId)); - } - - return tagPrefixes; - } - - /// - /// Extract the web parts from the page layout HTML outside of web part zones - /// - internal ExtractedHtmlBlocksEntity ExtractControlsFromPageLayoutHtml(ListItem pageLayout, Guid taskId) - { - /*Plan - * Scan through the file to find the web parts by the tags - * Extract and convert to definition - */ - - ExtractedHtmlBlocksEntity extractedHtmlBlocks = new ExtractedHtmlBlocksEntity(); - - try - { - // Data from SharePoint - pageLayout.EnsureProperties(o => o.File, o => o.File.ServerRelativeUrl); - var fileUrl = pageLayout.File.ServerRelativeUrl; - var fileHtml = LoadPageLayoutFile(pageLayout, fileUrl); - - // replace cdata tags to 'fool' AngleSharp - fileHtml = fileHtml.Replace(""); - fileHtml = fileHtml.Replace("]]>", ""); - - using (var document = this.parser.ParseDocument(fileHtml)) - { - - // Item 1 - WebPart Name, Item 2 - Full assembly reference - List> possibleWebPartsUsed = new List>(); - List> multipleTagFinds = new List>(); - - //List of all the assembly references and prefixes in the page - List> prefixesAndNameSpaces = ExtractWebPartPrefixesFromNamespaces(pageLayout, taskId); - - // Determine the possible web parts from the page from the namespaces used in the aspx header - prefixesAndNameSpaces.ForEach(p => - { - var possibleParts = WebParts.GetListOfWebParts(p.Item2); - foreach (var part in possibleParts) - { - var webPartName = part.Substring(0, part.IndexOf(",")).Replace($"{p.Item2}.", ""); - possibleWebPartsUsed.Add(new Tuple(webPartName, part)); - } - }); - - // Cycle through all the nodes in the document - foreach (var docNode in document.All) - { - foreach (var prefixAndNameSpace in prefixesAndNameSpaces) - { - if (docNode.TagName.Contains(prefixAndNameSpace.Item1.ToUpper())) - { - - // Expand, as this may contain many elements - //foreach (var control in tagFind) - //{ - - var attributes = docNode.Attributes; - - if (attributes.Any(o => o.Name == "fieldname")) - { - - var fieldName = attributes["fieldname"].Value; - - //DeDup - Some controls can be inside an edit panel - if (!extractedHtmlBlocks.WebPartFields.Any(o => o.Name == fieldName)) - { - List webPartProperties = new List(); - - foreach (var attr in attributes) - { - // This might need a filter - - webPartProperties.Add(new WebPartProperty() - { - Name = attr.Name, - Type = WebPartProperyType.@string, - Functions = "" // Need defaults here - }); - } - - extractedHtmlBlocks.WebPartFields.Add(new WebPartField() - { - Name = fieldName, - TargetWebPart = "", - Row = 1, - //RowSpecified = true, - Column = 1, - //ColumnSpecified = true, - Property = webPartProperties.ToArray() - }); - } - } - - if (docNode.TagName.Contains("WEBPARTZONE")) - { - - extractedHtmlBlocks.WebPartZones.Add(new WebPartZone() - { - ZoneId = docNode.Id, - Column = 1, - Row = 1, - // ZoneIndex = ??? // TODO: Is this used? - }); - } - - //Fixed web part zone - //This should only find one match - var matchedParts = possibleWebPartsUsed.Where(o => o.Item1.ToUpper() == docNode.TagName.Replace($"{prefixAndNameSpace.Item1.ToUpper()}:", "")); - - if (matchedParts.Any()) - { - var match = matchedParts.FirstOrDefault(); - if (match != default(Tuple)) - { - //Process Child properties - List fixedProperties = new List(); - if (docNode.HasChildNodes && docNode.FirstElementChild != null && docNode.FirstElementChild.HasChildNodes) - { - var childProperties = docNode.FirstElementChild.ChildNodes; - foreach (var childProp in childProperties) - { - - if (childProp.NodeName != "#text") - { - var stronglyTypedChild = (IElement)childProp; - //var content = !string.IsNullOrEmpty(childProp.TextContent) ? childProp.TextContent : stronglyTypedChild.InnerHtml; - var content = stronglyTypedChild.InnerHtml; - - fixedProperties.Add(new FixedWebPartProperty() - { - Name = stronglyTypedChild.NodeName, - Type = WebPartProperyType.@string, - Value = EncodingAndCleanUpContent(content) - }); - } - } - } - else - { - // Another scenario where there are no child nodes, just attributes - foreach (var attr in attributes) - { - // This might need a filter - - fixedProperties.Add(new FixedWebPartProperty() - { - Name = attr.Name, - Type = WebPartProperyType.@string, - Value = attr.Value - }); - } - } - - extractedHtmlBlocks.FixedWebParts.Add(new FixedWebPart() - { - Column = 1, - //ColumnSpecified = true, - Row = 1, - //RowSpecified = true, - Type = match.Item2, - Property = fixedProperties.ToArray() - }); - } - } - } - } - } - - } - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserErrorOccurredExtractHtmlBlocks - .CorrelateString(taskId)); - } - - return extractedHtmlBlocks; - } - - /// - /// Cleans and encodes content data - /// - /// web part value - /// - internal string EncodingAndCleanUpContent(string content) - { - if (string.IsNullOrEmpty(content)) - { - return ""; - } - - if (content.Contains("encodeddata>")) - { - // Drop the 'fake' tags again...we'll the XML serializer deal with the encoding work - content = content.Replace("", "").Replace("", ""); - return content; - } - else - { - return System.Web.HttpUtility.HtmlEncode(content); - } - } - - #endregion - - #region Helper methods - private string LoadPageLayoutFile(ListItem pageLayoutItem, string fileUrl) - { - ClientContext siteCollContext = EnsureSiteCollectionContext(pageLayoutItem.Context as ClientContext); - - // Try to get from cache - if (_pageLayoutFileCache.TryGetValue(fileUrl, out string fileContentsFromCache)) - { - return fileContentsFromCache; - } - - // Load from SharePoint - string fileContents = siteCollContext.Web.GetFileByServerRelativeUrlAsString(fileUrl); - - // Store in cache - _pageLayoutFileCache.Add(fileUrl, fileContents); - - return fileContents; - } - - /// - /// Loads the content type fields - /// - /// ListItem linked to the page layout - /// - /// ID of the transformation task - /// - private FieldCollection LoadContentTypeFields(ListItem pageLayoutItem, string contentTypeId, Guid taskId) - { - ClientContext siteCollContext = EnsureSiteCollectionContext(pageLayoutItem.Context as ClientContext); - - try - { - // Try loading from cache first - if (_contentTypeFieldCache.TryGetValue(contentTypeId, out FieldCollection spFieldsFromCache)) - { - return spFieldsFromCache; - } - - var cType = siteCollContext.Web.ContentTypes.GetById(contentTypeId); - var spFields = cType.EnsureProperty(o => o.Fields); - - // Add to cache - _contentTypeFieldCache.Add(contentTypeId, spFields); - - return spFields; - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_CannotMapMetadataFields - .CorrelateString(taskId)); - throw; - } - } - - /// - /// Perform cleanup of web part fields - /// - /// List of extracted web parts - /// Collection of fields - /// ID of the transformation task - /// - private List CleanExtractedWebPartFields(List webPartFields, FieldCollection spFields, Guid taskId) - { - List cleanedWebPartFields = new List(); - - try - { - - foreach (var webPartField in webPartFields) - { - if (PublishingDefaults.IgnoreWebPartFieldControls.Contains(webPartField.Name)) - { - // This is field we're ignoring as it's not meant to be translated into a web part on the modern page - continue; - } - - Guid fieldId = Guid.Empty; - Guid.TryParse(webPartField.Name, out fieldId); - - // Find the field, we'll use the field's type to get the 'default' transformation behaviour - var spField = spFields.Where(p => p.StaticName.Equals(webPartField.Name, StringComparison.InvariantCultureIgnoreCase) || p.Id == fieldId).FirstOrDefault(); - if (spField != null) - { - var webPartFieldDefaults = PublishingDefaults.WebPartFieldProperties.Where(p => p.FieldType.Equals(spField.TypeAsString)); - if (webPartFieldDefaults.Any()) - { - // Copy basic fields - WebPartField wpf = new WebPartField() - { - Name = spField.StaticName, - Row = webPartField.Row, - //RowSpecified = webPartField.RowSpecified, - Column = webPartField.Column, - //ColumnSpecified = webPartField.ColumnSpecified, - TargetWebPart = webPartFieldDefaults.First().TargetWebPart, - }; - - if (fieldId != Guid.Empty) - { - wpf.FieldId = fieldId.ToString(); - } - - // Copy the default target web part properties - var properties = PublishingDefaults.WebPartFieldProperties.Where(p => p.FieldType.Equals(spField.TypeAsString)); - if (properties.Any()) - { - List webPartProperties = new List(); - foreach (var property in properties) - { - webPartProperties.Add(new WebPartProperty() - { - Name = property.Name, - Type = this.CastToEnum(property.Type), - Functions = property.Functions, - }); - } - - wpf.Property = webPartProperties.ToArray(); - } - - cleanedWebPartFields.Add(wpf); - } - } - } - - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserCleaningExtractedWebPartFields - .CorrelateString(taskId)); - } - - return cleanedWebPartFields; - } - - /// - /// Sets the page layout header field defaults - /// - /// - /// ID of the transformation task - private Header ExtractPageHeaderFromPageLayoutAssociatedContentType(FieldCollection spFields, Guid taskId) - { - try - { - // If we've a publishing rollup image then let's try to use that as page header image...at conversion time we'll still switch back to no header in case there - // was no publishing rollup image set at content level - if (spFields.Where(p => p.InternalName.Equals("PublishingRollupImage", StringComparison.InvariantCultureIgnoreCase)).Any()) - { - var pageLayoutHeaderFields = PublishingDefaults.PageLayoutHeaderMetadata.Where(o => o.Type.Equals("FullWidthImage", StringComparison.InvariantCultureIgnoreCase)); - var header = new Header() - { - Type = HeaderType.FullWidthImage, - Alignment = this.CastToEnum(pageLayoutHeaderFields.First().Alignment), - ShowPublishedDate = pageLayoutHeaderFields.First().ShowPublishedDate, - ShowPublishedDateSpecified = true, - }; - - List headerFields = new List(); - foreach (var field in pageLayoutHeaderFields) - { - headerFields.Add(new HeaderField() - { - Name = field.Name, - HeaderProperty = this.CastToEnum(field.HeaderProperty), - Functions = field.Functions - }); - } - - header.Field = headerFields.ToArray(); - - return header; - } - else - { - return null; - } - - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_AnalyserExtractPageHeaderFromPageLayout - .CorrelateString(taskId)); - } - - return null; - } - - /// - /// Ensures the context of the source site collection - /// - /// Connection to SharePoint - /// The Client Context of the root Site Collection - private ClientContext EnsureSiteCollectionContext(ClientContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - try - { - if (context.Web.IsSubSite()) - { - string siteCollectionUrl = context.Site.EnsureProperty(o => o.Url); - - return context.Clone(siteCollectionUrl); - //return new ClientContext(siteCollectionUrl); - } - else - { - return context; - } - } - catch (Exception ex) - { - logger.LogError( - SharePointTransformationResources.Error_CannotGetSiteCollContext, - ex.Message); - throw; - } - } - - - /// - /// Cast a string to enum value - /// - /// Enum Type - /// string value - /// - private T CastToEnum(string enumString) - { - if (!string.IsNullOrEmpty(enumString)) - { - try - { - - return (T)Enum.Parse(typeof(T), enumString, true); - - } - catch (Exception) - { - logger.LogError( - SharePointTransformationResources.Error_CannotCastToEnum); - } - } - - return default(T); - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutHeaderFieldEntity.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutHeaderFieldEntity.cs deleted file mode 100644 index d8fd13fb4e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutHeaderFieldEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Publishing -{ - /// - /// Class for holding data properties for the fields that will be used in the page header - /// - internal class PageLayoutHeaderFieldEntity - { - internal string Type { get; set; } - internal string Name { get; set; } - internal string HeaderProperty { get; set; } - internal string Functions { get; set; } - internal string Alignment { get; set; } - internal bool ShowPublishedDate { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutMetadataEntity.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutMetadataEntity.cs deleted file mode 100644 index fb7d1020db..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutMetadataEntity.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Publishing -{ - /// - /// Class that will be used to hold the fields that will be used the field to metadata mapping - /// - internal class PageLayoutMetadataEntity - { - internal string FieldName { get; set; } - internal string TargetFieldName { get; set; } - internal string Functions { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutWebPartFieldEntity.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutWebPartFieldEntity.cs deleted file mode 100644 index cec84f3681..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PageLayoutWebPartFieldEntity.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Publishing -{ - /// - /// Class for holding data properties for field to web part mapping - /// - internal class PageLayoutWebPartFieldEntity - { - internal string TargetWebPart { get; set; } - internal string FieldType { get; set; } - internal string Name { get; set; } - internal string Type { get; set; } - internal string Functions { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PublishingDefaults.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PublishingDefaults.cs deleted file mode 100644 index f2420b3a5b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Publishing/PublishingDefaults.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Publishing -{ - /// - /// Contains a central point for defaults for publishing page processing - /// - internal static class PublishingDefaults - { - /// - /// Field Control Defaults for mappings - /// - internal static List WebPartFieldProperties = new List() - { - new PageLayoutWebPartFieldEntity() { TargetWebPart = "SharePointPnP.Modernization.WikiImagePart", FieldType = "Image", Name="ImageUrl", Type="string", Functions = "ToImageUrl({PublishingPageImage})" }, - new PageLayoutWebPartFieldEntity() { TargetWebPart = "SharePointPnP.Modernization.WikiImagePart", FieldType = "Image", Name="AlternativeText", Type="string", Functions = "ToImageAltText({PublishingPageImage})" }, - new PageLayoutWebPartFieldEntity() { TargetWebPart = "SharePointPnP.Modernization.WikiImagePart", FieldType = "Image", Name="Anchor", Type="string", Functions = "ToImageAnchor({PublishingPageImage})" }, - new PageLayoutWebPartFieldEntity() { TargetWebPart = "SharePointPnP.Modernization.WikiImagePart", FieldType = "Image", Name="Caption", Type="string", Functions = "ToImageCaption({PublishingImageCaption})" }, - - new PageLayoutWebPartFieldEntity() { TargetWebPart = "Microsoft.SharePoint.Publishing.WebControls.SummaryLinkWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", FieldType = "SummaryLinks", Name = "SummaryLinkStore", Type="string" }, - new PageLayoutWebPartFieldEntity() { TargetWebPart = "Microsoft.SharePoint.Publishing.WebControls.SummaryLinkWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", FieldType = "SummaryLinks", Name = "Title", Type="string", Functions="EmptyString()" }, - - new PageLayoutWebPartFieldEntity() { TargetWebPart = "SharePointPnP.Modernization.WikiTextPart", FieldType = "HTML", Name="Text", Type="string" }, - }; - - /// - /// Metadata field default mappings - /// - internal static List MetaDataFieldToTargetMappings = new List() - { - new PageLayoutMetadataEntity(){ FieldName = "Title", TargetFieldName="", Functions = "" }, - }; - - /// - /// Field to header mappings - /// - internal static List PageLayoutHeaderMetadata = new List() - { - new PageLayoutHeaderFieldEntity() { Type = "FullWidthImage", Alignment="Left", ShowPublishedDate = false, Name = "PublishingRollupImage", HeaderProperty = "ImageServerRelativeUrl", Functions = "ToImageUrl({PublishingRollupImage})" }, - new PageLayoutHeaderFieldEntity() { Type = "FullWidthImage", Alignment="Left", ShowPublishedDate = false, Name="ArticleByLine", HeaderProperty = "TopicHeader", Functions = "" }, - new PageLayoutHeaderFieldEntity() { Type = "FullWidthImage", Alignment="Left", ShowPublishedDate = false, Name="PublishingContact", HeaderProperty = "Authors", Functions = "ToAuthors({PublishingContact})" }, - }; - - /// - /// List of metadata fields in content types to ignore in mappings - /// - internal static List IgnoreMetadataFields = new List() - { - // System fields that will get their defaults via the target modern page - "ContentType", - "FileLeafRef", - "Modified_x0020_By", - "Created_x0020_By", - // Fields that have no functional meaning anymore in modern publishing - "RobotsNoIndex", - "SeoBrowserTitle", - "SeoMetaDescription", - "SeoKeywords", - "PublishingPageLayout", - // We're not (yet) mapping audiences, so skip for now - "Audience", - }; - - /// - /// List of field controls in page to ignore in mappings - /// - internal static List IgnoreWebPartFieldControls = new List() - { - "HeaderStyleDefinitions", - // Should go inside the image web part transformation, excluding for now - "PublishingImageCaption", - // Will be used in the header...so it will be used as preview image in the modern page... - "PublishingRollupImage", - }; - } - -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/Configuration/SharePointTransformationOptions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/Configuration/SharePointTransformationOptions.cs deleted file mode 100644 index bd8611f2a7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/Configuration/SharePointTransformationOptions.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Services.Builder.Configuration -{ - - /// - /// Options used for SharePoint transformations - /// - public class SharePointTransformationOptions - { - /// - /// Defines the path for the Web Part Mapping File - /// - public string WebPartMappingFile { get; set; } - - /// - /// Defines the path for the Page Layout Mapping File - /// - public string PageLayoutMappingFile { get; set; } - - /// - /// Defines whether to include the TitleBarWebPart in the list of web parts to process. Defaults to false. - /// - public bool IncludeTitleBarWebPart { get; set; } - - /// - /// Skip URL rewriting. Defaults to false. - /// - public bool SkipUrlRewrite { get; set; } - - /// - /// Defines whether to remove empty sections and columns on target. Defaults to false. - /// - public bool RemoveEmptySectionsAndColumns { get; set; } - - /// - /// Defines whether to map users or not. Defaults to true. - /// - public bool ShouldMapUsers { get; set; } = true; - - /// - /// If true images and videos embedded in wiki text will be transformed to actual image/video web parts, - /// else they'll get a placeholder and will be added as separate web parts at the end of the page. Defaults to true. - /// - public bool HandleWikiImagesAndVideos { get; set; } = true; - - /// - /// Defines whether to transform hidden web parts or not. Defaults to false. - /// - public bool SkipHiddenWebParts { get; set; } - - /// - /// When an image lives inside a table (or list) then also add it as a separate image web part. Defaults to true. - /// - public bool AddTableListImageAsImageWebPart { get; set; } = true; - - /// - /// Property bag for adding properties that will be exposed to the functions and selectors in the web part mapping file. - /// These properties are used to condition the transformation process. - /// - public Dictionary MappingProperties { get; set; } = new Dictionary - { - // Required for Function Processor - { "UseCommunityScriptEditor", "false" } - }; - - /// - /// Custom URL mappings - /// - public List UrlMappings { get; set; } - - /// - /// Custom URL mappings - /// - public List UserMappings { get; set; } - } - - /// - /// Defines a custom URL mapping item - /// - public class UrlMapping - { - /// - /// Url to be replaced - /// - public string SourceUrl { get; set; } - - /// - /// Url replacement value - /// - public string TargetUrl { get; set; } - } - - /// - /// Defines a custom User mapping item - /// - public class UserMapping - { - /// - /// User to be replaced - /// - public string SourceUser { get; set; } - - /// - /// User replacement value - /// - public string TargetUser { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/IPnPSharePointTransformationBuilder.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/IPnPSharePointTransformationBuilder.cs deleted file mode 100644 index 0335ae89ae..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/IPnPSharePointTransformationBuilder.cs +++ /dev/null @@ -1,20 +0,0 @@ -using PnP.Core.Transformation.Services.Builder; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; - -namespace PnP.Core.Transformation.SharePoint.Services.Builder -{ - /// - /// Used to configure PnP Core Transformation for SharePoint - /// - public interface IPnPSharePointTransformationBuilder : IPnPTransformationBuilder - { - /// - /// Customizes the default transformation options for SharePoint - /// - /// The configuration options - /// The builder object for the current configuration - IPnPSharePointTransformationBuilder WithSharePointOptions(Action options); - - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilder.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilder.cs deleted file mode 100644 index a9b8e84010..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using PnP.Core.Transformation.Services.Builder; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; - -namespace PnP.Core.Transformation.SharePoint.Services.Builder -{ - /// - /// Used to configure PnP Core Transformation for SharePoint - /// - public class PnPSharePointTransformationBuilder : PnPTransformationBuilder, IPnPSharePointTransformationBuilder - { - /// - /// Constructor - /// - /// The services being configured - public PnPSharePointTransformationBuilder(IServiceCollection services) : base(services) - {} - - /// - /// Customizes the default transformation options for SharePoint - /// - /// The configuration options - /// The builder object for the current configuration - public IPnPSharePointTransformationBuilder WithSharePointOptions(Action options) - { - Services.Configure(options); - return this; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilderExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilderExtensions.cs deleted file mode 100644 index c56cc4b7c4..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/Builder/PnPSharePointTransformationBuilderExtensions.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; -using PnP.Core.Transformation.Services.Builder; -using PnP.Core.Transformation.Services.Builder.Configuration; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint; -using PnP.Core.Transformation.SharePoint.Functions; -using PnP.Core.Transformation.SharePoint.MappingProviders; -using PnP.Core.Transformation.SharePoint.Publishing; -using PnP.Core.Transformation.SharePoint.Services; -using PnP.Core.Transformation.SharePoint.Services.Builder; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Extension methods for setting up PnP Transformation services in an . - /// - public static class PnPSharePointTransformationBuilderExtensions - { - /// - /// Configures PnP Transformation for SharePoint with default options - /// - /// The collection of services in an - /// A PnPTransformationBuilder instance - public static IPnPSharePointTransformationBuilder AddPnPSharePointTransformation(this IServiceCollection services) - { - return AddPnPSharePointTransformation(services, null, null); - } - - /// - /// Configures PnP Transformation for SharePoint with default options - /// - /// The collection of services in an - /// An Action to configure the PnP Transformation global options - /// An Action to configure the page transformation options - /// A PnPTransformationBuilder instance - public static IPnPSharePointTransformationBuilder AddPnPSharePointTransformation(this IServiceCollection services, - Action pnpOptions, - Action pageOptions) - { - return services.AddPnPTransformation(pnpOptions, pageOptions) - .WithSharePoint(); - } - - /// - /// Configures PnP Transformation for SharePoint with default options - /// - /// The collection of services in an - /// An Action to configure the PnP Transformation global options - /// An Action to configure the page transformation options - /// An Action to configure the SharePoint transformation options - /// A PnPTransformationBuilder instance - public static IPnPSharePointTransformationBuilder AddPnPSharePointTransformation(this IServiceCollection services, - Action pnpOptions, - Action pageOptions, - Action sharePointOptions) - { - return services.AddPnPTransformation(pnpOptions, pageOptions) - .WithSharePoint(sharePointOptions); - } - - /// - /// Adds default implementations provided by the transformation framework - /// - /// - /// - public static IPnPSharePointTransformationBuilder WithSharePoint(this IPnPTransformationBuilder builder) - { - return WithSharePoint(builder, null); - } - - /// - /// Adds default implementations provided by the transformation framework - /// - /// - /// - /// - public static IPnPSharePointTransformationBuilder WithSharePoint(this IPnPTransformationBuilder builder, Action options) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - if (options != null) - { - builder.Services.Configure(options); - } - - builder.WithMappingProvider() - .WithTransformationDistiller() - .WithTargetPageUriResolver(); - - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - - // Add the custom PageLayoutAnalyser type - builder.Services.TryAddTransient(); - - // Add the SharePoint functions services - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - - // Add the HTML Transformator service - builder.Services.TryAddTransient(); - - // Add the Wiki HTML Transformator service - builder.Services.TryAddTransient(); - - // Add the Publishing Page Transformator service - builder.Services.TryAddTransient(); - - return new PnPSharePointTransformationBuilder(builder.Services); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/HtmlTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/HtmlTransformator.cs deleted file mode 100644 index 5da5215ec7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/HtmlTransformator.cs +++ /dev/null @@ -1,1779 +0,0 @@ -using AngleSharp; -using AngleSharp.Css.Dom; -using AngleSharp.Dom; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using AngleSharp.Io; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PnP.Core.Transformation.SharePoint.Services -{ - /// - /// Class that translates classic SharePoint HTML into html that works on a modern page - /// - public class HtmlTransformator - { - private ILogger logger; - - #region Internal table classes - internal class Table - { - internal string ClassName { get; set; } - internal IElement InsertionPoint { get; set; } - internal IElementCell[] Header { get; set; } - internal IElementCell[,] Cells { get; set; } - - internal bool HasHeader - { - get - { - if (this.Header == null) - { - return false; - } - - for (int colPos = 0; colPos < this.Header.Length; colPos += 1) - { - if (this.Header[colPos] != null) - { - return true; - } - } - - return false; - } - } - } - - internal class IElementCell - { - internal bool HasValue - { - get - { - return Element != null; - } - } - internal IElement Element { get; set; } - - internal IElementCell(IElement element) - { - this.Element = element; - } - - internal string TextAlign { get; set; } - } - #endregion - - private const int DefaultTableWidth = 800; - private HtmlParser parser; - - #region Construction - - /// - /// HtmlTransformator class constructor - /// - public HtmlTransformator(ILogger logger) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - - var config = Configuration.Default.WithDefaultLoader(new LoaderOptions { IsResourceLoadingEnabled = true }).WithCss(); - var context = BrowsingContext.New(config); - parser = new HtmlParser(new HtmlParserOptions { IsEmbedded = true }, context); - } - - #endregion - - /// - /// Transforms the passed html to be usable by the client side text part - /// - /// Html to be transformed - /// Insert placeholders for images and iframe tags - /// Html that can be used and edited via the client side text part - public string Transform(string text, bool usePlaceHolder) - { - // Strip out the "zero width space characters" - text = text.Replace("\u200B", string.Empty); - text = text.Replace("\u200b", string.Empty); - - using (var document = this.parser.ParseDocument(text)) - { - // Process headings: RTE does h2, h3, h4 while wiki does h1, h2, h3, h4. Wiki h4 to h6 will become (formatted) text - TransformHeadings(document, 6, 0); - TransformHeadings(document, 5, 0); - TransformHeadings(document, 4, 0); - TransformHeadings(document, 3, 4); - TransformHeadings(document, 2, 3); - TransformHeadings(document, 1, 2); - - // Process blockquotes - TransformBlockQuotes(document.QuerySelectorAll("blockquote"), document); - - // Process elements that can hold forecolor, backcolor, fontsize, underline and strikethrough information - TransformElements(document.QuerySelectorAll("span"), document); - TransformElements(document.QuerySelectorAll("sup"), document); - TransformElements(document.QuerySelectorAll("sub"), document); - TransformElements(document.QuerySelectorAll("strong"), document); - TransformElements(document.QuerySelectorAll("em"), document); - TransformElements(document.QuerySelectorAll("p"), document); - - - // Process image and iframes ==> put a place holder text as these will be dropped by RTE on edit mode - if (usePlaceHolder) - { - ImageIFramePlaceHolders(document); - } - - // Process tables - TransformTables(document.QuerySelectorAll("table"), document); - - // Finalize the transformation by cleaning the html - // - styling information: RTE does only support a limited set of styles - // - html nodes that are not supported in RTE (clean/replace nodes but avoid dropping relevant content) - CleanStyles(document); - CleanHtmlNodes(document); - - // Return the transformed html - if (document.DocumentElement.Children.Length > 1) - { - string updatedText = document.DocumentElement.Children[1].InnerHtml; - return updatedText; - } - else - { - return text; - } - } - } - - /// - /// Returns true is the passed html is "empty" - /// - /// Html to verify - /// True if considered empty, false otherwise - public bool IsEmptyParagraph(string text) - { - // Remove the "Zero width space" chars - text = text.Replace("\u200B", string.Empty); - text = text.Replace("\u200b", string.Empty); - - // Check for empty text or "Zero width space" - if (string.IsNullOrEmpty(text) || (text.Length == 1 && (text[0] == '\u200B' || text[0] == '\u200b'))) - { - return true; - } - - using (var document = this.parser.ParseDocument(text)) - { - if (document.Body.InnerHtml.ToLower() == "

    " || - document.Body.InnerHtml.ToLower() == "

    ") - { - return true; - } - } - - return false; - } - - /// - /// Cleans the HTML (HR tag becomes BR) - /// - /// Html doc to operate on - protected virtual void CleanHtmlNodes(IHtmlDocument document) - { - // HR tag --> replace by BR - foreach (var element in document.All.Where(p => p.TagName.Equals("hr", StringComparison.InvariantCultureIgnoreCase))) - { - if (element.ParentElement != null) - { - var container = document.CreateElement("span"); - container.AppendChild(document.CreateElement("br")); - container.AppendChild(document.CreateElement("br")); - element.ParentElement.ReplaceChild(container, element); - } - } - } - - /// - /// Cleans up the style attributes - /// - /// Html doc to operate on - protected virtual void CleanStyles(IHtmlDocument document) - { - foreach (var element in document.All.Where(p => p.HasAttribute("style"))) - { - - if (string.IsNullOrEmpty(element.GetAttribute("style"))) - { - // If the style attribute was empty then drop it - element.RemoveAttribute("style"); - } - else - { - if (IsBlockElement(element)) - { - // Save the styles we want to maintain - string marginLeft = element.GetStyle().GetMarginLeft(); - string textAlign = element.GetStyle().GetTextAlign(); - - // Delete all styling information from the element - element.RemoveAttribute("style"); - - // Add the "styles to keep" again - if (!string.IsNullOrEmpty(marginLeft)) - { - element.GetStyle().SetMarginLeft(marginLeft); - } - if (!string.IsNullOrEmpty(textAlign)) - { - element.GetStyle().SetTextAlign(textAlign); - } - } - else - { - // Save the styles we want to maintain - string width = element.GetStyle().GetWidth(); - string textAlign = element.GetStyle().GetTextAlign(); - - // Delete all styling information from the element - element.RemoveAttribute("style"); - - // Add the "styles to keep" again - if (!string.IsNullOrEmpty(width)) - { - element.GetStyle().SetWidth(width); - } - if (!string.IsNullOrEmpty(textAlign)) - { - element.GetStyle().SetTextAlign(textAlign); - } - } - } - } - } - - /// - /// Transforms the html tables into tables that work on SharePoint pages - /// - /// Colletion of tables to process - /// Html doc to operate on - protected virtual void TransformTables(IHtmlCollection tables, IHtmlDocument document) - { - List> tableReplaceList = new List>(); - - foreach (var table in tables) - { - Table normalizedTable = null; - - try - { - // Normalize table by removing the col and row spans and returning a Table object containing matrix of cells, header cells and table information - normalizedTable = NormalizeTable(table); - } - catch (Exception ex) - { - logger.LogWarning( - SharePointTransformationResources.Warning_TableCouldNotBeNormalized, - ex.Message); - } - - // If we could not normalize this table then let's skip it - if (normalizedTable == null) - { - continue; - } - - // Skip empty tables - if (normalizedTable.Cells.GetLength(0) == 0 && normalizedTable.Cells.GetLength(1) == 0) - { - continue; - } - - //
    - var newTableElement = document.CreateElement($"div"); - newTableElement.ClassName = "canvasRteResponsiveTable"; - - //
    - var innerDiv = document.CreateElement("div"); - // Possible alignments: tableLeftAlign, tableCenterAlign and tableRightAlign, since wiki does not have this option default to left align - innerDiv.ClassList.Add(new string[] { "tableLeftAlign", "tableWrapper" }); - newTableElement.AppendChild(innerDiv); - - //
  • - var tableElement = document.CreateElement("table"); - //ms-rteTable-default: basic grid lines - string tableClassName = "borderHeaderTableStyleNeutral"; - if (!string.IsNullOrEmpty(normalizedTable.ClassName)) - { - if (normalizedTable.ClassName.Equals("ms-rteTable-default", StringComparison.InvariantCultureIgnoreCase)) - { - tableClassName = "borderHeaderTableStyleNeutral"; - } - else - { - if (int.TryParse(normalizedTable.ClassName.ToLower().Replace("ms-rtetable-", ""), out int tableStyleCode)) - { - tableClassName = TableStyleCodeToName(tableStyleCode); - } - } - } - - tableElement.ClassName = tableClassName; - tableElement.SetAttribute("title", "Table"); - innerDiv.AppendChild(tableElement); - - // - var tableBody = document.CreateElement("tbody"); - tableElement.AppendChild(tableBody); - - // Table cell width - var cellWidth = GetDefaultCellTableCellWidths(normalizedTable.Cells.GetLength(1)); - - // Table headers - if (normalizedTable.HasHeader) - { - // Header is transformed into a row with bold formatted headers - var newRow = document.CreateElement("tr"); - int headerCounter = 0; - - for (int colPos = 0; colPos < normalizedTable.Header.Length; colPos += 1) - { - var tableHeaderValue = document.CreateElement("strong"); - if (normalizedTable.Header[colPos].HasValue) - { - tableHeaderValue.TextContent = normalizedTable.Header[colPos].Element.TextContent; - } - - var tableHeaderCell = document.CreateElement("td"); - tableHeaderCell.GetStyle().SetWidth($"{cellWidth[headerCounter]}px"); - - // Restore alignment if needed - if (normalizedTable.Header[colPos].TextAlign != null) - { - tableHeaderCell.GetStyle().SetTextAlign(normalizedTable.Header[colPos].TextAlign); - } - - headerCounter++; - - tableHeaderCell.AppendChild(tableHeaderValue); - newRow.AppendChild(tableHeaderCell); - } - - // Append the new header as table row - tableBody.AppendChild(newRow); - } - - // Iterate the table rows - for (int rowPos = 0; rowPos < normalizedTable.Cells.GetLength(0); rowPos += 1) - { - var newRow = document.CreateElement("tr"); - int cellCounter = 0; - - for (int colPos = 0; colPos < normalizedTable.Cells.GetLength(1); colPos += 1) - { - var newTableCell = document.CreateElement("td"); - newTableCell.GetStyle().SetWidth($"{cellWidth[cellCounter]}px"); - cellCounter++; - - // Did we put a "real" cell? - if (normalizedTable.Cells[rowPos, colPos].HasValue) - { - // Copy over the content, take over html content as cell can have formatting inside - // Formatting of the table cell content was already done in previous steps, so we simply copy what we have - newTableCell.InnerHtml = normalizedTable.Cells[rowPos, colPos].Element.InnerHtml; - } - - // Restore alignment if needed - if (normalizedTable.Cells[rowPos, colPos].TextAlign != null) - { - newTableCell.GetStyle().SetTextAlign(normalizedTable.Cells[rowPos, colPos].TextAlign); - } - - newRow.AppendChild(newTableCell); - } - - // Append the new row - tableBody.AppendChild(newRow); - } - - // Add table to list for doing actual replacements once we're done analyzing all tables - tableReplaceList.Add(new Tuple(table, newTableElement, normalizedTable.InsertionPoint)); - } - - foreach (var tableToReplace in tableReplaceList.Where(p => p.Item3 != null)) - { - // Insert the new table at the insertion point. Add a br to work around an RTE bug that you can't separate tables - tableToReplace.Item3.AppendChild(document.CreateElement("br")); - tableToReplace.Item3.AppendChild(tableToReplace.Item2); - - // Remove the old table - tableToReplace.Item1.Parent.RemoveChild(tableToReplace.Item1); - } - - foreach (var tableToReplace in tableReplaceList.Where(p => p.Item3 == null)) - { - // Swap old table with new table - tableToReplace.Item1.Parent.ReplaceChild(tableToReplace.Item2, tableToReplace.Item1); - } - - // Drop any nested table - foreach (var tableToCheck in tableReplaceList) - { - var nestedTables = tableToCheck.Item2.QuerySelectorAll("table"); - if (nestedTables.Any()) - { - foreach (var nestedTable in nestedTables.ToList()) - { - // the new Table element has a DIV as top node, an inner DIV and then the table as child - if (tableToCheck.Item2.FirstElementChild != null && tableToCheck.Item2.FirstElementChild.FirstElementChild != null) - { - if (nestedTable != tableToCheck.Item2.FirstElementChild.FirstElementChild) - { - nestedTable.ParentElement.RemoveChild(nestedTable); - } - } - } - } - } - } - - /// - /// Inserts placeholders for img and iframe tags (images and videos) - /// - /// Html doc to operate on - protected virtual void ImageIFramePlaceHolders(IHtmlDocument document) - { - var images = document.QuerySelectorAll("img"); - var iframes = document.QuerySelectorAll("iframe"); - var elements = images.Union(iframes); - - foreach (var element in elements) - { - // Add a text content in place of the element - string webPartType = string.Empty; - string sourceValue = string.Empty; - var source = element.Attributes.Where(p => p.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (source != null) - { - sourceValue = source.Value; - } - if (element is IHtmlImageElement) - { - webPartType = "Image"; - } - else if (element is IHtmlInlineFrameElement) - { - webPartType = "IFrame"; - } - - string placeHolder = $"***{webPartType} placeholder for source {sourceValue}***"; - - // Create P element and insert it just before our current image or iframe element - var newElement = document.CreateElement($"P"); - newElement.TextContent = placeHolder; - - if (element.Parent != null) - { - element.Parent.InsertBefore(newElement, element); - } - } - } - - /// - /// Transforms blockquotes into compliant html - /// - /// Collection of blockquote blocks to transform - /// Html doc to operate on - protected virtual void TransformBlockQuotes(IHtmlCollection blockQuotes, IHtmlDocument document) - { - int level = 1; - // Dictionary that holds the nodes that will be replaced. Replacement node is a span that groups the elements at the given level. - // We can't immediately replace the nodes as that will break the model where we walk the tree to find the level and top node - Dictionary replacementList = new Dictionary(); - - // Iterate over all blockquote nodes in the html - foreach (var blockQuote in blockQuotes) - { - // Only process block quotes that are used for indentation - if (blockQuote.OuterHtml.ToLower().Contains("margin:0px 0px 0px 40px")) - { - if ((blockQuote.ChildElementCount > 0 && blockQuote.Children[0].TagName.ToLower() == "blockquote")) - { - // do nothing if we still see a blockquote as child element - } - else - { - // We're at the bottom level, the children of this blockquote node do contain regular content. This is always wrapped inside either P or Div tags - bool replacementDone = false; - IElement insertionContainer = null; - IElement topLevelBlockQuote = null; - - // Calculate the level this blockquote is at, it's top level blockquote node, the insertion container for that level (if it exists), whether strike-through was used and whether underline was used - bool strikeThroughWasUsed = false; - bool underLineWasUsed = false; - DetectBlockQuoteLevelParentContainer(replacementList, blockQuote, ref level, ref topLevelBlockQuote, ref insertionContainer, ref strikeThroughWasUsed, ref underLineWasUsed); - - // For the first level we get null as top level blockquote, so assign the current blockquote - if (topLevelBlockQuote == null) - { - topLevelBlockQuote = blockQuote; - } - - // If we found an insertion container then we'll append converted nodes to that container - if (insertionContainer != null) - { - replacementDone = true; - } - - // Important to do a ToList() to get a copy as the Children list might be affected by the code in the loop! - foreach (var nodeToProcess in blockQuote.Children.ToList()) - { - // Indention in realized via P element with the needed pixels as margin-left - IElement newElement; - - // Block elements get their indentation style on the element, others will we wrapped inside a P - bool isBlockElement = IsBlockElement(nodeToProcess); - string innerHtml = ""; - if (isBlockElement) - { - newElement = nodeToProcess; - newElement.GetStyle().SetMarginLeft($"{level * 40}px"); - // Store the block element html for later adding - innerHtml = nodeToProcess.InnerHtml; - // since we're injecting the block element content again we first remove all nodes from it - foreach (var child in newElement.ChildNodes.ToList()) - { - newElement.RemoveChild(child); - } - } - else - { - newElement = document.CreateElement($"p"); - newElement.GetStyle().SetMarginLeft($"{level * 40}px"); - } - - if (strikeThroughWasUsed) - { - // Since strikethrough was used wrap the contents in an s element - var newLineThroughElement = document.CreateElement("s"); - if (isBlockElement) - { - newLineThroughElement.InnerHtml = innerHtml; - } - else - { - newLineThroughElement.InnerHtml = nodeToProcess.OuterHtml; - } - newElement.AppendChild(newLineThroughElement); - } - else if (underLineWasUsed) - { - // Since underline was used wrap the contents in an u element - var newUnderlineElement = document.CreateElement("u"); - if (isBlockElement) - { - newUnderlineElement.InnerHtml = innerHtml; - } - else - { - newUnderlineElement.InnerHtml = nodeToProcess.OuterHtml; - } - newElement.AppendChild(newUnderlineElement); - } - else - { - if (isBlockElement) - { - newElement.InnerHtml = innerHtml; - } - else - { - newElement.InnerHtml = nodeToProcess.OuterHtml; - } - } - - // if no insertion container was created then create it as span and add the new element - if (insertionContainer == null) - { - insertionContainer = document.CreateElement("span"); - insertionContainer.AppendChild(newElement); - } - - if (!replacementDone) - { - // Since we can't simply replace the node (as that would break future blockquote tree traversals) we're storing the "new" node in a list. - // Replacement will happen at the very end of this flow - if (!replacementList.ContainsKey(topLevelBlockQuote)) - { - replacementList.Add(topLevelBlockQuote, insertionContainer); - } - } - else - { - // There's already an insertion container, so we can simply append our element - insertionContainer.AppendChild(newElement); - } - - replacementDone = true; - } - - // reset variables since we're starting a new blockquote - level = 1; - insertionContainer = null; - } - } - } - - // Perform the actual replacements - foreach (var node in replacementList) - { - var parent = node.Key.Parent; - if (parent.Contains(node.Key)) - { - parent.ReplaceChild(node.Value, node.Key); - } - } - } - - /// - /// Drops the BR from the html - /// - /// Html doc to operate on - protected virtual void DropBRs(IHtmlDocument document) - { - var brNodes = document.QuerySelectorAll($"BR"); - - foreach (var brNode in brNodes) - { - var parent = brNode.Parent; - parent.RemoveChild(brNode); - } - } - -#pragma warning disable CA1716 - /// - /// Transforms the used headers into compliant headers - /// - /// Html doc to operate on - /// Source header level - /// Target header level - protected virtual void TransformHeadings(IHtmlDocument document, int from, int to) -#pragma warning restore CA1716 - { - var fromNodes = document.QuerySelectorAll($"h{from}"); - foreach (var fromNode in fromNodes) - { - var parent = fromNode.Parent; - - if (to == 0) - { - // wrap the content inside a div so that further formatting processing will pick it up - var newElement = document.CreateElement("div"); - newElement.InnerHtml = fromNode.InnerHtml; - parent.ReplaceChild(newElement, fromNode); - } - else - { - var newElement = document.CreateElement($"h{to}"); - newElement.InnerHtml = fromNode.InnerHtml; - - // Copy the text alignment style - if (fromNode.GetStyle() != null && !string.IsNullOrEmpty(fromNode.GetStyle().GetTextAlign())) - { - newElement.GetStyle().SetTextAlign(fromNode.GetStyle().GetTextAlign()); - } - parent.ReplaceChild(newElement, fromNode); - } - } - } - - /// - /// Rewrites html styles to be compliant - /// - /// Html elements to process - /// Html doc to operate on - protected virtual void TransformElements(IHtmlCollection elementsToTransform, IHtmlDocument document) - { - foreach (var element in elementsToTransform) - { - var parent = element.Parent; - - // ================================ - // Rewrite font styles outside of the header styles - // ================================ - // Norm - //ms-rteStyle-Normal - //ms-rteStyle-Quote = Italic - //ms-rteStyle-IntenseQuote = Italic + Underline - //ms-rteStyle-Emphasis = italic + light blue - //ms-rteStyle-IntenseEmphasis = italic + light blue + underline - //ms-rteStyle-References = light gray - //ms-rteStyle-IntenseReferences = light gray + underline - //ms-rteStyle-Accent1 = light blue - //ms-rteStyle-Accent2 = dark blue - var rtestylenormal = element.ClassList.PartialMatch("ms-rtestyle-normal"); - if (!string.IsNullOrEmpty(rtestylenormal)) - { - element.ClassList.Remove(rtestylenormal); - } - - // Replace "rte styles" with their wiki style alternatives which then can be picked up by the default processing - var rtestyle = element.ClassList.PartialMatch("ms-rtestyle-"); - bool addItalic = false; - if (!string.IsNullOrEmpty(rtestyle)) - { - // Remove the wiki style - element.ClassList.Remove(rtestyle); - - rtestyle = rtestyle.ToLower().Replace("ms-rtestyle-", ""); - // Underline style - if (rtestyle.Equals("IntenseQuote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseReferences", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element Style which will be picked up later on - element.GetStyle().SetTextDecoration("underline"); - } - - // Light blue - if (rtestyle.Equals("Emphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("Accent1", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element ClassList which will be picked up later on - element.ClassList.Add("ms-rteforecolor-8"); - } - - // Light gray - if (rtestyle.Equals("References", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseReferences", StringComparison.InvariantCultureIgnoreCase)) - { - // no color to add as default is light gray - } - - // Dark blue - if (rtestyle.Equals("Accent2", StringComparison.InvariantCultureIgnoreCase)) - { - // Update current element ClassList which will be picked up later on - element.ClassList.Add("ms-rteforecolor-9"); - } - - // Italic style - if (rtestyle.Equals("Quote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseQuote", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("Emphasis", StringComparison.InvariantCultureIgnoreCase) || - rtestyle.Equals("IntenseEmphasis", StringComparison.InvariantCultureIgnoreCase)) - { - // As we need to insert an EM element we can't do that right now. Let's start with setting an indicator - addItalic = true; - } - } - - // ================================ - // rewrite colors, back and fore color + size can be defined as class on a single span element - // ================================ - // red - var themeForeColor = element.ClassList.PartialMatch("ms-rtethemeforecolor-"); - if (!string.IsNullOrEmpty(themeForeColor)) - { - string newClass = null; - - // Modern Theme colors - // Darker, Dark, Dark Alternate, Primary, Secondary - // Neutral Tertiary, Neutral Secondary, Primary alternate, Neutral primary, Neutral Dark - if (int.TryParse(themeForeColor.ToLower()[themeForeColor.ToLower().Length - 1].ToString(), out int themeCode)) - { - string colorName = ThemeCodeToForegroundColorName(themeCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"fontColor{colorName}"; - } - } - - element.ClassList.Remove(themeForeColor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // red - var rtethemebackcolor = element.ClassList.PartialMatch("ms-rtethemebackcolor-"); - if (!string.IsNullOrEmpty(rtethemebackcolor)) - { - // There are no themed back colors in modern, so for now drop the color span and the background color - element.ClassList.Remove(rtethemebackcolor); - } - - //Red,  - //superscript - var rteforecolor = element.ClassList.PartialMatch("ms-rteforecolor-"); - if (!string.IsNullOrEmpty(rteforecolor)) - { - // Modern Theme colors - // Dark Red, Red, Orange, Yellow, Light green - // Green, Light Blue, Blue, Dark Blue, Purple - - string newClass = null; - if (int.TryParse(rteforecolor.ToLower().Replace("ms-rteforecolor-", ""), out int colorCode)) - { - string colorName = ColorCodeToForegroundColorName(colorCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"fontColor{colorName}"; - } - } - - element.ClassList.Remove(rteforecolor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // lowerscript - var rtebackcolor = element.ClassList.PartialMatch("ms-rtebackcolor-"); - if (!string.IsNullOrEmpty(rtebackcolor)) - { - // Modern Theme colors - // Dark Red, Red, Orange, Yellow, Light green - // Green, Light Blue, Blue, Dark Blue, Purple - - string newClass = null; - if (int.TryParse(rtebackcolor.ToLower().Replace("ms-rtebackcolor-", ""), out int colorCode)) - { - string colorName = ColorCodeToBackgroundColorName(colorCode); - if (!string.IsNullOrEmpty(colorName)) - { - newClass = $"highlightColor{colorName}"; - } - } - - element.ClassList.Remove(rtebackcolor); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // ================================ - // rewrite font size - // ================================ - var rtefontsize = element.ClassList.PartialMatch("ms-rtefontsize-"); - if (!string.IsNullOrEmpty(rtefontsize)) - { - string newClass = null; - if (int.TryParse(rtefontsize.ToLower().Replace("ms-rtefontsize-", ""), out int fontsizeCode)) - { - string fontSize = FontCodeToName(fontsizeCode); - if (!string.IsNullOrEmpty(fontSize)) - { - newClass = $"fontSize{fontSize}"; - } - } - - element.ClassList.Remove(rtefontsize); - if (!string.IsNullOrEmpty(newClass)) - { - // We mapped a color - element.ClassList.Add(newClass); - } - } - - // ================================ - // rewrite fonts - // ================================ - // custom font - var rtefontface = element.ClassList.PartialMatch("ms-rtefontface-"); - if (!string.IsNullOrEmpty(rtefontface)) - { - // There are no themed back colors in modern, so for now drop the color span and the background color - element.ClassList.Remove(rtefontface); - } - - // rewrite striked and underline - // striked - // underline - bool replacementDone = false; - bool isStrikeThroughOnElementToKeep = false; - bool isUnderlineOnElementToKeep = false; - string elementToKeep = string.Empty; - if (IsStrikeThrough(element)) - { - // strike through can be on an element that we're replacing as well (like em)...if so don't - // replace em with strike through now, but wait until at the very end - if (element.TagName.Equals("em", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("strong", StringComparison.InvariantCultureIgnoreCase) || - //element.TagName.Equals("blockquote", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sup", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sub", StringComparison.InvariantCultureIgnoreCase)) - { - elementToKeep = element.TagName; - isStrikeThroughOnElementToKeep = true; - } - else - { - ReplaceByRelevantTag(document, element, parent, "s"); - replacementDone = true; - } - } - else if (IsUnderline(element)) - { - if (element.TagName.Equals("em", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("strong", StringComparison.InvariantCultureIgnoreCase) || - //element.TagName.Equals("blockquote", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sup", StringComparison.InvariantCultureIgnoreCase) || - element.TagName.Equals("sub", StringComparison.InvariantCultureIgnoreCase)) - { - elementToKeep = element.TagName; - isUnderlineOnElementToKeep = true; - } - else - { - // Don't apply the default logic when we need to insert a EM tag...a node can only be replaced once, so postpone the - // replacement by an U element when we also need to insert an EM tag - if (!addItalic) - { - ReplaceByRelevantTag(document, element, parent, "u"); - replacementDone = true; - } - } - } - - // No need to wrap a span into a new span - if (element is IHtmlSpanElement) - { - if (addItalic) - { - // We needed to insert an EM tag as the provided style uses italic - var newElement = document.CreateElement("span"); - newElement.ClassList.Add(element.ClassList.ToArray()); - var newItalic = document.CreateElement("em"); - - // Transform the piece of html we add in this span, needed to handle spans nested in here as these otherwise would not be found anymore - string innerHtml = TransformInnerHtml(element.InnerHtml); - - // If the implemented style also used underline then add the U node as well - if (IsUnderline(element)) - { - var newUnderline = document.CreateElement("u"); - newUnderline.InnerHtml = innerHtml; - newItalic.AppendChild(newUnderline); - } - else - { - newItalic.InnerHtml = innerHtml; - } - - newElement.AppendChild(newItalic); - parent.ReplaceChild(newElement, element); - } - else - { - // if we still did not replace the span element, the span has no classes set anymore and the span does not have child elements then we can replace it by text - if (!replacementDone && element.ClassList.Length == 0 && element.FirstElementChild == null) - { - ReplaceChildElementByText(parent, element, document); - } - else - { - // drop style attribute if still present - if (element.HasAttribute("style")) - { - element.RemoveAttribute("style"); - } - // if the element has no classes anymore then let's drop the class attribute - if (element.ClassList.Length == 0 && element.HasAttribute("class")) - { - element.RemoveAttribute("class"); - } - } - } - } - else - { - // Non span element with styling that was transformed will be wrapped in a span containing the styling which wraps a "clean" element - var newElement = document.CreateElement("span"); - newElement.ClassList.Add(element.ClassList.ToArray()); - // no point in having an empty class attribute - if (element.HasAttribute("class")) - { - element.RemoveAttribute("class"); - } - - if (isStrikeThroughOnElementToKeep) - { - var strikeThroughElement = document.CreateElement("s"); - newElement.AppendChild(strikeThroughElement); - // Create the element that held the strikethrough style - var emElement = document.CreateElement(elementToKeep.ToLower()); - emElement.InnerHtml = element.InnerHtml; - strikeThroughElement.AppendChild(emElement); - } - else if (isUnderlineOnElementToKeep) - { - var underlineElement = document.CreateElement("u"); - newElement.AppendChild(underlineElement); - // Create the element that held the underline style - var emElement = document.CreateElement(elementToKeep.ToLower()); - emElement.InnerHtml = element.InnerHtml; - underlineElement.AppendChild(emElement); - } - else - { - newElement.InnerHtml = element.OuterHtml; - } - - // Only replace the element if it's still available...it could have been replaced earlier on - if (parent.Contains(element)) - { - parent.ReplaceChild(newElement, element); - } - } - } - } - - /// - /// Map wiki table style to a RTE compatible style - /// - /// Code used for the wiki table style - /// RTE compatible table style - public static string TableStyleCodeToName(int tableStyleCode) - { - //ms-rteTable-default: basic grid lines - //ms-rteTable-0: no grid - //ms-rteTable-1: table style 2, light banded: no header, alternating light gray rows, no grid - //ms-rteTable-2: table style 4, light lines: header, no alternating rows, no grid - //ms-rteTable-3: table style 5, grid: no header, no alternating rows, grid - //ms-rteTable-4: table style 6, Accent 1: header, no alternating rows, grid, blue colors - //ms-rteTable-5: table style 7, Accent 2: header, no alternating rows, grid, light blue colors - //ms-rteTable-6: table style 3, medium two tones: header with alternating blue colors, no grid - //ms-rteTable-7: table style 8, Accent 3: header, no alternating rows, grid, green colors - //ms-rteTable-8: table style 9, Accent 4: header, no alternating rows, grid, brownish colors - //ms-rteTable-9: table style 10, Accent 5: header, no alternating rows, grid, red colors - //ms-rteTable-10: table style 11, Accent 6: header, no alternating rows, grid, purple colors - - switch (tableStyleCode) - { - case 0: - case 3: - { - return "simpleTableStyleNeutral"; - } - case 1: - { - return "bandedRowTableStyleNeutral"; - } - case 2: - { - return "filledHeaderTableStyleNeutral"; - } - case 4: - case 5: - case 7: - case 8: - case 9: - case 10: - { - return "filledHeaderTableStyleTheme"; - } - case 6: - { - return "bandedRowTableStyleTheme"; - } - } - - return "borderHeaderTableStyleNeutral"; - } - - /// - /// Translates SharePoint wiki font size (e.g. ms-rtefontsize-3 means font size 3) to RTE font size name - /// - /// Wiki font size code - /// RTE font size name - public static string FontCodeToName(int fontCode) - { - switch (fontCode) - { - case 1: //8pt - { - return "Small"; - } - case 2: //10t - { - return "Medium"; - } - case 3: //12pt - { - return "MediumPlus"; - } - case 4: //18pt - { - // Return empty as this will be mapped to default size - return ""; - } - case 5: //24pt - { - return "XxLarge"; - } - case 6: //36pt - { - return "XxxLarge"; - } - case 7: //48pt - { - return "XxLargePlus"; - } - case 8: //72pt - { - return "Super"; - } - } - - return null; - } - - /// - /// Translated SharePoint Wiki foreground color number (ms-rteforecolor-2 means number 2 is used) to RTE compatible color name - /// - /// Used color number - /// RTE color string - public static string ColorCodeToForegroundColorName(int colorCode) - { - switch (colorCode) - { - case 1: - { - return "RedDark"; - } - case 2: - { - return "Red"; - } - case 3: - { - return "Yellow"; - } - case 4: - { - return "YellowLight"; - } - case 5: - { - return "GreenLight"; - } - case 6: - { - return "Green"; - } - case 7: - { - return "BlueLight"; - } - case 8: - { - return "Blue"; - } - case 9: - { - return "BlueDark"; - } - case 10: - { - return "Purple"; - } - } - - return null; - } - - /// - /// Translated SharePoint Wiki foreground theme color number (e.g. ms-rteThemeForeColor-6-1) to RTE compatible color name - /// - /// Theme color code - /// RTE color string - public static string ThemeCodeToForegroundColorName(int themeCode) - { - switch (themeCode) - { - case 0: - { - // 0 (light) will go to NeutralPrimary which is the default, hence returning null - return null; - } - case 1: - { - return "ThemeSecondary"; - } - case 2: - { - return "ThemePrimary"; - } - case 3: - { - return "ThemeDarkAlt"; - } - case 4: - { - return "ThemeDark"; - } - case 5: - { - return "ThemeDarker"; - } - } - - return null; - } - - /// - /// Translated SharePoint Wiki background color number (ms-rtebackcolor-5 means number 5 is used) to RTE compatible color name - /// - /// Used color number - /// RTE color string - public static string ColorCodeToBackgroundColorName(int colorCode) - { - switch (colorCode) - { - case 1: - { - return "Maroon"; - } - case 2: - { - return "Red"; - } - case 3: - { - return "Yellow"; - } - case 4: - { - return "Yellow"; - } - case 5: - { - return "Green"; - } - case 6: - { - return "Green"; - } - case 7: - { - return "Aqua"; - } - case 8: - { - return "Blue"; - } - case 9: - { - return "DarkBlue"; - } - case 10: - { - return "Purple"; - } - } - - return null; - } - - #region Helper methods - /// - /// Recursively loop the blockquote elements until we're at the top level, returns needed information to process: - /// - Level: how many indents where used - /// - TopLevelBlockQuote: what is the top level blockquote that we'll be using as "replacement node" - /// - If there already was a container created to store content at this level then let's return that one - /// - If by walking the blockquote tree we see strike through being used then indicate that - /// - If by walking the blockquote tree we see underline being used then indicate that - /// - /// - /// - /// - /// - /// - /// - /// - private void DetectBlockQuoteLevelParentContainer(Dictionary replacementList, IElement blockQuote, ref int level, ref IElement topLevelBlockQuote, ref IElement insertionContainer, ref bool strikeThroughWasUsed, ref bool underLineWasUsed) - { - if (IsStrikeThrough(blockQuote)) - { - strikeThroughWasUsed = true; - } - if (IsUnderline(blockQuote)) - { - underLineWasUsed = true; - } - - if (blockQuote.Parent is IElement) - { - if (blockQuote.ParentElement.TagName.ToLower() == "blockquote") - { - topLevelBlockQuote = blockQuote.ParentElement; - level++; - - if (IsStrikeThrough(blockQuote.ParentElement)) - { - strikeThroughWasUsed = true; - } - if (IsUnderline(blockQuote.ParentElement)) - { - underLineWasUsed = true; - } - - DetectBlockQuoteLevelParentContainer(replacementList, blockQuote.ParentElement, ref level, ref topLevelBlockQuote, ref insertionContainer, ref strikeThroughWasUsed, ref underLineWasUsed); - } - else - { - if (insertionContainer == null && topLevelBlockQuote != null && replacementList.ContainsKey(topLevelBlockQuote)) - { - insertionContainer = replacementList[topLevelBlockQuote]; - } - } - } - } - - private string TransformInnerHtml(string innerHtml) - { - // Let's inspect if there's still span's in the html we take over...these spans are not in our current list of spans - // and as such will be ignored if we don't handle them. - using (var documentTemp = this.parser.ParseDocument(innerHtml)) - { - TransformElements(documentTemp.QuerySelectorAll("span"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("sup"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("sub"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("strong"), documentTemp); - //TransformElements(documentTemp.QuerySelectorAll("em"), documentTemp); - - if (documentTemp.DocumentElement.Children.Length > 1) - { - innerHtml = documentTemp.DocumentElement.Children[1].InnerHtml; - } - } - - return innerHtml; - } - - private void ReplaceByRelevantTag(IHtmlDocument document, IElement element, INode parent, string tag) - { - var newElement = document.CreateElement(tag); - - // Transform the piece of html we add in this span, needed to handle spans nested in here as these otherwise would not be found anymore - string innerHtml = TransformInnerHtml(element.InnerHtml); - - // If the element has a class defined then wrap inside a span - if (element.ClassList.Length > 0) - { - var newSpan = document.CreateElement("span"); - newSpan.ClassList.Add(element.ClassList.ToArray()); - newSpan.InnerHtml = innerHtml; - newElement.AppendChild(newSpan); - } - else - { - newElement.InnerHtml = innerHtml; - } - - parent.ReplaceChild(newElement, element); - } - - private static void ReplaceChildElementByText(INode parent, IElement child, IHtmlDocument document) - { - if (!string.IsNullOrEmpty(child.TextContent)) - { - // Add a text content in place of the element - var newElement = document.CreateTextNode(child.TextContent); - parent.ReplaceChild(newElement, child); - } - else - { - // If no content then drop the element - parent.RemoveChild(child); - } - } - - private static bool IsBlockElement(IElement element) - { - var tag = element.TagName.ToLower(); - if (tag == "article" || - tag == "div" || - tag == "p" || - tag == "h1" || - tag == "h2" || - tag == "h3" || - tag == "h3" || - tag == "h4" || - tag == "h5" || - tag == "h6" || - tag == "li" || - tag == "ol" || - tag == "ul" || - tag == "address" || - tag == "aside" || - tag == "canvas" || - tag == "dd" || - tag == "dl" || - tag == "dt" || - tag == "fieldset" || - tag == "figcaption" || - tag == "figure" || - tag == "footer" || - tag == "form" || - tag == "header" || - tag == "hr" || - tag == "main" || - tag == "nav" || - tag == "noscript" || - tag == "output" || - tag == "pre" || - tag == "section" || - tag == "table" || - tag == "tfoot" || - tag == "video" || - tag == "aside" || - tag == "blockquote") - { - return true; - } - - return false; - } - - private static List GetDefaultCellTableCellWidths(int columns) - { - List widths = new List(columns); - - int width = DefaultTableWidth / columns; - int lastWidth = DefaultTableWidth - ((columns - 1) * width); - - for (int i = 0; i < columns; i++) - { - if (i < columns - 1) - { - widths.Add(width); - } - else - { - widths.Add(lastWidth); - } - } - - return widths; - } - - private static bool IsStrikeThrough(IElement element) - { - if (element.GetStyle() != null && (!string.IsNullOrEmpty(element.GetStyle().GetTextDecoration()) && element.GetStyle().GetTextDecoration().Equals("line-through", StringComparison.InvariantCultureIgnoreCase) || - !string.IsNullOrEmpty(element.GetStyle().GetPropertyValue("text-decoration-line")) && element.GetStyle().GetPropertyValue("text-decoration-line").Equals("line-through", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - else - { - return false; - } - } - - private static bool IsUnderline(IElement element) - { - if (element.GetStyle() != null && (!string.IsNullOrEmpty(element.GetStyle().GetTextDecoration()) && element.GetStyle().GetTextDecoration().Equals("underline", StringComparison.InvariantCultureIgnoreCase) || - !string.IsNullOrEmpty(element.GetStyle().GetPropertyValue("text-decoration-line")) && element.GetStyle().GetPropertyValue("text-decoration-line").Equals("underline", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - else - { - return false; - } - } - - - /// - /// Returns the table as a normalized table object. Includes replacing row and col spans by actual empty cells - /// - /// Table to normalize - /// Normalized table - private Table NormalizeTable(IElement table) - { - // Determine the table dimension, item1 = max cols / item 2 = max rows - var tableDimension = GetTableDimensions(table); - - // Create table object - Table normalizedTable = new Table() - { - ClassName = table.ClassName, - }; - - // A 2 dimensional array to hold the normalized table cells - IElementCell[,] cells = new IElementCell[tableDimension.Item2, tableDimension.Item1]; - // An array to hold the header cells (if any) - IElementCell[] headerCells = new IElementCell[tableDimension.Item1]; - - var tableBodyElement = (table as IHtmlTableElement).Bodies[0]; - var rows = tableBodyElement.Children.Where(p => p.TagName.Equals("tr", StringComparison.InvariantCultureIgnoreCase)); - if (rows != null && rows.Any()) - { - int rowPos = 0; - foreach (var row in rows) - { - // We assume a table header only has TH cells - var tableHeaders = row.Children.Where(p => p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - var cellsInTableHeader = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase)); - if (tableHeaders != null && tableHeaders.Any() && !cellsInTableHeader.Any()) - { - int colPos = 0; - foreach (var tableHeader in tableHeaders) - { - headerCells[colPos] = new IElementCell(tableHeader); - - //Check header styles - if (tableHeader.GetStyle() != null && !string.IsNullOrEmpty(tableHeader.GetStyle().GetTextAlign())) - { - headerCells[colPos].TextAlign = tableHeader.GetStyle().GetTextAlign(); - } - - // Prep for next cell - colPos++; - - var colSpan = tableHeader.GetAttribute("colspan"); - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd)) - { - for (int i = 0; i < columnCellsToAdd - 1; i++) - { - headerCells[colPos] = new IElementCell(null); - colPos++; - } - } - } - } - else - { - // Support model of TD cells mixed with TH - var tableCells = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase) || p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - if (tableCells != null && tableCells.Any()) - { - int colPos = 0; - foreach (var tableCell in tableCells) - { - - if (cells[rowPos, colPos] != null) - { - // cells was already filled due to previous row span expansion. Find next free cell on this row - while ((cells[rowPos, colPos] != null) && colPos < tableDimension.Item1) - { - colPos++; - } - } - - cells[rowPos, colPos] = new IElementCell(tableCell); - - //Check header styles - if (tableCell.GetStyle() != null && !string.IsNullOrEmpty(tableCell.GetStyle().GetTextAlign())) - { - cells[rowPos, colPos].TextAlign = tableCell.GetStyle().GetTextAlign(); - } - - // Prep for next cell - colPos++; - - var colSpan = tableCell.GetAttribute("colspan"); - var rowSpan = tableCell.GetAttribute("rowspan"); - - // We do have both col and row span set - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd) && - !string.IsNullOrEmpty(rowSpan) && rowSpan != "1" && int.TryParse(rowSpan, out int rowCellsToAdd)) - { - int tempRowPos = rowPos; - for (int j = 0; j < rowCellsToAdd; j++) - { - int tempColPos = colPos - 1; - for (int i = 0; i < columnCellsToAdd; i++) - { - if (cells[tempRowPos, tempColPos] == null) - { - cells[tempRowPos, tempColPos] = new IElementCell(null); - } - tempColPos++; - } - tempRowPos++; - } - } - else - { - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1" && int.TryParse(colSpan, out int columnCellsToAdd2)) - { - for (int i = 0; i < columnCellsToAdd2 - 1; i++) - { - cells[rowPos, colPos] = new IElementCell(null); - colPos++; - } - } - - if (!string.IsNullOrEmpty(rowSpan) && rowSpan != "1" && int.TryParse(rowSpan, out int rowCellsToAdd2)) - { - int tempRowPos = rowPos + 1; - for (int i = 0; i < rowCellsToAdd2 - 1; i++) - { - cells[tempRowPos, colPos - 1] = new IElementCell(null); - tempRowPos++; - } - } - } - } - - // Only increase row pos for actual rows as header is handled differently - rowPos++; - } - else - { - // Deals with rows formatted without TD or TH tags (e.g. ) - rowPos++; - } - } - } - } - - normalizedTable.Header = headerCells; - normalizedTable.Cells = cells; - - // Let's assume there's badly shaped tables as well...fill the empty cells with an empty cell value so they're correctly replaced - for (int rowPos = 0; rowPos < normalizedTable.Cells.GetLength(0); rowPos += 1) - { - for (int colPos = 0; colPos < normalizedTable.Cells.GetLength(1); colPos += 1) - { - if (normalizedTable.Cells[rowPos, colPos] == null) - { - normalizedTable.Cells[rowPos, colPos] = new IElementCell(null); - } - } - } - - if (normalizedTable.HasHeader) - { - for (int colPos = 0; colPos < normalizedTable.Header.GetLength(0); colPos += 1) - { - if (normalizedTable.Header[colPos] == null) - { - normalizedTable.Header[colPos] = new IElementCell(null); - } - } - } - - // Determine insertion point - if (table.ParentElement != null) - { - var startNode = table.ParentElement; - IElement lastTableElement = null; - - // walk up the parent tree - while (startNode != null) - { - if (startNode.TagName.Equals("table", StringComparison.InvariantCultureIgnoreCase)) - { - lastTableElement = startNode; - } - startNode = startNode.ParentElement; - } - - if (lastTableElement != null) - { - // This table was nested, let's take the parent element of the 'highest' table as insertion point - normalizedTable.InsertionPoint = lastTableElement.ParentElement; - } - - } - - return normalizedTable; - } - - /// - /// Gets the dimensions of a table, excluding the header - /// - /// Table to investigate - /// Tuple containing the columns and rows - private static (int columns, int rows) GetTableDimensions(IElement table) - { - int maxCols = 0; - int maxRows = 0; - bool hasHeader = false; - - var tableBodyElement = (table as IHtmlTableElement).Bodies[0]; - var rows = tableBodyElement.Children.Where(p => p.TagName.Equals("tr", StringComparison.InvariantCultureIgnoreCase)); - if (rows != null && rows.Any()) - { - maxRows = rows.Count(); - foreach (var row in rows) - { - // Do we have a header - var tableHeaders = row.Children.Where(p => p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - - var tdInHeader = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase)); - int tdCellsInHeader = 0; - if (tdInHeader != null) - { - tdCellsInHeader = tdInHeader.Count(); - } - - if (tableHeaders != null && tableHeaders.Any() && tdCellsInHeader == 0) - { - hasHeader = true; - } - - // It's allowed to mix TH and TD in a table row - var tableCells = row.Children.Where(p => p.TagName.Equals("td", StringComparison.InvariantCultureIgnoreCase) || p.TagName.Equals("th", StringComparison.InvariantCultureIgnoreCase)); - if (tableCells != null && tableCells.Any()) - { - int colCount = 0; - foreach (var tableCell in tableCells) - { - var colSpan = tableCell.GetAttribute("colspan"); - if (!string.IsNullOrEmpty(colSpan) && colSpan != "1") - { - if (int.TryParse(colSpan, out int columnCellsToAdd)) - { - colCount = colCount + columnCellsToAdd; - } - else - { - colCount++; - } - } - else - { - colCount++; - } - } - - if (colCount > maxCols) - { - maxCols = colCount; - } - } - } - } - - if (hasHeader) - { - // remove one row as header is handled separately - maxRows--; - } - - // If there's a TR then there must be one cell - if (maxRows > 0 && maxCols == 0) - { - maxCols = 1; - } - - return (maxCols, maxRows); - } - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderInput.cs deleted file mode 100644 index 2fd9832009..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderInput.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; - -namespace PnP.Core.Transformation.SharePoint.Services.MappingProviders -{ - /// - /// Defines the input for a Web Part mapping provider for SharePoint - /// - public class SharePointWebPartMappingProviderInput : WebPartMappingProviderInput - { - private ClientContext sourceContext; - - /// - /// Creates an instance for the specified context and web part - /// - /// The transformation context - /// Context for the source site - public SharePointWebPartMappingProviderInput(PageTransformationContext context, - ClientContext sourceContext) : base(context) - { - this.sourceContext = sourceContext; - } - - /// - /// Defines the SharePoint CSOM client context for the source - /// - public ClientContext SourceContext { get { return this.sourceContext; } } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderOutput.cs deleted file mode 100644 index 2951561df0..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/MappingProviders/SharePointWebPartMappingProviderOutput.cs +++ /dev/null @@ -1,27 +0,0 @@ -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.SharePoint.MappingFiles; - -namespace PnP.Core.Transformation.SharePoint.Services.MappingProviders -{ - /// - /// Defines the output for a Web Part mapping provider for SharePoint - /// - public class SharePointWebPartMappingProviderOutput : WebPartMappingProviderOutput - { - private Mapping mapping; - - /// - /// Creates an instance for the specified context and web part - /// - /// The web part mapping result - public SharePointWebPartMappingProviderOutput(Mapping mapping) - { - this.mapping = mapping; - } - - /// - /// Provides the mapping information for the mapped web part - /// - public Mapping Mapping { get { return this.mapping; } } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/PnPCoreTransformationSharePoint.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/PnPCoreTransformationSharePoint.cs deleted file mode 100644 index dedecf5523..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/PnPCoreTransformationSharePoint.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using PnP.Core.Transformation.Services.Builder.Configuration; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; - -namespace PnP.Core.Transformation.SharePoint.Services -{ - /// - /// Class that implements the inner logic to activate a PnP Transformation, hiding the Dependency Injection logic - /// - public class PnPCoreTransformationSharePoint - { - private ServiceProvider serviceProvider; - - /// - /// Constructor with configuration options - /// - public PnPCoreTransformationSharePoint( - Action pnpOptions, - Action pageOptions, - Action sharePointOptions) - { - InitServices(pnpOptions, pageOptions, sharePointOptions); - } - - private void InitServices( - Action pnpOptions, - Action pageOptions, - Action sharePointOptions) - { - // Build the service collection and load PnP Core SDK - IServiceCollection services = new ServiceCollection(); - - services.AddLogging(builder => { - // builder.AddConsole(); - var b = builder; - }); - - // To increase coverage of solutions providing tokens without graph scopes we turn of graphfirst for PnPContext created from PnP Framework - services = services.AddPnPCore(options => - { - options.PnPContext.GraphFirst = false; - }).Services; - - // Use the default settings for PnP Transformation Framework in SharePoint - services.AddPnPSharePointTransformation( - pnpOptions, pageOptions, sharePointOptions); - - this.serviceProvider = services.BuildServiceProvider(); - } - - /// - /// Provides a PnP Page Transformator instance - /// - /// The Page Transformator instance - public IPageTransformator GetPnPSharePointPageTransformator() - { - // Get the configured page transformator - var pageTransformator = serviceProvider.GetRequiredService(); - return pageTransformator; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/PublishingLayoutTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/PublishingLayoutTransformator.cs deleted file mode 100644 index 2a07e150e2..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/PublishingLayoutTransformator.cs +++ /dev/null @@ -1,497 +0,0 @@ -using Microsoft.Extensions.Logging; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.SharePoint.Model; -using System; -using System.Collections.Generic; -using System.Linq; -using PublishingMapping = PnP.Core.Transformation.SharePoint.MappingFiles.Publishing; - -namespace PnP.Core.Transformation.SharePoint.Services -{ - /// - /// Specific layout transformator for the 'AutoDetect' layout option for publishing pages - /// - internal class PublishingLayoutTransformator - { - private ILogger logger; - private readonly IServiceProvider serviceProvider; - - - public PublishingLayoutTransformator(ILogger logger, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.serviceProvider = serviceProvider; - } - - /// - /// Builds the layout (sections) of the modern page - /// - /// The publishing page layout mapping to the current page - /// Information about the source page - /// Web Parts to process - public List
    Transform(PublishingMapping.PageLayout pageLayout, PageLayout layout, List webPartsToProcess) - { - List GetColumns(int size) - { - return (from n in Enumerable.Range(0, size) select new Column()).ToList(); - } - - var result = new List
    (); - - bool includeVerticalColumn = false; - int verticalColumnEmphasis = 0; - - if (layout == Model.PageLayout.PublishingPage_AutoDetectWithVerticalColumn) - { - includeVerticalColumn = true; - verticalColumnEmphasis = GetVerticalColumnBackgroundEmphasis(pageLayout); - } - - // Should not occur, but to be on the safe side... - if (webPartsToProcess.Count == 0) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Columns = GetColumns(1), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, 1), - }); - - return result; - } - - var firstRow = webPartsToProcess.OrderBy(p => p.Row).First().Row; - var lastRow = webPartsToProcess.OrderBy(p => p.Row).Last().Row; - - // Loop over the possible rows...will take in account possible row gaps - // Each row means a new section - int sectionOrder = 1; - for (int rowIterator = firstRow; rowIterator <= lastRow; rowIterator++) - { - var webpartsInRow = webPartsToProcess.Where(p => p.Row == rowIterator); - if (webpartsInRow.Any()) - { - // Determine max column number - int maxColumns = 1; - - foreach (var wpInRow in webpartsInRow) - { - if (wpInRow.Column > maxColumns) - { - maxColumns = wpInRow.Column; - } - } - - // Deduct the vertical column - if (includeVerticalColumn && rowIterator == firstRow) - { - maxColumns--; - } - - if (maxColumns > 3) - { - this.logger.LogError( - SharePointTransformationResources.Error_Maximum3ColumnsAllowed); - throw new Exception(SharePointTransformationResources.Error_Maximum3ColumnsAllowed); - } - else - { - if (maxColumns == 1) - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(1), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.OneColumn, - Order = sectionOrder, - Columns = GetColumns(1), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - else if (maxColumns == 2) - { - // if we've only an image in one of the columns then make that one the 'small' column - var imageWebPartsInRow = webpartsInRow.Where(p => p.Type == WebParts.WikiImage); - if (imageWebPartsInRow.Any()) - { - Dictionary imageWebPartsPerColumn = new Dictionary(); - foreach (var imageWebPart in imageWebPartsInRow.OrderBy(p => p.Column)) - { - if (imageWebPartsPerColumn.TryGetValue(imageWebPart.Column, out int wpCount)) - { - imageWebPartsPerColumn[imageWebPart.Column] = wpCount + 1; - } - else - { - imageWebPartsPerColumn.Add(imageWebPart.Column, 1); - } - } - - var firstImageColumn = imageWebPartsPerColumn.First(); - var secondImageColumn = imageWebPartsPerColumn.Last(); - - if (firstImageColumn.Key == secondImageColumn.Key) - { - // there was only one column with images - var firstImageColumnOtherWebParts = webpartsInRow.Where(p => p.Column == firstImageColumn.Key && p.Type != WebParts.WikiImage); - if (!firstImageColumnOtherWebParts.Any()) - { - // no other web parts in this column - var orderedList = webpartsInRow.OrderBy(p => p.Column).First(); - - if (orderedList.Column == firstImageColumn.Key) - { - // image left - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnRightVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnRight, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - else - { - // image right - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeftVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeft, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - } - else - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - } - else - { - if (firstImageColumn.Value == 1 || secondImageColumn.Value == 1) - { - // does one of the two columns have anything else besides image web parts - var firstImageColumnOtherWebParts = webpartsInRow.Where(p => p.Column == firstImageColumn.Key && p.Type != WebParts.WikiImage); - var secondImageColumnOtherWebParts = webpartsInRow.Where(p => p.Column == secondImageColumn.Key && p.Type != WebParts.WikiImage); - - if (!firstImageColumnOtherWebParts.Any() && !secondImageColumnOtherWebParts.Any()) - { - // two columns with each only one image... - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - else if (!firstImageColumnOtherWebParts.Any() && secondImageColumnOtherWebParts.Any()) - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnRightVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnRight, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - else if (firstImageColumnOtherWebParts.Any() && !secondImageColumnOtherWebParts.Any()) - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeftVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnLeft, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - else - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - } - else - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - } - } - else - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.TwoColumn, - Order = sectionOrder, - Columns = GetColumns(2), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - } - else if (maxColumns == 3) - { - if (includeVerticalColumn && rowIterator == firstRow) - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumnVerticalSection, - Order = sectionOrder, - Columns = GetColumns(3), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - VerticalSectionZoneEmphasis = verticalColumnEmphasis, - }); - } - else - { - result.Add( - new Section - { - CanvasTemplate = Core.Model.SharePoint.CanvasSectionTemplate.ThreeColumn, - Order = sectionOrder, - Columns = GetColumns(3), - ZoneEmphasis = GetBackgroundEmphasis(pageLayout, rowIterator), - }); - } - } - - sectionOrder++; - } - } - else - { - // non used row...ignore - } - } - - return result; - } - - #region Helper methods - private int GetBackgroundEmphasis(PublishingMapping.PageLayout pageLayout, int row) - { - PublishingMapping.BackgroundEmphasis emphasis = PublishingMapping.BackgroundEmphasis.None; - - if (pageLayout != null) - { - if (pageLayout.SectionEmphasis != null && pageLayout.SectionEmphasis.Section != null) - { - var section = pageLayout.SectionEmphasis.Section.Where(p => p.Row == row).FirstOrDefault(); - if (section != null) - { - return BackgroundEmphasisToInt(section.Emphasis); - } - } - } - - return BackgroundEmphasisToInt(emphasis); - } - - private int GetVerticalColumnBackgroundEmphasis(PublishingMapping.PageLayout pageLayout) - { - PublishingMapping.BackgroundEmphasis emphasis = PublishingMapping.BackgroundEmphasis.None; - - if (pageLayout != null) - { - if (pageLayout.SectionEmphasis != null && pageLayout.SectionEmphasis.VerticalColumnEmphasisSpecified) - { - return BackgroundEmphasisToInt(pageLayout.SectionEmphasis.VerticalColumnEmphasis); - } - } - - return BackgroundEmphasisToInt(emphasis); - } - - private int BackgroundEmphasisToInt(PublishingMapping.BackgroundEmphasis emphasis) - { - switch (emphasis) - { - case PublishingMapping.BackgroundEmphasis.None: return 0; - case PublishingMapping.BackgroundEmphasis.Neutral: return 1; - case PublishingMapping.BackgroundEmphasis.Soft: return 2; - case PublishingMapping.BackgroundEmphasis.Strong: return 3; - } - - return 0; - } - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/TelemetryPropertiesNames.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/TelemetryPropertiesNames.cs deleted file mode 100644 index e4ed49472b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/TelemetryPropertiesNames.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.Services -{ - internal class TelemetryPropertiesNames - { - internal const string PageTransformed = "PageTransformed"; - internal const string EngineVersion = "Version"; - internal const string AADTenantId = "AADTenantId"; - internal const string SourceVersion = "SourceVersion"; - internal const string TargetVersion = "TargetVersion"; - internal const string SourceVersionNumber = "SourceVersionNumber"; - internal const string TargetVersionNumber = "TargetVersionNumber"; - internal const string PageType = "PageType"; - internal const string Duration = "Duration"; - internal const string CrossFarm = "CrossFarm"; - internal const string CrossSite = "CrossSite"; - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Services/WikiHtmlTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Services/WikiHtmlTransformator.cs deleted file mode 100644 index 4d4b8d3cc8..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Services/WikiHtmlTransformator.cs +++ /dev/null @@ -1,519 +0,0 @@ -using AngleSharp.Dom; -using AngleSharp.Html.Dom; -using AngleSharp.Html.Parser; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.SharePoint.Functions; -using PnP.Core.Transformation.SharePoint.Services.Builder.Configuration; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PnP.Core.Transformation.SharePoint.Services -{ - /// - /// Translates wiki text parts in to a list of real and fake web parts - /// - public class WikiHtmlTransformator - { - private ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly SharePointFunctionsService sharePointFunctionsService; - private readonly IOptions options; - - #region Construction - /// - /// Default constructor - /// - public WikiHtmlTransformator(ILogger logger, - IOptions options, - SharePointFunctionsService sharePointFunctionsService, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.options = options ?? throw new ArgumentNullException(nameof(options)); - this.sharePointFunctionsService = sharePointFunctionsService ?? throw new ArgumentNullException(nameof(sharePointFunctionsService)); - this.serviceProvider = serviceProvider; - } - - #endregion - - /// - /// Replaces embedded images and iframes with respective "fake" image and video web parts. Depending on the - /// image/iframe position in the html the wiki text is broken up in multiple wiki text parts intermixed - /// with image and/or video parts. Later on these web parts will be transformed to client side web parts - /// - /// The CSOM Client Context of the source - /// List of web parts on the page - /// Updated list of web parts - public List TransformPlusSplit(ClientContext sourceContext, List wikiPageWebParts) - { - // Instantiate the AngleSharp Html parser - var parser = new HtmlParser(new HtmlParserOptions() { IsEmbedded = true }); - - List updatedWebParts = new List(wikiPageWebParts.Count + 10); - List replacedWebParts = new List(10); - - // Counters used for breaking up wiki text and placing images in between the broken wiki text fragments - int lastRow = 1; - int lastColum = 1; - int extraWebPartCounter = 1; - - // first ensure there's a big gap in the ordering to allow insertion - foreach (var wp in wikiPageWebParts) - { - wp.Order = wp.Order * 1000; - } - - // Counters used for placing web parts at the end of the page (e.g. image was defined inside a table) - int lastRow2 = 1; - int lastColum2 = 1; - int extraWebPartCounter2 = 1; - int splitCounter = 1; - - var lastWebPart2 = wikiPageWebParts.OrderByDescending(p => p.Row).ThenByDescending(p => p.Column).ThenByDescending(p => p.Order).FirstOrDefault(); - if (lastWebPart2 != null) - { - lastRow2 = lastWebPart2.Row; - lastColum2 = lastWebPart2.Column; - extraWebPartCounter2 = lastWebPart2.Order + 100; - } - - // iterate over all parts found on the wiki page - foreach (var wp in wikiPageWebParts) - { - string htmlToParse = ""; - if (wp.Type == WebParts.WikiText) - { - htmlToParse = wp.Properties["Text"]; - } - else if (wp.Type.GetTypeShort() == WebParts.ContentEditor.GetTypeShort()) - { - string fileContents = ""; - if (wp.Properties.ContainsKey("ContentLink") && !string.IsNullOrEmpty(wp.Properties["ContentLink"]) && !wp.Properties["ContentLink"].ToLower().EndsWith(".aspx")) - { - // Load file contents - fileContents = this.sharePointFunctionsService.LoadContentFromFile(wp.Properties["ContentLink"]); - } - - // Run the same selector as we're running from the default mapping file - var selectorResult = this.sharePointFunctionsService.ContentEmbedSelectorContentLink( - (wp.Properties.ContainsKey("ContentLink") ? wp.Properties["ContentLink"] : null), - (wp.Properties.ContainsKey("Content") ? wp.Properties["Content"] : null), - fileContents, - (options.Value.MappingProperties?[SharePointConstants.UseCommunityScriptEditorMappingProperty])); - - if (selectorResult.Equals("NonASPXLinkNoScript", StringComparison.InvariantCultureIgnoreCase) || - selectorResult.Equals("ContentNoScript", StringComparison.InvariantCultureIgnoreCase)) - { - if (!string.IsNullOrEmpty(fileContents)) - { - htmlToParse = fileContents; - } - else - { - htmlToParse = (wp.Properties.ContainsKey("Content") ? wp.Properties["Content"] : null); - } - - // Is there a need to add the web part title as content? - htmlToParse = IncludeWebPartTitle(htmlToParse, wp.Properties); - } - } - - if (!string.IsNullOrEmpty(htmlToParse)) - { - // Reset the replaced web parts list - replacedWebParts = new List(10); - - // Parse the html - using (var document = parser.ParseDocument(htmlToParse)) - { - // Check if this text requires special handling due to embedded images or iframes... - var images = document.QuerySelectorAll("img"); - var iframes = document.QuerySelectorAll("iframe"); - var elementsToHandle = images.Union(iframes); - - // No special handling needed, so skip this wiki text part - if (!elementsToHandle.Any()) - { - if (wp.Type == WebParts.ContentEditor) - { - // Since we've already read (and possible extended) the content let's 'rewrite' the web part properties - if (wp.Properties.ContainsKey("ContentLink")) - { - wp.Properties["ContentLink"] = ""; - } - - if (!wp.Properties.ContainsKey("Content")) - { - wp.Properties.Add("Content", htmlToParse); - } - else - { - wp.Properties["Content"] = htmlToParse; - } - } - - updatedWebParts.Add(wp); - continue; - } - - // Right, we've found a wiki text part with images or iframes... - lastRow = wp.Row; - lastColum = wp.Column; - extraWebPartCounter = wp.Order; - - // Iterate over each each element, need to check each element to ensure we create the - // replacement web parts in the right order - foreach (var element in document.All) - { - Dictionary props = new Dictionary(); - - // Img elements might require splitting of wiki text - if (element is IHtmlImageElement) - { - bool split = true; - bool inUnSplitableElement = InUnSplitableElement(element); - - // Only split if the image was not living in a table or list - if (options.Value.HandleWikiImagesAndVideos && !inUnSplitableElement) - { - // Get the current html tree from this element up and add as text part - props.Add("Title", "Wiki text"); - props.Add("Text", $"SplitPart{splitCounter}"); - splitCounter++; - - replacedWebParts.Add(new WebPartEntity() - { - Type = WebParts.WikiText, - Title = "Wiki text", - Row = lastRow, - Column = lastColum, - Order = extraWebPartCounter, - Properties = props - }); - extraWebPartCounter++; - } - else - { - split = false; - } - - // Check if this image tag is wrapped inside an Anchor - string anchorTag = null; - string captionHtml = ""; - if (element.ParentElement != null && element.ParentElement.TagName.Equals("A", StringComparison.InvariantCultureIgnoreCase)) - { - if (element.ParentElement.HasAttribute("href")) - { - anchorTag = element.ParentElement.GetAttribute("href"); - } - - if (!string.IsNullOrEmpty(element.ParentElement.TextContent)) - { - captionHtml = element.ParentElement.TextContent; - } - } - - // Fill properties of the image web part - props = new Dictionary(); - if ((element as IHtmlImageElement).Source != null) - { - props.Add("Title", "Image in wiki text"); - props.Add("Description", ""); - props.Add("ImageUrl", (element as IHtmlImageElement).Source.Replace("about://", "")); - props.Add("Width", (element as IHtmlImageElement).DisplayWidth.ToString()); - props.Add("Height", (element as IHtmlImageElement).DisplayHeight.ToString()); - props.Add("Anchor", anchorTag ?? ""); - props.Add("Caption", captionHtml ?? ""); - } - - var alt = (element as IElement).Attributes.Where(p => p.Name.Equals("alt", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (alt != null) - { - props.Add("AlternativeText", alt.Value.CleanForJSON()); - } - - if (!inUnSplitableElement || options.Value.AddTableListImageAsImageWebPart) - { - // Add image part - replacedWebParts.Add(new WebPartEntity() - { - Type = WebParts.WikiImage, - Title = "Image in wiki text", - Row = split ? lastRow : lastRow2, - Column = split ? lastColum : lastColum2, - Order = split ? extraWebPartCounter : extraWebPartCounter2, - Properties = props - }); - } - - if (split) - { - // replace img or img nested in A with "splitter" - var splitter = document.CreateElement("span"); - splitter.ClassName = "split"; - - if (element.ParentElement != null) - { - if (element.ParentElement.TagName.Equals("A", StringComparison.InvariantCultureIgnoreCase)) - { - element.ParentElement.ParentElement.ReplaceChild(splitter, element.ParentElement); - } - else - { - element.ParentElement.ReplaceChild(splitter, element); - } - } - extraWebPartCounter++; - } - else - { - extraWebPartCounter2++; - } - } - // IFrame elements might require splitting of wiki text - else if (element is IHtmlInlineFrameElement) - { - bool split = true; - bool inUnSplitableElement = InUnSplitableElement(element); - - // Only split if the iframe was not living in a table or list - if (options.Value.HandleWikiImagesAndVideos && !inUnSplitableElement) - { - // Get the current html tree from this element up and add as text part - props.Add("Title", "Wiki text"); - props.Add("Text", $"SplitPart{splitCounter}"); - splitCounter++; - - replacedWebParts.Add(new WebPartEntity() - { - Type = WebParts.WikiText, - Title = "Wiki text", - Row = lastRow, - Column = lastColum, - Order = extraWebPartCounter, - Properties = props - }); - extraWebPartCounter++; - } - else - { - split = false; - } - - // Fill properties of the video web part - props = new Dictionary(); - if ((element as IHtmlInlineFrameElement).Source != null) - { - props.Add("Title", "Video in wiki text"); - props.Add("Description", ""); - props.Add("IFrameEmbed", (element as IElement).OuterHtml); - props.Add("Source", (element as IHtmlInlineFrameElement).Source); - } - - var allowFullScreen = (element as IElement).Attributes.Where(p => p.Name.Equals("allowfullscreen", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (allowFullScreen != null) - { - bool.TryParse(allowFullScreen.Value, out bool val); - props.Add("AllowFullScreen", val.ToString()); - } - var size = (element as IElement).Attributes.Where(p => p.Name.Equals("width", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (size != null) - { - props.Add("Width", size.Value.ToString()); - } - size = (element as IElement).Attributes.Where(p => p.Name.Equals("height", StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - if (size != null) - { - props.Add("Height", size.Value.ToString()); - } - - // Add video part, in contrast with the image we're always adding the video as a separate web part as the modern text editor strips out embedded videos - replacedWebParts.Add(new WebPartEntity() - { - Type = WebParts.WikiVideo, - Title = "Video in wiki text", - Row = split ? lastRow : lastRow2, - Column = split ? lastColum : lastColum2, - Order = split ? extraWebPartCounter : extraWebPartCounter2, - Properties = props - }); - - if (split) - { - // replace img or img nested in A with "splitter" - var splitter = document.CreateElement("span"); - splitter.ClassName = "split"; - - if (element.ParentElement != null) - { - element.ParentElement.ReplaceChild(splitter, element); - } - extraWebPartCounter++; - } - else - { - extraWebPartCounter2++; - } - } - } - - Dictionary props2 = new Dictionary(); - props2.Add("Title", "Wiki text"); - props2.Add("Text", $"SplitPart{splitCounter}"); - splitCounter++; - - replacedWebParts.Add(new WebPartEntity() - { - Type = WebParts.WikiText, - Title = "Wiki text", - Row = lastRow, - Column = lastColum, - Order = extraWebPartCounter, - Properties = props2 - }); - extraWebPartCounter++; - - // Fix up WikiText parts - // Step 1: get the html now that we've replaced img/iframe tags with a span - string preppedWikiText = ""; - if (document.DocumentElement.Children.Length > 1) - { - preppedWikiText = document.DocumentElement.Children[1].InnerHtml; - } - // Step 2: split the html text in parts based upon the span we added - string[] splitText = preppedWikiText.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); - - // Step 3: use AngleSharp to construct valid html from each part and link it back to the WikiText placeholder web part - foreach (var replacedWebPart in replacedWebParts.ToList()) - { - if (replacedWebPart.Type == WebParts.WikiText) - { - if (Int32.TryParse(replacedWebPart.Properties["Text"].Replace("SplitPart", ""), out int index)) - { - index = index - 1; - - if (splitText.Length >= index + 1) - { - using (var docTemp = parser.ParseDocument(splitText[index])) - { - if (docTemp.DocumentElement.Children.Length > 1) - { - // Remove empty DIV's as that's a net result of the splitting - StripEmptyDivAndPfromHtmlTree(docTemp.DocumentElement.Children[1]); - - string updatedText = docTemp.DocumentElement.Children[1].InnerHtml; - replacedWebPart.Properties["Text"] = updatedText; - } - } - } - else - { - // no text part for this web part, so delete it. This happens when there was no content anymore below the last img/iframe tag - replacedWebParts.Remove(replacedWebPart); - } - } - } - } - - // reset counter for next wiki zone - splitCounter = 1; - - // Insert into updated web parts list - updatedWebParts.AddRange(replacedWebParts); - } - } - else - { - // Not a text editor web part, so we simply retain it - updatedWebParts.Add(wp); - } - } - - // Return the collection of "split" web parts - return updatedWebParts; - } - - #region Helper methods - - private string IncludeWebPartTitle(string htmlContent, Dictionary webPartProperties) - { - if (webPartProperties.ContainsKey("ChromeType") && webPartProperties["ChromeType"] != null) - { - string chromeType = webPartProperties["ChromeType"].ToString(); - - // Add header for all but the chrometype = none and chrometype = border only - if (chromeType != "2" && chromeType != "4") - { - // Only add a header if there a title set - if (webPartProperties.ContainsKey("Title") && webPartProperties["Title"] != null) - { - htmlContent = $"

    {webPartProperties["Title"].ToString()}

    {htmlContent}
    "; - } - } - } - - return htmlContent; - } - - private void StripEmptyDivAndPfromHtmlTree(IElement newWikiTextHtmlFragment) - { - var divs = newWikiTextHtmlFragment.QuerySelectorAll("div"); - var ps = newWikiTextHtmlFragment.QuerySelectorAll("p"); - var list = divs.Union(ps); - - - if (list.Any()) - { - foreach (var el in list) - { - if (!el.HasChildNodes) - { - el.Remove(); - } - } - } - } - - private bool InUnSplitableElement(INode node) - { - IElement start = null; - - if (!(node is IElement)) - { - start = node.ParentElement; - } - else - { - start = node as IElement; - } - - bool unSplitableElementFound = false; - - while (!unSplitableElementFound) - { - if (start.TagName == "TD" || start.TagName == "TR" || start.TagName == "TBODY" || // table - start.TagName == "LI" || start.TagName == "UL" || start.TagName == "OL") // lists - { - return true; - } - else - { - start = start.ParentElement; - } - - if (start == null) - { - return false; - } - } - - return false; - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointConstants.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointConstants.cs deleted file mode 100644 index f6660c27ed..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointConstants.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// SharePoint constants - /// - internal static class SharePointConstants - { - // Client tag and user agent - public const string TransformationClientTag = "SPDev:PageTransformator"; - public const string TransformationUserAgent = ""; - // Web Properties - public const string WebPropertyKeyPagesListId = "__PagesListId"; - - // Schemas - public const string PageTransformationSchema = "http://schemas.dev.office.com/PnP/2018/01/PageTransformationSchema"; - public const string PageLayoutMappingSchema = "http://schemas.dev.office.com/PnP/2019/03/PublishingPageTransformationSchema"; - - // Fields - public const string FileRefField = "FileRef"; - public const string FileDirRefField = "FileDirRef"; - public const string FileLeafRefField = "FileLeafRef"; - public const string FileTitleField = "Title"; - public const string ClientSideApplicationIdField = "ClientSideApplicationId"; - public const string HtmlFileTypeField = "HTML_x0020_File_x0020_Type"; - public const string WikiField = "WikiField"; - public const string ModifiedField = "Modified"; - public const string ModifiedByField = "Editor"; - public const string CreatedField = "Created"; - public const string CreatedByField = "Author"; - public const string ContentTypeIdField = "ContentTypeId"; - public const string PublishingPageLayoutField = "PublishingPageLayout"; - public const string AudienceField = "Audience"; - public const string PublishingRollupImageField = "PublishingRollupImage"; - public const string TitleField = "Title"; - public const string PublishingAssociatedContentTypeField = "PublishingAssociatedContentType"; - public const string SPSitePageFlagsField = "_SPSitePageFlags"; - public const string PromotedStateField = "PromotedState"; - public const string FirstPublishedDateField = "FirstPublishedDate"; - public const string BannerImageUrlField = "BannerImageUrl"; - public const string CanvasContentField = "CanvasContent1"; - public const string IDField = "ID"; - public const string BodyField = "Body"; // Blog pages - public const string PublishedDateField = "PublishedDate"; // Blog pages - public const string FileTypeField = "File_x0020_Type"; // Delve blog pages - - // Content Type ID's - public const string PageLayoutBaseContentTypeId = "0x01010007FF3E057FA8AB4AA42FCB67B453FFC1"; //Page Layout Content Type Id - public const string ModernPageContentTypeId = "0x0101009D1CB255DA76424F860D91F20E6C4118"; - - // Field ID's - public static readonly Guid PostCategory = new Guid("38bea83b-350a-1a6e-f34a-93a6af31338b"); - - // Features - public static readonly Guid FeatureId_Web_ModernPage = new Guid("B6917CB1-93A0-4B97-A84D-7CF49975D4EC"); - - // Mapping properties - public const string UseCommunityScriptEditorMappingProperty = "UseCommunityScriptEditor"; - public const string SummaryLinksToQuickLinksMappingProperty = "SummaryLinksToQuickLinks"; - - // Queries - public const string CAMLQueryByExtension = @" - - - - - - aspx - - - - "; - public const string CAMLQueryByExtensionAndName = @" - - - - - - - aspx - - - - {0} - - - - - "; - public const string CAMLQueryByNameForBlog = @" - - - - - - {0} - - - - "; - - // Cross site assets transfers that are currently allowed - public static string[] AllowedAssetFileExtensions = new string[] { "png", "jpg", "gif", "mp4", "mpeg" }; - // Cross site assets transfer blocked files that are referred to - public static string[] BlockedAssetFileExtensions = new string[] { "aspx" }; - // OOB Publishing Portal page layouts - these layouts are already included in the default mapping file - public static string[] OobPublishingPageLayouts = new string[] { "ArticleLeft", "ArticleLinks", "ArticleRight", "BlankWebPartPage", "ProjectPage", "WelcomeLinks", - "PageFromDocLayout", "WelcomeSplash", "ErrorLayout", "EnterpriseWiki", "CatalogArticle", "CatalogWelcome", - "PageLayoutTemplate", "RedirectPageLayout", "VariationRootPageLayout" }; - - - public const string HeaderTitle = "HeaderTitle"; - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItem.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItem.cs deleted file mode 100644 index 22dbd864ed..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItem.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// SharePoint implementation of - /// - public class SharePointSourceItem : ISourceItem - { - private readonly SharePointSourceItemId sourceItemId; - - /// - /// Creates a new instance based on a uri - /// - /// The URI of the item - /// The source context - public SharePointSourceItem(Uri uri, ClientContext sourceContext) : this(new SharePointSourceItemId(uri), sourceContext) - { - } - - /// - /// Creates a new instance based on an id - /// - /// The ID of the item - /// The source context - public SharePointSourceItem(SharePointSourceItemId sourceItemId, ClientContext sourceContext) - { - SourceContext = sourceContext; - this.sourceItemId = sourceItemId ?? throw new ArgumentNullException(nameof(sourceItemId)); - } - - /// - /// Gets the source context - /// - public ClientContext SourceContext { get; } - - /// - /// Gets the id of the source item - /// - ISourceItemId ISourceItem.Id => sourceItemId; - - /// - /// Gets the id of the source item - /// - public SharePointSourceItemId Id => sourceItemId; - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItemId.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItemId.cs deleted file mode 100644 index b4ca3875b2..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceItemId.cs +++ /dev/null @@ -1,35 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// SharePoint implementation of - /// - public class SharePointSourceItemId : ISourceItemId - { - /// - /// Creates a new instance - /// - /// The URI of the item - public SharePointSourceItemId(Uri uri) - { - Uri = uri ?? throw new ArgumentNullException(nameof(uri)); - } - - /// - /// The URI of the item - /// - public Uri Uri { get; } - - /// - /// Gets the string representation of the item - /// - public string Id => Uri.ToString(); - - /// - /// Gets the string representation of the Server Relative Url of the item - /// - public string ServerRelativeUrl => Uri.AbsolutePath; - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceProvider.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceProvider.cs deleted file mode 100644 index d4bb7588f9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointSourceProvider.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint.Extensions; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// Implementation of supporting SharePoint - /// - public class SharePointSourceProvider : ISourceProvider - { - /// - /// Gets the source context used by the provider - /// - public ClientContext SourceContext { get; } - - /// - /// Creates a new instance for the context - /// - /// The source PnP Context - public SharePointSourceProvider(ClientContext sourceContext) - { - SourceContext = sourceContext ?? throw new ArgumentNullException(nameof(sourceContext)); - } - - /// - /// Gets the id of each available item - /// - /// The cancellation token, if any - /// -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetItemsIdsAsync([EnumeratorCancellation] CancellationToken token = default) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - SourceContext.Web.EnsureProperties(w => w.Url); - for (int x = 0; x < 10; x++) - { - yield return new SharePointSourceItemId(new Uri(new Uri(SourceContext.Web.Url + "/"), $"{x}")); - } - } - - /// - /// Gets an item and its related information based on the id - /// - /// The Id of the item to retrieve - /// The cancellation token, if any - /// The retrieved item - public Task GetItemAsync(ISourceItemId id, CancellationToken token = default) - { - if (!(id is SharePointSourceItemId sid)) - { - throw new ArgumentException($"Only id of type {typeof(SharePointSourceItemId)} is supported"); - } - - ISourceItem result = new SharePointSourceItem(sid, SourceContext); - return Task.FromResult(result); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTargetPageUriResolver.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTargetPageUriResolver.cs deleted file mode 100644 index 9880de9a6f..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTargetPageUriResolver.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.SharePoint.Extensions; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// Implementation of for SharePoint sources - /// - public class SharePointTargetPageUriResolver : ITargetPageUriResolver - { - private readonly PageTransformationOptions defaultPageTransformationOptions; - - /// - /// Default constructor - /// - /// Page transformation options - /// - public SharePointTargetPageUriResolver(IOptions pageTransformationOptions) - { - this.defaultPageTransformationOptions = pageTransformationOptions?.Value ?? throw new ArgumentNullException(nameof(pageTransformationOptions)); - } - - /// - /// Resolves the SharePoint target uri for the specified source item - /// - /// The source item - /// The target context - /// The cancellation token, if any - /// The resolved URI - public Task ResolveAsync(ISourceItem sourceItem, PnPContext targetContext, CancellationToken token = default) - { - if (sourceItem == null) throw new ArgumentNullException(nameof(sourceItem)); - if (targetContext == null) throw new ArgumentNullException(nameof(targetContext)); - if (!(sourceItem is SharePointSourceItem spItem)) - throw new ArgumentException($"Only source item of type {typeof(SharePointSourceItem)} is supported", nameof(sourceItem)); - - var sourceWeb = spItem.SourceContext.Web; - sourceWeb.EnsureProperties(w => w.Url); - - var webUri = new Uri(sourceWeb.Url); - - var itemSiteLocalUri = new StringBuilder(); - // Remove all the segements of the web URL. - foreach (var s in spItem.Id.Uri.Segments.Skip(webUri.Segments.Length).Take(spItem.Id.Uri.Segments.Length - 4)) - { - itemSiteLocalUri.Append(s); - } - - itemSiteLocalUri.Append(this.defaultPageTransformationOptions.TargetPagePrefix); - itemSiteLocalUri.Append(spItem.Id.Uri.Segments[spItem.Id.Uri.Segments.Length - 1]); - - // In case the source is a classi publishing portal - // we also need to replace "pages" with "sitepages" - var uri = new Uri((targetContext.Uri + "/" + - itemSiteLocalUri.ToString()) - .Replace("/pages/", "/sitepages/", StringComparison.InvariantCultureIgnoreCase)); - - return Task.FromResult(uri); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationDistiller.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationDistiller.cs deleted file mode 100644 index 85a0383798..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationDistiller.cs +++ /dev/null @@ -1,48 +0,0 @@ -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// Default implementation of transformation distiller which returns all items from source provider - /// - public class SharePointTransformationDistiller : ITransformationDistiller - { - private readonly ITargetPageUriResolver targetPageUriResolver; - - /// - /// Creates a new instance - /// - /// Instance of an URI resolver to use - public SharePointTransformationDistiller(ITargetPageUriResolver targetPageUriResolver) - { - this.targetPageUriResolver = targetPageUriResolver ?? throw new ArgumentNullException(nameof(targetPageUriResolver)); - } - - /// - /// Defines a list of Page Transformation Tasks - /// - /// The source provider - /// The target context - /// The cancellation token, if any - /// A list of PageTransformationTask to transform - public async IAsyncEnumerable GetPageTransformationTasksAsync( - ISourceProvider sourceProvider, - PnPContext targetContext, - [EnumeratorCancellation] CancellationToken token = default) - { - if (sourceProvider == null) throw new ArgumentNullException(nameof(sourceProvider)); - if (targetContext == null) throw new ArgumentNullException(nameof(targetContext)); - - await foreach (var sourceItemId in sourceProvider.GetItemsIdsAsync(token).WithCancellation(token)) - { - yield return new PageTransformationTask(sourceProvider, sourceItemId, targetContext); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationExecutorExtensions.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationExecutorExtensions.cs deleted file mode 100644 index 96c02b97be..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationExecutorExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Services; -using PnP.Core.Transformation.Services.Core; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.SharePoint -{ - /// - /// Extensions for - /// - public static class SharePointTransformationExecutorExtensions - { - /// - /// Creates a new transformation process for SharePoint - /// - /// The process to use - /// The source context - /// The target context - /// The cancellation token, if any - public static Task StartSharePointProcessAsync( - this ITransformationProcess transformationProcess, - ClientContext sourceContext, - PnPContext targetContext, - CancellationToken token = default) - { - if (transformationProcess == null) throw new ArgumentNullException(nameof(transformationProcess)); - - // Create the provider - var sourceProvider = new SharePointSourceProvider(sourceContext); - - return transformationProcess.StartProcessAsync(sourceProvider, targetContext, token); - } - - /// - /// Creates a new transformation process for SharePoint and waits for its completion - /// - /// The executor to use - /// The source context - /// The target context - /// The cancellation token, if any - /// The status of the transformation process - public static Task TransformSharePointAsync( - this ITransformationExecutor transformationExecutor, - ClientContext sourceContext, - PnPContext targetContext, - CancellationToken token = default) - { - if (transformationExecutor == null) throw new ArgumentNullException(nameof(transformationExecutor)); - - // Create the provider - var sourceProvider = new SharePointSourceProvider(sourceContext); - - return transformationExecutor.TransformAsync(sourceProvider, targetContext, token); - } - - /// - /// Creates a new transformation process for SharePoint - /// - /// The process to use - /// The context factory to use - /// The source context - /// The target site name - /// The cancellation token, if any - /// - public static async Task StartProcessAsync( - this ITransformationProcess transformationProcess, - IPnPContextFactory pnpContextFactory, - ClientContext sourceContext, - string targetName, - CancellationToken token = default) - { - if (transformationProcess == null) throw new ArgumentNullException(nameof(transformationProcess)); - if (pnpContextFactory == null) throw new ArgumentNullException(nameof(pnpContextFactory)); - - // Create context - var targetContext = await pnpContextFactory.CreateAsync(targetName).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - - await transformationProcess.StartSharePointProcessAsync(sourceContext, targetContext, token).ConfigureAwait(false); - } - - /// - /// Creates a new transformation process for SharePoint and waits for its completion - /// - /// The executor to use - /// The context factory to use - /// The source context - /// The target site name - /// The cancellation token, if any - /// - public static async Task TransformSharePointAsync( - this ITransformationExecutor transformationExecutor, - IPnPContextFactory pnpContextFactory, - ClientContext sourceContext, - string targetName, - CancellationToken token = default) - { - if (transformationExecutor == null) throw new ArgumentNullException(nameof(transformationExecutor)); - if (pnpContextFactory == null) throw new ArgumentNullException(nameof(pnpContextFactory)); - - // Create contexts - var targetContext = await pnpContextFactory.CreateAsync(targetName).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - - return await transformationExecutor.TransformSharePointAsync(sourceContext, targetContext, token).ConfigureAwait(false); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.Designer.cs b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.Designer.cs deleted file mode 100644 index b615603ac6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.Designer.cs +++ /dev/null @@ -1,882 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PnP.Core.Transformation.SharePoint { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SharePointTransformationResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal SharePointTransformationResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PnP.Core.Transformation.SharePoint.SharePointTransformationResources", typeof(SharePointTransformationResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Combining mapping data. - /// - internal static string Debug_CombiningMappingData { - get { - return ResourceManager.GetString("Debug_CombiningMappingData", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Processing selector functions. - /// - internal static string Debug_ProcessingSelectorFunctions { - get { - return ResourceManager.GetString("Debug_ProcessingSelectorFunctions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Principal Input: {0}. - /// - internal static string Debug_UserTransformPrincipalInput { - get { - return ResourceManager.GetString("Debug_UserTransformPrincipalInput", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred cleaning extracted web part fields. - /// - internal static string Error_AnalyserCleaningExtractedWebPartFields { - get { - return ResourceManager.GetString("Error_AnalyserCleaningExtractedWebPartFields", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to GetAllPageLayouts - Could not search for page layouts. - /// - internal static string Error_AnalyserCouldNotFindLayouts { - get { - return ResourceManager.GetString("Error_AnalyserCouldNotFindLayouts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred extracting html blocks from page layout html. - /// - internal static string Error_AnalyserErrorOccurredExtractHtmlBlocks { - get { - return ResourceManager.GetString("Error_AnalyserErrorOccurredExtractHtmlBlocks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An issue occurred with extracting metadata from page layout. - /// - internal static string Error_AnalyserErrorOccurredExtractMetadata { - get { - return ResourceManager.GetString("Error_AnalyserErrorOccurredExtractMetadata", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred extracting web part prefixes from namespaces. - /// - internal static string Error_AnalyserErrorOccurredExtractNamespaces { - get { - return ResourceManager.GetString("Error_AnalyserErrorOccurredExtractNamespaces", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred extract page header from page layout associated content type. - /// - internal static string Error_AnalyserExtractPageHeaderFromPageLayout { - get { - return ResourceManager.GetString("Error_AnalyserExtractPageHeaderFromPageLayout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No web parts were found on page '{0}'. - /// - internal static string Error_AnalysingNoWebPartsFound { - get { - return ResourceManager.GetString("Error_AnalysingNoWebPartsFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred processing functions. - /// - internal static string Error_AnErrorOccurredFunctions { - get { - return ResourceManager.GetString("Error_AnErrorOccurredFunctions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Asset was not transferred. Asset: {0}. - /// - internal static string Error_AssetTransferFailedFallback { - get { - return ResourceManager.GetString("Error_AssetTransferFailedFallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Source page '{0}' is a basic ASPX page ... can't currently transform that one, sorry!. - /// - internal static string Error_BasicASPXPageCannotTransform { - get { - return ResourceManager.GetString("Error_BasicASPXPageCannotTransform", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Blog pages cannot be transformed in-place, their target site collection must be a different one.. - /// - internal static string Error_BlogPageTransformationHasToBeCrossSite { - get { - return ResourceManager.GetString("Error_BlogPageTransformationHasToBeCrossSite", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error calling Web Services to Extract Web Parts from page '{0}'. - /// - internal static string Error_CallingWebServicesToExtractWebPartsFromPage { - get { - return ResourceManager.GetString("Error_CallingWebServicesToExtractWebPartsFromPage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred casting value to enum. - /// - internal static string Error_CannotCastToEnum { - get { - return ResourceManager.GetString("Error_CannotCastToEnum", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot get site collection context. - /// - internal static string Error_CannotGetSiteCollContext { - get { - return ResourceManager.GetString("Error_CannotGetSiteCollContext", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot map the metadata fields from the content types. - /// - internal static string Error_CannotMapMetadataFields { - get { - return ResourceManager.GetString("Error_CannotMapMetadataFields", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error mapping page layout - Analyse. - /// - internal static string Error_CannotProcessPageLayoutAnalyse { - get { - return ResourceManager.GetString("Error_CannotProcessPageLayoutAnalyse", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error mapping page layout - Analyse All. - /// - internal static string Error_CannotProcessPageLayoutAnalyseAll { - get { - return ResourceManager.GetString("Error_CannotProcessPageLayoutAnalyseAll", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error writing to mapping file: {0} {1}. - /// - internal static string Error_CannotWriteToXmlFile { - get { - return ResourceManager.GetString("Error_CannotWriteToXmlFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ExecuteQuery threw following exception: {0}.. - /// - internal static string Error_ClientContextExtensions_ExecuteQueryRetryException { - get { - return ResourceManager.GetString("Error_ClientContextExtensions_ExecuteQueryRetryException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Url of the site is required.. - /// - internal static string Error_Clone_Context_Url_Required { - get { - return ResourceManager.GetString("Error_Clone_Context_Url_Required", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CSOM request socket exception. Retry attempt {0}. Sleeping for {1} milliseconds before retrying.. - /// - internal static string Error_CSOMRequestSocketException { - get { - return ResourceManager.GetString("Error_CSOMRequestSocketException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred in DocumentEmbedLookup function: {0}. - /// - internal static string Error_DocumentEmbedLookup { - get { - return ResourceManager.GetString("Error_DocumentEmbedLookup", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred in DocumentEmbedLookup function - file not retrievable - {0}. - /// - internal static string Error_DocumentEmbedLookupFileNotRetrievable { - get { - return ResourceManager.GetString("Error_DocumentEmbedLookupFileNotRetrievable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to ensure user exists: {0}. - /// - internal static string Error_GetPrincipalFailedEnsureUser { - get { - return ResourceManager.GetString("Error_GetPrincipalFailedEnsureUser", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Body contents was set to null, this is an invalid and empty blog page: '{0}'. - /// - internal static string Error_InvalidOrMissingBlogContent { - get { - return ResourceManager.GetString("Error_InvalidOrMissingBlogContent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WikiField contents was set to null, this is an invalid and empty wiki page: '{0}'. - /// - internal static string Error_InvalidOrMissingWikiContent { - get { - return ResourceManager.GetString("Error_InvalidOrMissingWikiContent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid source context for exporting content. - /// - internal static string Error_InvalidSourceContext { - get { - return ResourceManager.GetString("Error_InvalidSourceContext", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid input for the SharePointWebPartMappingProvider. - /// - internal static string Error_InvalidWebPartMappingProviderInput { - get { - return ResourceManager.GetString("Error_InvalidWebPartMappingProviderInput", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred in LoadContentFromFile function: {0}. - /// - internal static string Error_LoadContentFromFile { - get { - return ResourceManager.GetString("Error_LoadContentFromFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred in getting the referenced file in content link: {0}. - /// - internal static string Error_LoadContentFromFileContentLink { - get { - return ResourceManager.GetString("Error_LoadContentFromFileContentLink", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Publishing transformation layout mapping can maximum use 3 columns. - /// - internal static string Error_Maximum3ColumnsAllowed { - get { - return ResourceManager.GetString("Error_Maximum3ColumnsAllowed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The media web part configuration could not be read, skipping this web part from the transformation. - /// - internal static string Error_MediaWebpartConfiguration { - get { - return ResourceManager.GetString("Error_MediaWebpartConfiguration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Media web part configuration did not allow to read it's settings. Did you check if there was a preview image set?. - /// - internal static string Error_MediaWebPartNotProperlyConfigured { - get { - return ResourceManager.GetString("Error_MediaWebPartNotProperlyConfigured", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid empty string argument. - /// - internal static string Error_Message_EmptyString_Arg { - get { - return ResourceManager.GetString("Error_Message_EmptyString_Arg", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The SharePoint input item is missing. - /// - internal static string Error_MissiningSharePointInputItem { - get { - return ResourceManager.GetString("Error_MissiningSharePointInputItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No default mapping was found int the provided mapping file. - /// - internal static string Error_NoDefaultMappingFound { - get { - return ResourceManager.GetString("Error_NoDefaultMappingFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No valid PageLayout transformation model could be retrieved for publishing page layout '{0}' of page '{1}'. - /// - internal static string Error_NoPageLayoutTransformationModel { - get { - return ResourceManager.GetString("Error_NoPageLayoutTransformationModel", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to NotAvailableAtTargetException is used to "skip" a web part since it's not valid for the target site collection (only applies to cross site collection transfers). - /// - internal static string Error_NotValidForTargetSiteCollection { - get { - return ResourceManager.GetString("Error_NotValidForTargetSiteCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Provided custom pagelayout mapping file is invalid: {0}. - /// - internal static string Error_PageLayoutMappingFileSchemaValidation { - get { - return ResourceManager.GetString("Error_PageLayoutMappingFileSchemaValidation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page '{0}' is not valid due to missing FileRef or FileLeafRef value. - /// - internal static string Error_PageNotValidMissingFileRef { - get { - return ResourceManager.GetString("Error_PageNotValidMissingFileRef", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Issue while retrieving the new location for the asset. Asset: {0}. - /// - internal static string Error_ReturnCrossSiteRelativePathFailedFallback { - get { - return ResourceManager.GetString("Error_ReturnCrossSiteRelativePathFailedFallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Source page '{0}' is already a modern page. - /// - internal static string Error_SourcePageIsModern { - get { - return ResourceManager.GetString("Error_SourcePageIsModern", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Source page '{0}' cannot be null. - /// - internal static string Error_SourcePageNotFound { - get { - return ResourceManager.GetString("Error_SourcePageNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unsupported version of SharePoint as the source of transformation. - /// - internal static string Error_UnsupportedSourceVersion { - get { - return ResourceManager.GetString("Error_UnsupportedSourceVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Provided custom web part mapping file is invalid: {0}. - /// - internal static string Error_WebPartMappingSchemaValidation { - get { - return ResourceManager.GetString("Error_WebPartMappingSchemaValidation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to XML definition for web part '{0}' from page '{1}' was not exportable. Exception: {2}.. - /// - internal static string Error_WebPartXmlNotExported { - get { - return ResourceManager.GetString("Error_WebPartXmlNotExported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Added 'Client Side Text Web Part' to target page. - /// - internal static string Info_AddedClientSideTextWebPart { - get { - return ResourceManager.GetString("Info_AddedClientSideTextWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Found {0} page layouts. - /// - internal static string Info_AnalyserFoundItems { - get { - return ResourceManager.GetString("Info_AnalyserFoundItems", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Generating mapping for `{0}` layout. - /// - internal static string Info_AnalyserMappingLayout { - get { - return ResourceManager.GetString("Info_AnalyserMappingLayout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are no page layouts found to analyse. - /// - internal static string Info_AnalyserNoLayoutsFound { - get { - return ResourceManager.GetString("Info_AnalyserNoLayoutsFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No web parts were found on page. - /// - internal static string Info_AnalysingNoWebPartsFound { - get { - return ResourceManager.GetString("Info_AnalysingNoWebPartsFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Calling Web Services to Extract Web Parts from page '{0}'. - /// - internal static string Info_CallingWebServicesToExtractWebPartsFromPage { - get { - return ResourceManager.GetString("Info_CallingWebServicesToExtractWebPartsFromPage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Source page '{0}' contains web part '{1}' of type '{2}'. - /// - internal static string Info_ContentTransformFoundSourceWebParts { - get { - return ResourceManager.GetString("Info_ContentTransformFoundSourceWebParts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using {0} modern web part. - /// - internal static string Info_ContentUsingModernWebPart { - get { - return ResourceManager.GetString("Info_ContentUsingModernWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Web Part '{0}' of type '{1}' is being transformed. - /// - internal static string Info_ContentWebPartBeingTransformed { - get { - return ResourceManager.GetString("Info_ContentWebPartBeingTransformed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Item level permissions read. - /// - internal static string Info_GetItemLevelPermissions { - get { - return ResourceManager.GetString("Info_GetItemLevelPermissions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image file {0} temporary persisted as {1}. - /// - internal static string Info_ImageFilePersisted { - get { - return ResourceManager.GetString("Info_ImageFilePersisted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not transforming Title Bar - this is not used in modern pages. - /// - internal static string Info_NotTransformingTitleBar { - get { - return ResourceManager.GetString("Info_NotTransformingTitleBar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No web parts were found on page '{0}'. - /// - internal static string Info_NoWebPartsFound { - get { - return ResourceManager.GetString("Info_NoWebPartsFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Skipped page layout {0} because it's an OOB page layout. - /// - internal static string Info_OOBPageLayoutSkipped { - get { - return ResourceManager.GetString("Info_OOBPageLayoutSkipped", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page analysis completed for page '{0}'. - /// - internal static string Info_PageAnalysisComplete { - get { - return ResourceManager.GetString("Info_PageAnalysisComplete", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page '{0}' uses '{1}' as page layout, mapping that will be used is {2}. - /// - internal static string Info_PageLayoutMappingBeingUsed { - get { - return ResourceManager.GetString("Info_PageLayoutMappingBeingUsed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page '{0}' uses '{1}' as page layout, no mapping was provided so auto generating a mapping. - /// - internal static string Info_PageLayoutMappingGeneration { - get { - return ResourceManager.GetString("Info_PageLayoutMappingGeneration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page '{0}' is loaded from outside a library. - /// - internal static string Info_PageLivesOutsideOfALibrary { - get { - return ResourceManager.GetString("Info_PageLivesOutsideOfALibrary", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Validation checks completed for page '{0}'. - /// - internal static string Info_PageValidationChecksComplete { - get { - return ResourceManager.GetString("Info_PageValidationChecksComplete", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Processing mapping functions. - /// - internal static string Info_ProcessingMappingFunctions { - get { - return ResourceManager.GetString("Info_ProcessingMappingFunctions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving Web Part '{0}' using Workaround from page '{1}'. - /// - internal static string Info_RetreivingExportWebPartXmlWorkaround { - get { - return ResourceManager.GetString("Info_RetreivingExportWebPartXmlWorkaround", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Transformation of page '{0}' of type '{1}'. - /// - internal static string Info_TransformationMode { - get { - return ResourceManager.GetString("Info_TransformationMode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Setting title of page '{0}' to value: '{1}'. - /// - internal static string Info_TransformPageModernTitle { - get { - return ResourceManager.GetString("Info_TransformPageModernTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Removing empty text web part. - /// - internal static string Info_TransformRemovingEmptyWebPart { - get { - return ResourceManager.GetString("Info_TransformRemovingEmptyWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The current page is used as a home page - settings modern page to 'Home' layout. - /// - internal static string Info_TransformSourcePageIsHomePage { - get { - return ResourceManager.GetString("Info_TransformSourcePageIsHomePage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The current page is not used as the site home page. - /// - internal static string Info_TransformSourcePageIsNotHomePage { - get { - return ResourceManager.GetString("Info_TransformSourcePageIsNotHomePage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Default remapping mechanism for user: {0}. - /// - internal static string Info_UserTransformDefaultMapping { - get { - return ResourceManager.GetString("Info_UserTransformDefaultMapping", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mapping for user cannot be found: {0}. - /// - internal static string Info_UserTransformMappingNotFound { - get { - return ResourceManager.GetString("Info_UserTransformMappingNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mapping done and user replaced: from {0} to {1}. - /// - internal static string Info_UserTransformSuccess { - get { - return ResourceManager.GetString("Info_UserTransformSuccess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using 'custom' modern web part. - /// - internal static string Info_UsingCustomModernWebPart { - get { - return ResourceManager.GetString("Info_UsingCustomModernWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Welcome page setting does exist, checking if the transform page is a home page. - /// - internal static string Info_WelcomePageSettingsIsPresent { - get { - return ResourceManager.GetString("Info_WelcomePageSettingsIsPresent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Xml Mapping saved as {0}. - /// - internal static string Info_XmlMappingSavedAs { - get { - return ResourceManager.GetString("Info_XmlMappingSavedAs", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not retrieve field value from mapping, the contents were empty. - /// - internal static string Warning_CannotRetrieveFieldValue { - get { - return ResourceManager.GetString("Warning_CannotRetrieveFieldValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Web Part Mapping not found. - /// - internal static string Warning_ContentWebPartMappingNotFound { - get { - return ResourceManager.GetString("Warning_ContentWebPartMappingNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Skipping this web part's transformation - cross site not supported. - /// - internal static string Warning_CrossSiteNotSupported { - get { - return ResourceManager.GetString("Warning_CrossSiteNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CSOM request frequency exceeded usage limits. Retry attempt {0}. Sleeping for {1} milliseconds before retrying.. - /// - internal static string Warning_CSOMRequestFrequencyExceeded { - get { - return ResourceManager.GetString("Warning_CSOMRequestFrequencyExceeded", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CSOM request timeout. Retry attempt {0}. Sleeping for {1} milliseconds before retrying.. - /// - internal static string Warning_CSOMRequestTimeout { - get { - return ResourceManager.GetString("Warning_CSOMRequestTimeout", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There is nothing to transform - no web parts found. - /// - internal static string Warning_NothingToTransform { - get { - return ResourceManager.GetString("Warning_NothingToTransform", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page layout could not be determined by the publishing page. - /// - internal static string Warning_PageLayoutsCannotBeDetermined { - get { - return ResourceManager.GetString("Warning_PageLayoutsCannotBeDetermined", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Target web part {0} is not added for field {1} because the field value was empty and the RemoveEmptySectionsAndColumns flag was set. - /// - internal static string Warning_SkippedWebPartDueToEmptyInSourcee { - get { - return ResourceManager.GetString("Warning_SkippedWebPartDueToEmptyInSourcee", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Table could not be normalized and will be taken over as is. - /// - internal static string Warning_TableCouldNotBeNormalized { - get { - return ResourceManager.GetString("Warning_TableCouldNotBeNormalized", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You don't have needed the needed permission level (ManagePermissions) to read item level permissions. - /// - internal static string Warning_TransformGetItemPermissionsAccessDenied { - get { - return ResourceManager.GetString("Warning_TransformGetItemPermissionsAccessDenied", resourceCulture); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.resx b/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.resx deleted file mode 100644 index d5adf9814b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/SharePointTransformationResources.resx +++ /dev/null @@ -1,393 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Combining mapping data - - - Processing selector functions - - - Principal Input: {0} - - - An error occurred cleaning extracted web part fields - - - GetAllPageLayouts - Could not search for page layouts - - - An error occurred extracting html blocks from page layout html - - - An issue occurred with extracting metadata from page layout - - - An error occurred extracting web part prefixes from namespaces - - - An error occurred extract page header from page layout associated content type - - - No web parts were found on page '{0}' - - - An error occurred processing functions - - - Asset was not transferred. Asset: {0} - - - Source page '{0}' is a basic ASPX page ... can't currently transform that one, sorry! - - - Blog pages cannot be transformed in-place, their target site collection must be a different one. - - - Error calling Web Services to Extract Web Parts from page '{0}' - - - An error occurred casting value to enum - - - Cannot get site collection context - - - Cannot map the metadata fields from the content types - - - Error mapping page layout - Analyse - - - Error mapping page layout - Analyse All - - - Error writing to mapping file: {0} {1} - - - ExecuteQuery threw following exception: {0}. - - - Url of the site is required. - - - CSOM request socket exception. Retry attempt {0}. Sleeping for {1} milliseconds before retrying. - - - An error occurred in DocumentEmbedLookup function: {0} - - - An error occurred in DocumentEmbedLookup function - file not retrievable - {0} - - - Failed to ensure user exists: {0} - - - Body contents was set to null, this is an invalid and empty blog page: '{0}' - - - WikiField contents was set to null, this is an invalid and empty wiki page: '{0}' - - - Invalid source context for exporting content - - - Invalid input for the SharePointWebPartMappingProvider - - - An error occurred in LoadContentFromFile function: {0} - - - An error occurred in getting the referenced file in content link: {0} - - - Publishing transformation layout mapping can maximum use 3 columns - - - The media web part configuration could not be read, skipping this web part from the transformation - - - Media web part configuration did not allow to read it's settings. Did you check if there was a preview image set? - - - Invalid empty string argument - - - The SharePoint input item is missing - - - No default mapping was found int the provided mapping file - - - No valid PageLayout transformation model could be retrieved for publishing page layout '{0}' of page '{1}' - - - NotAvailableAtTargetException is used to "skip" a web part since it's not valid for the target site collection (only applies to cross site collection transfers) - - - Provided custom pagelayout mapping file is invalid: {0} - - - Page '{0}' is not valid due to missing FileRef or FileLeafRef value - - - Issue while retrieving the new location for the asset. Asset: {0} - - - Source page '{0}' is already a modern page - - - Source page '{0}' cannot be null - - - Unsupported version of SharePoint as the source of transformation - - - Provided custom web part mapping file is invalid: {0} - - - XML definition for web part '{0}' from page '{1}' was not exportable. Exception: {2}. - - - Added 'Client Side Text Web Part' to target page - - - Found {0} page layouts - - - Generating mapping for `{0}` layout - - - There are no page layouts found to analyse - - - No web parts were found on page - - - Calling Web Services to Extract Web Parts from page '{0}' - - - Source page '{0}' contains web part '{1}' of type '{2}' - - - Using {0} modern web part - - - Web Part '{0}' of type '{1}' is being transformed - - - Item level permissions read - - - Image file {0} temporary persisted as {1} - - - Not transforming Title Bar - this is not used in modern pages - - - No web parts were found on page '{0}' - - - Skipped page layout {0} because it's an OOB page layout - - - Page analysis completed for page '{0}' - - - Page '{0}' uses '{1}' as page layout, mapping that will be used is {2} - - - Page '{0}' uses '{1}' as page layout, no mapping was provided so auto generating a mapping - - - Page '{0}' is loaded from outside a library - - - Validation checks completed for page '{0}' - - - Processing mapping functions - - - Retrieving Web Part '{0}' using Workaround from page '{1}' - - - Transformation of page '{0}' of type '{1}' - - - Setting title of page '{0}' to value: '{1}' - - - Removing empty text web part - - - The current page is used as a home page - settings modern page to 'Home' layout - - - The current page is not used as the site home page - - - Default remapping mechanism for user: {0} - - - Mapping for user cannot be found: {0} - - - Mapping done and user replaced: from {0} to {1} - - - Using 'custom' modern web part - - - Welcome page setting does exist, checking if the transform page is a home page - - - Xml Mapping saved as {0} - - - Could not retrieve field value from mapping, the contents were empty - - - Web Part Mapping not found - - - Skipping this web part's transformation - cross site not supported - - - CSOM request frequency exceeded usage limits. Retry attempt {0}. Sleeping for {1} milliseconds before retrying. - - - CSOM request timeout. Retry attempt {0}. Sleeping for {1} milliseconds before retrying. - - - There is nothing to transform - no web parts found - - - Page layout could not be determined by the publishing page - - - Target web part {0} is not added for field {1} because the field value was empty and the RemoveEmptySectionsAndColumns flag was set - - - Table could not be normalized and will be taken over as is - - - You don't have needed the needed permission level (ManagePermissions) to read item level permissions - - \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/BuiltInFieldId.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/BuiltInFieldId.cs deleted file mode 100644 index cb1947c4a9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/BuiltInFieldId.cs +++ /dev/null @@ -1,1618 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - /// - /// Defines a list of IDs for built in fields - /// - internal static class BuiltInFieldIds - { - /// - /// Returns a GUID that represents the content type identifier of the specified Windows SharePoint Services object. - /// - public static readonly Guid ContentTypeId = new Guid("{03e45e84-1992-4d42-9116-26f756012634}"); - /// - /// Returns a GUID that represents the content type of the specified Windows SharePoint Services object. - /// - public static readonly Guid ContentType = new Guid("{c042a256-787d-4a6f-8a8a-cf6ab767f12d}"); - public static readonly Guid ID = new Guid("{1d22ea11-1e32-424e-89ab-9fedbadb6ce1}"); - /// - /// Returns a GUID that represents the last modified date and time information that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid Modified = new Guid("{28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f}"); - /// - /// Returns a GUID that represents the date and time when the specified Windows SharePoint Services object was created. - /// - public static readonly Guid Created = new Guid("{8c06beca-0777-48f7-91c7-6da68bc07b69}"); - /// - /// Returns a GUID that represents the specified author of the Windows SharePoint Services object. - /// - public static readonly Guid Author = new Guid("{1df5e554-ec7e-46a6-901d-d85a3881cb18}"); - /// - /// Returns a GUID that is used to represent the editor name or information that is associated with a person who is referenced by a Windows SharePoint Services contact object. - /// - public static readonly Guid Editor = new Guid("{d31655d1-1d5b-4511-95a1-7a09e9b75bf2}"); - /// - /// Returns a GUID that represents the internal version of a rowset, and is used when the rowset includes multiple versions of the same Windows SharePoint Services list item object. - /// - public static readonly Guid owshiddenversion = new Guid("{d4e44a66-ee3a-4d02-88c9-4ec5ff3f4cd5}"); - public static readonly Guid Subject = new Guid("{76a81629-44d4-4ce1-8d4d-6d7ebcd885fc}"); - /// - /// Returns a GUID that represents the author of the specified Windows SharePoint Services object. - /// - public static readonly Guid _Author = new Guid("{246d0907-637c-46b7-9aa0-0bb914daa832}"); - /// - /// Returns a string that represents the category of the specified Windows SharePoint Services object. - /// - public static readonly Guid _Category = new Guid("{0fc9cace-c5c2-465d-ae88-b67f2964ca93}"); - public static readonly Guid _Status = new Guid("{1dab9b48-2d1a-47b3-878c-8e84f0d211ba}"); - /// - /// Returns a GUID that represents information about the server-relative URL for the specified Windows SharePoint Services object. - /// - public static readonly Guid FileRef = new Guid("{94f89715-e097-4e8b-ba79-ea02aa8b7adb}"); - /// - /// Returns a GUID that represents information about the file directory for the specified Windows SharePoint Services object. - /// - public static readonly Guid FileDirRef = new Guid("{56605df6-8fa1-47e4-a04c-5b384d59609f}"); - /// - /// Returns a GUID that represents version control information for the last modified version of the specified Windows SharePoint Services list object. - /// - public static readonly Guid Last_x0020_Modified = new Guid("{173f76c8-aebd-446a-9bc9-769a2bd2c18f}"); - /// - /// Returns a GUID that indicates the date that is associated with the creation of the specified Windows SharePoint Services object. - /// - public static readonly Guid Created_x0020_Date = new Guid("{998b5cff-4a35-47a7-92f3-3914aa6aa4a2}"); - /// - /// Returns a GUID that represents information about the file size for the version history of the specified Windows SharePoint Services list object. - /// - public static readonly Guid File_x0020_Size = new Guid("{8fca95c0-9b7d-456f-8dae-b41ee2728b85}"); - /// - /// Returns a GUID that represents information about the file system type for the specified Windows SharePoint Services object. - /// - public static readonly Guid FSObjType = new Guid("{30bb605f-5bae-48fe-b4e3-1f81d9772af9}"); - public static readonly Guid PermMask = new Guid("{ba3c27ee-4791-4867-8821-ff99000bac98}"); - /// - /// Returns a GUID that represents the designated user who has checked out the Windows SharePoint Services object by using version control. - /// - public static readonly Guid CheckoutUser = new Guid("{3881510a-4e4a-4ee8-b102-8ee8e2d0dd4b}"); - /// - /// Returns a GUID that represents the virus scan status of a specified Windows SharePoint Services object. - /// - public static readonly Guid VirusStatus = new Guid("{4a389cb9-54dd-4287-a71a-90ff362028bc}"); - /// - /// Returns a GUID that represents the associated instance identifier for the history of the specified Windows SharePoint Services list object that was used under version control. - /// - public static readonly Guid InstanceID = new Guid("{50a54da4-1528-4e67-954a-e2d24f1e9efb}"); - /// - /// Returns a GUID that represents the check-in comments of the specified Windows SharePoint Services object. - /// - public static readonly Guid _CheckinComment = new Guid("{58014f77-5463-437b-ab67-eec79532da67}"); - public static readonly Guid MetaInfo = new Guid("{687c7f94-686a-42d3-9b67-2782eac4b4f8}"); - /// - /// Returns a GUID that represents the version control alert level information of the specified Windows SharePoint Services object. - /// - public static readonly Guid _Level = new Guid("{43bdd51b-3c5b-4e78-90a8-fb2087f71e70}"); - /// - /// Returns a GUID that represents the latest version information of the check-in history of the specified Windows SharePoint Services object. - /// - public static readonly Guid _IsCurrentVersion = new Guid("{c101c3e7-122d-4d4d-bc34-58e94a38c816}"); - /// - /// Returns a GUID that represents the destination information of the specified Windows SharePoint Services object. - /// - public static readonly Guid _HasCopyDestinations = new Guid("{26d0756c-986a-48a7-af35-bf18ab85ff4a}"); - /// - /// Returns a GUID that represents the CopySource property of the specified Windows SharePoint Services object. - /// - public static readonly Guid _CopySource = new Guid("{6b4e226d-3d88-4a36-808d-a129bf52bccf}"); - /// - /// Returns a GUID that represents the information about the moderation status of the specified Windows SharePoint Services object. - /// - public static readonly Guid _ModerationStatus = new Guid("{fdc3b2ed-5bf2-4835-a4bc-b885f3396a61}"); - /// - /// Returns a GUID that represents information about the moderation comments of the specified Windows SharePoint Services weblog object. - /// - public static readonly Guid _ModerationComments = new Guid("{34ad21eb-75bd-4544-8c73-0e08330291fe}"); - /// - /// Returns a GUID that represents information about the occupational title of a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Title = new Guid("{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"); - public static readonly Guid WorkflowVersion = new Guid("{f1e020bc-ba26-443f-bf2f-b68715017bbc}"); - /// - /// Returns a GUID that represents the attachments that are associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid Attachments = new Guid("{67df98f4-9dec-48ff-a553-29bece9c5bf4}"); - /// - /// Returns a GUID that indicates the editing state icon that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid Edit = new Guid("{503f1caa-358e-4918-9094-4a2cdc4bc034}"); - public static readonly Guid LinkTitleNoMenu = new Guid("{bc91a437-52e7-49e1-8c4e-4698904b2b6d}"); - public static readonly Guid LinkTitle = new Guid("{82642ec8-ef9b-478f-acf9-31f7d45fbc31}"); - public static readonly Guid SelectTitle = new Guid("{b1f7969b-ea65-42e1-8b54-b588292635f2}"); - public static readonly Guid Order = new Guid("{ca4addac-796f-4b23-b093-d2a3f65c0774}"); - /// - /// Returns a GUID that is used to return the GUID of the specified Windows SharePoint Services object. - /// -#pragma warning disable CA1720 - public static readonly Guid GUID = new Guid("{ae069f25-3ac2-4256-b9c3-15dbc15da0e0}"); -#pragma warning restore CA1720 - /// - /// Returns a GUID that represents the workflow instance identifier that is specified in a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowInstanceID = new Guid("{de8beacf-5505-47cd-80a6-aa44e7ffe2f4}"); - public static readonly Guid UniqueId = new Guid("{4b7403de-8d94-43e8-9f0f-137a3e298126}"); - public static readonly Guid ProgId = new Guid("{c5c4b81c-f1d9-4b43-a6a2-090df32ebb68}"); - /// - /// Returns a GUID that represents information about the server-relative URL for the file node that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid FileLeafRef = new Guid("{8553196d-ec8d-4564-9861-3dbe931050c8}"); - public static readonly Guid ScopeId = new Guid("{dddd2420-b270-4735-93b5-92b713d0944d}"); - /// - /// Returns a GUID that represents information about the fully qualified e-mail sender for the specified Windows SharePoint Services list object. - /// - public static readonly Guid EmailSender = new Guid("{4ce600fb-a927-4911-bfc1-11076b76b522}"); - /// - /// Returns a GUID that represents the "sent to" information for the specified Windows SharePoint Services list object. - /// - public static readonly Guid EmailTo = new Guid("{caa2cb1e-a124-4068-9496-14feef1a901f}"); - /// - /// Returns a GUID that is used to represent information about the e-mail carbon copy recipient for the specified SharePoint list object. - /// - public static readonly Guid EmailCc = new Guid("{a6af6df4-feb5-4dbf-bef6-d81230d4a071}"); - /// - /// Returns a GUID that is used to represent information about the e-mail display name for the specified Windows SharePoint Services list object. - /// - public static readonly Guid EmailFrom = new Guid("{e7cb6f60-f676-4b1d-89a3-975b6bc78cad}"); - /// - /// Returns a GUID that represents the subject information for the specified Windows SharePoint Services list object. - /// - public static readonly Guid EmailSubject = new Guid("{072e9bb6-a643-44ce-b6fb-8b574a792556}"); - /// - /// Returns a GUID that is used to represent the update identifier that is associated with the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid EmailCalendarUid = new Guid("{f4e00567-8a9d-451b-82d4-a4447f9bd9a5}"); - /// - /// Returns a GUID that is used to represent the sequence modification number that is associated with the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid EmailCalendarSequence = new Guid("{7a0cb12b-c70c-4f99-99f1-a232783a87d7}"); - /// - /// Returns a GUID that is used to represent the date stamp information that is associated with the specified Windows SharePoint Services calendar object. - /// - public static readonly Guid EmailCalendarDateStamp = new Guid("{32f182ba-284e-4a87-93c3-936a6585af39}"); - /// - /// Returns a GUID that represents the version number of the user interface of the specified Windows SharePoint Services weblog object. - /// - public static readonly Guid _UIVersion = new Guid("{7841bf41-43d0-4434-9f50-a673baef7631}"); - /// - /// Returns a GUID that represents the version string that is associated with the user interface of the specified Windows SharePoint Services weblog object. - /// - public static readonly Guid _UIVersionString = new Guid("{dce8262a-3ae9-45aa-aab4-83bd75fb738a}"); - public static readonly Guid Modified_x0020_By = new Guid("{822c78e3-1ea9-4943-b449-57863ad33ca9}"); - /// - /// Returns a GUID that indicates the user who is associated with the creation of the specified Windows SharePoint Services object. - /// - public static readonly Guid Created_x0020_By = new Guid("{4dd7e525-8d6b-4cb4-9d3e-44ee25f973eb}"); - /// - /// Returns a GUID that represents file type information that is associated with the version history for the specified Windows SharePoint Services list object. - /// - public static readonly Guid File_x0020_Type = new Guid("{39360f11-34cf-4356-9945-25c44e68dade}"); - public static readonly Guid HTML_x0020_File_x0020_Type = new Guid("{0c5e0085-eb30-494b-9cdd-ece1d3c649a2}"); - /// - /// Returns a GUID that represents the source URL of the specified Windows SharePoint Services weblog object. - /// - public static readonly Guid _SourceUrl = new Guid("{c63a459d-54ba-4ab7-933a-dcf1c6fadec2}"); - /// - /// Returns a GUID that represents information about the shared file index of the specified Windows SharePoint Services object. - /// - public static readonly Guid _SharedFileIndex = new Guid("{034998e9-bf1c-4288-bbbd-00eacfc64410}"); - /// - /// Returns a GUID that represents the icon that is used to create a link to a file in a document library, where the file can be edited without using a menu. - /// - public static readonly Guid LinkFilenameNoMenu = new Guid("{9d30f126-ba48-446b-b8f9-83745f322ebe}"); - /// - /// Returns a GUID that represents the EditMenuTableEnd property of the specified Windows SharePoint Services object. - /// - public static readonly Guid _EditMenuTableStart = new Guid("{3c6303be-e21f-4366-80d7-d6d0a3b22c7a}"); - /// - /// Returns a GUID that represents the EditMenuTableEnd property of the specified Windows SharePoint Services object. - /// - public static readonly Guid _EditMenuTableEnd = new Guid("{2ea78cef-1bf9-4019-960a-02c41636cb47}"); - /// - /// Returns a GUID that represents the icon that is used to create a link to a file in a document library, where the file can be edited by using a menu. - /// - public static readonly Guid LinkFilename = new Guid("{5cc6dc79-3710-4374-b433-61cb4a686c12}"); - public static readonly Guid SelectFilename = new Guid("{5f47e085-2150-41dc-b661-442f3027f552}"); - /// - /// Returns a GUID that specifies the document icon that is associated with the creation of the specified Windows SharePoint Services template document object. - /// - public static readonly Guid DocIcon = new Guid("{081c6e4c-5c14-4f20-b23e-1a71ceb6a67c}"); - public static readonly Guid ServerUrl = new Guid("{105f76ce-724a-4bba-aece-f81f2fce58f5}"); - /// - /// Returns a GUID that represents the encoded search URL for the specified Windows SharePoint Services object. - /// - public static readonly Guid EncodedAbsUrl = new Guid("{7177cfc7-f399-4d4d-905d-37dd51bc90bf}"); - /// - /// Returns a GUID that represents the base name of the specified Windows SharePoint Services object that does not include URL information. - /// - public static readonly Guid BaseName = new Guid("{7615464b-559e-4302-b8e2-8f440b913101}"); - /// - /// Returns a GUID that represents information about the properly formatted file size for version history of the specified Windows SharePoint Services list object. - /// - public static readonly Guid FileSizeDisplay = new Guid("{78a07ba4-bda8-4357-9e0f-580d64487583}"); - /// - /// Returns a GUID that represents the body of the specified Windows SharePoint Services message object. - /// - public static readonly Guid Body = new Guid("{7662cd2c-f069-4dba-9e35-082cf976e170}"); - /// - /// Returns a GUID that represents expiration date for the specified Windows SharePoint Services announcement object. - /// - public static readonly Guid Expires = new Guid("{6a09e75b-8d17-4698-94a8-371eda1af1ac}"); - /// - /// Returns a GUID that represents the URL of a Windows SharePoint Services link object. - /// - public static readonly Guid URL = new Guid("{c29e077d-f466-4d8e-8bbe-72b66c5f205c}"); - /// - /// Returns a GUID that represents comments that are associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid _Comments = new Guid("{52578fc3-1f01-4f4d-b016-94ccbcf428cf}"); - public static readonly Guid _EndDate = new Guid("{8a121252-85a9-443d-8217-a1b57020fadf}"); - /// - /// Returns a GUID that represents the end date for the specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid EndDate = new Guid("{2684f9f2-54be-429f-ba06-76754fc056bf}"); - /// - /// Returns a GUID that represents the associated URL for the specified Windows SharePoint Services link object. - /// - public static readonly Guid URLwMenu = new Guid("{2a9ab6d3-268a-4c1c-9897-e5f018f87e64}"); - public static readonly Guid URLNoMenu = new Guid("{aeaf07ee-d2fb-448b-a7a3-cf7e062d6c2a}"); - /// - /// Returns a GUID that represents information about the phonetics (speech sounds) that are associated with the last name of the specified Windows SharePoint Services contact object. - /// - public static readonly Guid LastNamePhonetic = new Guid("{fdc8216d-dabf-441d-8ac0-f6c626fbdc24}"); - /// - /// Returns a GUID that represents the first name for the specified Windows SharePoint Services contact object. - /// - public static readonly Guid FirstName = new Guid("{4a722dd4-d406-4356-93f9-2550b8f50dd0}"); - /// - /// Returns a GUID that represents the phonetic information (speech sounds) that are associated with the specified first name for the Windows SharePoint Services contact object. - /// - public static readonly Guid FirstNamePhonetic = new Guid("{ea8f7ca9-2a0e-4a89-b8bf-c51a6af62c73}"); - /// - /// Returns a GUID that represents information about the full name of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid FullName = new Guid("{475c2610-c157-4b91-9e2d-6855031b3538}"); - /// - /// Returns a GUID that represents pronunciation information for the person or company that is specified in a Windows SharePoint Services contact object. - /// - public static readonly Guid CompanyPhonetic = new Guid("{034aae88-6e9a-4e41-bc8a-09b6c15fcdf4}"); - /// - /// Returns a GUID that represents the company information for the person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid Company = new Guid("{038d1503-4629-40f6-adaf-b47d1ab2d4fe}"); - /// - /// Returns a GUID that represents the job title of the person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid JobTitle = new Guid("{c4e0f350-52cc-4ede-904c-dd71a3d11f7d}"); - /// - /// Returns a GUID that represents the corporate telephone number for the person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid WorkPhone = new Guid("{fd630629-c165-4513-b43c-fdb16b86a14d}"); - /// - /// Returns a GUID that represents the home telephone number for the person specified in a Windows SharePoint Services contact object. - /// - public static readonly Guid HomePhone = new Guid("{2ab923eb-9880-4b47-9965-ebf93ae15487}"); - /// - /// Returns a GUID that represents the cell phone number of the person who is specified in a Windows SharePoint Services contact object. - /// - public static readonly Guid CellPhone = new Guid("{2a464df1-44c1-4851-949d-fcd270f0ccf2}"); - /// - /// Returns a GUID that represents the corporate fax information for the person specified in a Windows SharePoint Services contact object. - /// - public static readonly Guid WorkFax = new Guid("{9d1cacc8-f452-4bc1-a751-050595ad96e1}"); - /// - /// Returns a GUID that represents the work address of the person who is referenced in the Windows SharePoint Services contact object. - /// - public static readonly Guid WorkAddress = new Guid("{fc2e188e-ba91-48c9-9dd3-16431afddd50}"); - public static readonly Guid _Photo = new Guid("{1020c8a0-837a-4f1b-baa1-e35aff6da169}"); - /// - /// Returns a GUID that represents the work city for the person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid WorkCity = new Guid("{6ca7bd7f-b490-402e-af1b-2813cf087b1e}"); - /// - /// Returns a GUID that represents the regional corporate information for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid WorkState = new Guid("{ceac61d3-dda9-468b-b276-f4a6bb93f14f}"); - /// - /// Returns a GUID that represents the work ZIP code for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid WorkZip = new Guid("{9a631556-3dac-49db-8d2f-fb033b0fdc24}"); - /// - /// Returns a GUID that represents the corporate country information for a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid WorkCountry = new Guid("{3f3a5c85-9d5a-4663-b925-8b68a678ea3a}"); - /// - /// Returns a GUID that represents the Web page that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid WebPage = new Guid("{a71affd2-dcc7-4529-81bc-2fe593154a5f}"); - /// - /// Returns a GUID that represents the priority information that is associated with a Windows SharePoint Services workflow task object. - /// - public static readonly Guid Priority = new Guid("{a8eb573e-9e11-481a-a8c9-1104a54b2fbd}"); - /// - /// Returns a GUID that represents information about the enumerated completion status for a specified Windows SharePoint Services task object. - /// - public static readonly Guid TaskStatus = new Guid("{c15b34c3-ce7d-490a-b133-3f4de8801b76}"); - /// - /// Returns a GUID that represents information about what percent of a specified Windows SharePoint Services workflow object is completed. - /// - public static readonly Guid PercentComplete = new Guid("{d2311440-1ed6-46ea-b46d-daa643dc3886}"); - /// - /// Returns a GUID that indicates the user to whom the specified Windows SharePoint Services workflow task object is assigned. - /// - public static readonly Guid AssignedTo = new Guid("{53101f38-dd2e-458c-b245-0c236cc13d1a}"); - /// - /// Returns a GUID that represents information about the task group for a specified Windows SharePoint Services task object. - /// - public static readonly Guid TaskGroup = new Guid("{50d8f08c-8e99-4948-97bf-2be41fa34a0d}"); - /// - /// Returns a GUID that represents information about the start date of a task that is associated with the specified Windows SharePoint Services task object. - /// - public static readonly Guid StartDate = new Guid("{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}"); - /// - /// Returns a GUID that represents information about the due date for a specified Windows SharePoint Services task object. - /// - public static readonly Guid TaskDueDate = new Guid("{cd21b4c2-6841-4f9e-a23a-738a65f99889}"); - /// - /// Returns a GUID that represents the workflow URL that is specified in a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowLink = new Guid("{58ddda52-c2a3-4650-9178-3bbc1f6e36da}"); - /// - /// Returns a GUID that represents information about an off-site participant user object that is associated with the specified Windows SharePoint Services workflow object. - /// - public static readonly Guid OffsiteParticipant = new Guid("{16b6952f-3ce6-45e0-8f4e-42dac6e12441}"); - /// - /// Returns a GUID that represents information about why an offsite participant is offsite. This information, in turn, is associated with the specified Windows SharePoint Services workflow object. - /// - public static readonly Guid OffsiteParticipantReason = new Guid("{4a799ba5-f449-4796-b43e-aa5186c3c414}"); - /// - /// Returns a GUID that represents the type of outcome (for example, "Approved" or "Rejected") that is associated with a specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowOutcome = new Guid("{18e1c6fa-ae37-4102-890a-cfb0974ef494}"); - /// - /// Returns a GUID that represents the workflow name that is specified in a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowName = new Guid("{e506d6ca-c2da-4164-b858-306f1c41c9ec}"); - /// - /// Returns a GUID that represents information about the task type of a specified Windows SharePoint Services task object. - /// - public static readonly Guid TaskType = new Guid("{8d96aa48-9dff-46cf-8538-84c747ffa877}"); - public static readonly Guid FormURN = new Guid("{17ca3a22-fdfe-46eb-99b5-9646baed3f16}"); - public static readonly Guid FormData = new Guid("{78eae64a-f5f2-49af-b416-3247b76f46a1}"); - /// - /// Returns a GUID that is used to represent information about the custom e-mail body of the specified Windows SharePoint Services object. - /// - public static readonly Guid EmailBody = new Guid("{8cbb9252-1035-4156-9c35-f54e9056c65a}"); - /// - /// Returns a GUID that references the custom e-mail body of the specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid HasCustomEmailBody = new Guid("{47f68c3b-8930-406f-bde2-4a8c669ee87c}"); - public static readonly Guid SendEmailNotification = new Guid("{cb2413f2-7de9-4afc-8587-1ca3f563f624}"); - /// - /// Returns a GUID that represents the time when the specified Windows SharePoint Services workflow object modifications were accepted (and not rolled back to an earlier version). - /// - public static readonly Guid PendingModTime = new Guid("{4d2444c2-0e97-476c-a2a3-e9e4a9c73009}"); - /// - /// Returns a GUID that represents information about the completed status of the specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid Completed = new Guid("{35363960-d998-4aad-b7e8-058dfe2c669e}"); - public static readonly Guid WorkflowListId = new Guid("{1bfee788-69b7-4765-b109-d4d9c31d1ac1}"); - public static readonly Guid WorkflowItemId = new Guid("{8e234c69-02b0-42d9-8046-d5f49bf0174f}"); - public static readonly Guid ExtendedProperties = new Guid("{1c5518e2-1e99-49fe-bfc6-1a8de3ba16e2}"); - public static readonly Guid AdminTaskAction = new Guid("{7b016ee5-70aa-4abb-8aa3-01795b4efe6f}"); - public static readonly Guid AdminTaskDescription = new Guid("{93490584-b6a8-4996-aa00-ead5f59aae0d}"); - public static readonly Guid AdminTaskOrder = new Guid("{cf935cc2-a00c-4ad3-bca1-0865ab15afc1}"); - public static readonly Guid Service = new Guid("{48b4a73e-8853-44ac-83a8-3a4bd59ce9ec}"); - public static readonly Guid SystemTask = new Guid("{af0a3d4b-3ceb-449e-9bf4-51103f2032e3}"); - /// - /// Returns a GUID that represents information about the physical location that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Location = new Guid("{288f5f32-8462-4175-8f09-dd7ba29359a9}"); - /// - /// Returns a GUID that represents information about the recurrence field of the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid fRecurrence = new Guid("{f2e63656-135e-4f1c-8fc2-ccbe74071901}"); - /// - /// Returns a GUID that represents the URL for the meeting workspace for a specified Windows SharePoint Services event object. - /// - public static readonly Guid WorkspaceLink = new Guid("{08fc65f9-48eb-4e99-bd61-5946c439e691}"); - /// - /// Returns a GUID that represents information about the event type for the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid EventType = new Guid("{5d1d4e76-091a-4e03-ae83-6a59847731c0}"); - /// - /// Returns a GUID that represents an update identifier for a Windows SharePoint Services calendar event object. - /// - public static readonly Guid UID = new Guid("{63055d04-01b5-48f3-9e1e-e564e7c6b23b}"); - /// - /// Returns a GUID that represents the recurrence identifier for a specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid RecurrenceID = new Guid("{dfcc8fff-7c4c-45d6-94ed-14ce0719efef}"); - /// - /// Returns a GUID that represents cancellation information for the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid EventCanceled = new Guid("{b8bbe503-bb22-4237-8d9e-0587756a2176}"); - /// - /// Returns a GUID that is associated with the duration of an event, as represented in a Windows SharePoint Services workflow event object. - /// - public static readonly Guid Duration = new Guid("{4d54445d-1c84-4a6d-b8db-a51ded4e1acc}"); - /// - /// Returns a GUID that represents information about the recurrence data that is associated with a Windows SharePoint Services calendar event object. - /// - public static readonly Guid RecurrenceData = new Guid("{d12572d0-0a1e-4438-89b5-4d0430be7603}"); - /// - /// Returns a GUID that represents the time zone that is associated with a specified Windows SharePoint Services site or user object. - /// - public static readonly Guid TimeZone = new Guid("{6cc1c612-748a-48d8-88f2-944f477f301b}"); - /// - /// Returns a GUID that indicates a time zone that is expressed in XML format and is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid XMLTZone = new Guid("{c4b72ed6-45aa-4422-bff1-2b6750d30819}"); - public static readonly Guid MasterSeriesItemID = new Guid("{9b2bed84-7769-40e3-9b1d-7954a4053834}"); - /// - /// Returns a GUID that represents the meeting workspace information for a specified Windows SharePoint Services event object. - /// - public static readonly Guid Workspace = new Guid("{881eac4a-55a5-48b6-a28e-8329d7486120}"); - /// - /// Returns a GUID that is used to indicate the issue status (for example, "Active", "Resolved", "Closed") that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid IssueStatus = new Guid("{3f277a5c-c7ae-4bbe-9d44-0456fb548f94}"); - /// - /// Returns a GUID that represents the comments that are associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid Comment = new Guid("{6df9bd52-550e-4a30-bc31-a4366832a87f}"); - /// - /// Returns a GUID that represents comments that are associated with a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid Comments = new Guid("{9da97a8a-1da5-4a77-98d3-4bc10456e700}"); - /// - /// Returns a GUID that represents the data category of the specified Windows SharePoint Services message object. - /// - public static readonly Guid Category = new Guid("{6df9bd52-550e-4a30-bc31-a4366832a87d}"); - /// - /// Returns a GUID that represents related issues for a specified Windows SharePoint Services issue object. - /// - public static readonly Guid RelatedIssues = new Guid("{875fab27-6e95-463b-a4a6-82544f1027fb}"); - public static readonly Guid LinkIssueIDNoMenu = new Guid("{03f89857-27c9-4b58-aaab-620647deda9b}"); - /// - /// Returns a GUID that represents the comments that are associated with a change in issue status (for example, changing an item to "Active", "Resolved", or "Closed" status) of the specified Windows SharePoint Services issue object. - /// - public static readonly Guid V3Comments = new Guid("{6df9bd52-550e-4a30-bc31-a4366832a87e}"); - /// - /// Returns a GUID that represents the name of the person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Name = new Guid("{bfc6f32c-668c-43c4-a903-847cca2f9b3c}"); - /// - /// Returns a GUID that is used to represent the e-mail address of a person who is represented by a Windows SharePoint Services contact object. - /// - public static readonly Guid EMail = new Guid("{fce16b4c-fe53-4793-aaab-b4892e736d15}"); - /// - /// Returns a GUID that represents the notes that are associated with the person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Notes = new Guid("{e241f186-9b94-415c-9f66-255ce7f86235}"); - /// - /// Returns a GUID that indicates whether the person who is associated with the specified Windows SharePoint Services user object is a site administrator. - /// - public static readonly Guid IsSiteAdmin = new Guid("{9ba260b2-85a1-4a32-ad7a-63eaceffe6b4}"); - /// - /// Returns a GUID that is used to represent deletion information that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid Deleted = new Guid("{4ed6dfdf-86a8-4894-bd1b-4fa28042be53}"); - /// - /// Returns a GUID that represents the graphic image that is associated with a specified Windows SharePoint Services object. - /// - public static readonly Guid Picture = new Guid("{d9339777-b964-489a-bf09-2ac3c3fe5f0d}"); - /// - /// Returns a GUID that is used to represent information about the department information for the specified Windows SharePoint Services object. - /// - public static readonly Guid Department = new Guid("{05fdf852-4b64-4096-9b2b-d2a62a86bc59}"); - /// - /// Returns a GUID that represents the Session Initiation Protocol (SIP) information for a Windows SharePoint Services user object. - /// - public static readonly Guid SipAddress = new Guid("{829c275d-8744-4d9b-a42f-53f53eb60559}"); - /// - /// Returns a GUID that is used to indicate whether a person who is associated with the specified Windows SharePoint Services user profile object is marked as active or inactive by the site administrator. - /// - public static readonly Guid IsActive = new Guid("{af5036db-36f4-46c8-bde7-a677bd0ef280}"); - /// - /// Returns a GUID that represents whether or not the text that follows the first HTML division has been trimmed for the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid TrimmedBody = new Guid("{6d0f8993-5050-41f3-be6c-18902d282357}"); - public static readonly Guid DiscussionLastUpdated = new Guid("{59956c56-30dd-4cb1-bf12-ef693b42679c}"); - public static readonly Guid MessageId = new Guid("{2ef29342-2f5f-4052-90d3-8192e0705e51}"); - public static readonly Guid ThreadTopic = new Guid("{769b99d9-d361-4948-b687-f01332391629}"); - public static readonly Guid ThreadIndex = new Guid("{cef73bf1-edf6-4dd9-9098-a07d83984700}"); - /// - /// Returns a GUID that represents information about the header of the specified Windows SharePoint Services list object. - /// - public static readonly Guid EmailReferences = new Guid("{124527a9-fc10-48ff-8d44-960a7db405f8}"); - public static readonly Guid RelevantMessages = new Guid("{9161f6cb-a8e6-47b8-9d24-89415de691f7}"); - /// - /// Returns a GUID that represents the identifier of the parent folder of the specified Windows SharePoint Services object. - /// - public static readonly Guid ParentFolderId = new Guid("{a9ec25bf-5a22-4658-bd19-484e52efbe1a}"); - public static readonly Guid ShortestThreadIndex = new Guid("{4753e73b-5b5d-4bbc-8e09-c9683b0d40a7}"); - public static readonly Guid ShortestThreadIndexId = new Guid("{2bec4782-695f-406d-9e50-f1d39a2b8eb6}"); - public static readonly Guid ShortestThreadIndexIdLookup = new Guid("{8ffccefe-998b-4896-a6df-32d566f69141}"); - /// - /// Returns a GUID that is used to represent lookup information for the associated title of the specified Windows SharePoint Services discussion board message object. - /// - public static readonly Guid DiscussionTitleLookup = new Guid("{f0218b98-d0d6-4fc1-b15b-aabeb89f32a9}"); - /// - /// Returns a GUID that is used to represent information about the discussion title for the specified Windows SharePoint Services object. - /// - public static readonly Guid DiscussionTitle = new Guid("{c5abfdc7-3435-4183-9207-3d1146895cf8}"); - public static readonly Guid ParentItemEditor = new Guid("{ff90fecb-7f46-44f5-9698-db44a81b2a8b}"); - public static readonly Guid ParentItemID = new Guid("{7d014138-1886-41f0-834f-ba9f4e72f33b}"); - public static readonly Guid LastReplyBy = new Guid("{7f15088c-1448-41c7-a125-18a3a90ce543}"); - public static readonly Guid IsQuestion = new Guid("{7aead996-f9f9-4682-9e0e-f5634ab352c8}"); - public static readonly Guid BestAnswerId = new Guid("{a8b93fba-7396-443d-9884-ee332caa4560}"); - public static readonly Guid IsAnswered = new Guid("{32b1ca82-a25b-48d1-b78d-3a956ba07c41}"); - /// - /// Returns a GUID that represents the linked discussion thread title, in which the discussion items in a discussion board can be edited without using a menu. - /// - public static readonly Guid LinkDiscussionTitleNoMenu = new Guid("{3ac9353f-613f-42bd-98e1-530e9fd1cbf6}"); - /// - /// Returns a GUID that represents the linked discussion thread title, in which discussion items in a discussion board can be edited by using a menu. - /// - public static readonly Guid LinkDiscussionTitle = new Guid("{46045bc4-283a-4826-b3dd-7a78d790b266}"); - public static readonly Guid LinkDiscussionTitle2 = new Guid("{b4e31c47-f962-4f9f-9132-eb555a1a026c}"); - public static readonly Guid ReplyNoGif = new Guid("{87cda0e2-fc57-4eec-a696-b0de2f61f361}"); - public static readonly Guid ThreadingControls = new Guid("{c55a4674-640b-4bae-8738-ce0439e6f6d4}"); - /// - /// Returns a GUID that represents information about the associated reply indentation level of the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid IndentLevel = new Guid("{68227570-72dd-4816-b6b6-4b81ff99a393}"); - /// - /// Returns a GUID that represents information about the associated reply indentation level of the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid Indentation = new Guid("{26c4f53e-733a-4202-814b-377492b6c841}"); - public static readonly Guid StatusBar = new Guid("{f90bce56-87dc-4d73-bfcb-03fcaf670500}"); - /// - /// Returns a GUID that represents the body and associated indexing information (for example, the subject or discussion thread title) of the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid BodyAndMore = new Guid("{c7e9537e-bde4-4923-a100-adbd9e0a0a0d}"); - public static readonly Guid MessageBody = new Guid("{fbba993f-afee-4e00-b9be-36bc660dcdd1}"); - /// - /// Returns a GUID that represents the expansion of the body and associated indexing information (for example, the subject or discussion thread title) of the specified Windows SharePoint Services message object. - /// - public static readonly Guid BodyWasExpanded = new Guid("{af82aa75-3039-4573-84a8-73ffdfd22733}"); - public static readonly Guid QuotedTextWasExpanded = new Guid("{e393d344-2e8c-425b-a8c3-89ac3144c9a2}"); - /// - /// Returns a GUID that represents the appropriate message body to display. - /// - public static readonly Guid CorrectBodyToShow = new Guid("{b0204f69-2253-43d2-99ad-c0df00031b66}"); - /// - /// Returns a GUID that represents a link that provides a large amount of disclosed information from the message body for the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid FullBody = new Guid("{9c4be348-663a-4172-a38a-9714b2634c17}"); - /// - /// Returns a GUID that represents a link that provides a small amount of disclosed information from the message body for the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid LimitedBody = new Guid("{61b97279-cbc0-4aa9-a362-f1ff249c1706}"); - /// - /// Returns a GUID that represents a link that provides a large amount of disclosed information (in snippet form) for the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid MoreLink = new Guid("{fb6c2494-1b14-49b0-a7ca-0506d6e85a62}"); - /// - /// Returns a GUID that represents a link that provides a small amount of disclosed information (in snippet form) for the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid LessLink = new Guid("{076193bd-865b-4de7-9633-1f12069a6fff}"); - /// - /// Returns a GUID that represents whether or not quoted text can be toggled in the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid ToggleQuotedText = new Guid("{e451420d-4e62-43e3-af83-010d36e353a2}"); - public static readonly Guid Threading = new Guid("{58ca6516-51cd-41fb-a908-dd2a4aeea8bc}"); - /// - /// Returns a GUID that represents the image of the person who is referenced by a specified Windows SharePoint Services user object. - /// - public static readonly Guid PersonImage = new Guid("{adfe65ee-74bb-4771-bec5-d691d9a6a14e}"); - /// - /// Returns a GUID that represents the minimal personal view of information that is associated with a user (for example, the header information that is associated with a discussion thread title) of the specified Windows SharePoint Services discussion board object. - /// - public static readonly Guid PersonViewMinimal = new Guid("{b4ab471e-0262-462a-8b3f-c1dfc9e2d5fd}"); - public static readonly Guid IsRootPost = new Guid("{bd2216c1-a2f3-48c0-b21c-dc297d0cc658}"); - /// - /// Returns a GUID that represents combined file information as well as a specific base name of a Windows SharePoint Services object. This file information can be specific to a UNC path, a URL, local directories, or local files. - /// - public static readonly Guid Combine = new Guid("{e52012a0-51eb-4c0c-8dfb-9b8a0ebedcb6}"); - public static readonly Guid RepairDocument = new Guid("{5d36727b-bcb2-47d2-a231-1f0bc63b7439}"); - public static readonly Guid ShowRepairView = new Guid("{11851948-b05e-41be-9d9f-bc3bf55d1de3}"); - public static readonly Guid ShowCombineView = new Guid("{086f2b30-460c-4251-b75a-da88a5b205c1}"); - public static readonly Guid TemplateUrl = new Guid("{4b1bf6c6-4f39-45ac-acd5-16fe7a214e5e}"); - public static readonly Guid xd_ProgID = new Guid("{cd1ecb9f-dd4e-4f29-ab9e-e9ff40048d64}"); - public static readonly Guid xd_Signature = new Guid("{fbf29b2d-cae5-49aa-8e0a-29955b540122}"); - /// - /// Returns a GUID that represents the workflow instance that is specified in a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowInstance = new Guid("{de21c770-a12b-4f88-af4b-aeebd897c8c2}"); - /// - /// Returns a GUID that represents an identifier that is associated with another workflow object, as specified with a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowAssociation = new Guid("{8d426880-8d96-459b-ae48-e8b3836d8b9d}"); - /// - /// Returns a GUID that represents the template that is associated with a Windows SharePoint Services workflow task object. - /// - public static readonly Guid WorkflowTemplate = new Guid("{bfb1589e-2016-4b98-ae62-e91979c3224f}"); - public static readonly Guid List = new Guid("{f44e428b-61c8-4100-a911-a3a635f43bb5}"); - /// - /// Returns a GUID that represents the item identifier for the specified Windows SharePoint Services object. - /// - public static readonly Guid Item = new Guid("{92b8e9d0-a11b-418f-bf1c-c44aaa73075d}"); - /// - /// Returns a GUID that represents the user information that is associated with the specified Windows SharePoint Services object. - /// - public static readonly Guid User = new Guid("{5928ff1f-daa1-406c-b4a9-190485a448cb}"); - /// - /// Returns a GUID that represents whether or not a workflow event has occurred for the specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid Occurred = new Guid("{5602dc33-a60a-4dec-bd23-d18dfcef861d}"); - /// - /// Returns a GUID that represents the name of the specified Windows SharePoint Services event object. - /// - public static readonly Guid Event = new Guid("{20a1a5b1-fddf-4420-ac68-9701490e09af}"); - /// - /// Returns a GUID that represents information about the permissions group of the specified Windows SharePoint Services object. - /// - public static readonly Guid Group = new Guid("{c86a2f7f-7680-4a0b-8907-39c4f4855a35}"); - /// - /// Returns a GUID that represents the outcome that is associated with the specified Windows SharePoint Services workflow object. - /// - public static readonly Guid Outcome = new Guid("{dcde7b1f-918b-4ed5-819f-9798f8abac37}"); - /// - /// Returns a GUID that is associated with an event duration, as represented in a Windows SharePoint Services workflow event object This GUID is limited to a maximum of 255 characters. - /// - public static readonly Guid DLC_Duration = new Guid("{80289bac-fd36-4848-b67a-bc8b5b621ec2}"); - /// - /// Returns a GUID that is associated with an event description, as represented in a Windows SharePoint Services workflow event object. This GUID is limited to a maximum of 255 characters. - /// - public static readonly Guid DLC_Description = new Guid("{2fd53156-ff9d-4cc3-b0ac-fe8a7bc82283}"); - /// - /// Returns a GUID that represents data that is associated with a Windows SharePoint Services workflow event object. - /// - public static readonly Guid Data = new Guid("{38269294-165e-448a-a6b9-f0e09688f3f9}"); - public static readonly Guid Purpose = new Guid("{8ee23f39-e2d1-4b46-8945-42386b24829d}"); - /// - /// Returns a GUID that represents the type of interface connection that is used with the associated Windows SharePoint Services object. - /// - public static readonly Guid ConnectionType = new Guid("{939dfb93-3107-44c6-a98f-dd88dca3f8cf}"); - /// - /// Returns a GUID that represents information about the file type for version history of the specified Windows SharePoint Services library picture object. - /// - public static readonly Guid FileType = new Guid("{c53a03f3-f930-4ef2-b166-e0f2210c13c0}"); - /// - /// Returns a GUID that represents the image size of the specified Windows SharePoint Services image object. - /// - public static readonly Guid ImageSize = new Guid("{922551b8-c7e0-46a6-b7e3-3cf02917f68a}"); - /// - /// Returns a GUID that represents the width of the specified Windows SharePoint Services image object. - /// - public static readonly Guid ImageWidth = new Guid("{7e68a0f9-af76-404c-9613-6f82bc6dc28c}"); - /// - /// Returns a GUID that represents the height of the specified Windows SharePoint Services image object. - /// - public static readonly Guid ImageHeight = new Guid("{1944c034-d61b-42af-aa84-647f2e74ca70}"); - /// - /// Returns a GUID that represents information about the creation date of the specified Windows SharePoint Services image object. - /// - public static readonly Guid ImageCreateDate = new Guid("{a5d2f824-bc53-422e-87fd-765939d863a5}"); - /// - /// Returns a GUID that represents the URL of the encoded thumbnail search image for the specified Windows SharePoint Services object. - /// - public static readonly Guid EncodedAbsThumbnailUrl = new Guid("{b9e6f3ae-5632-4b13-b636-9d1a2bd67120}"); - /// - /// Returns a GUID that represents the encoded Web image of the search URL for the specified Windows SharePoint Services object. - /// - public static readonly Guid EncodedAbsWebImgUrl = new Guid("{a1ca0063-779f-49f9-999c-a4a2e3645b07}"); - public static readonly Guid SelectedFlag = new Guid("{7ebf72ca-a307-4c18-9e5b-9d89e1dae74f}"); - /// - /// Returns a GUID that represents information about the image of the specified Windows SharePoint Services picture library object. - /// - public static readonly Guid NameOrTitle = new Guid("{76d1cc87-56de-432c-8a2a-16e5ba5331b3}"); - public static readonly Guid RequiredField = new Guid("{de1baa4b-2117-473b-aa0c-4d824034142d}"); - /// - /// Returns a GUID that represents information about the keyword summary of the specified Windows SharePoint Services object. - /// - public static readonly Guid Keywords = new Guid("{b66e9b50-a28e-469b-b1a0-af0e45486874}"); - /// - /// Returns a GUID that represents the thumbnail image for a Windows SharePoint Services image object. - /// - public static readonly Guid Thumbnail = new Guid("{ac7bb138-02dc-40eb-b07a-84c15575b6e9}"); - public static readonly Guid Preview = new Guid("{bd716b26-546d-43f2-b229-62699581fa9f}"); - /// - /// Returns a GUID that represents the selected decision status that is associated with a Windows SharePoint Services workflow event object. - /// - public static readonly Guid DecisionStatus = new Guid("{ac3a1092-34ad-42b2-8d47-a79d01d9f516}"); - /// - /// Returns a GUID that represents the availability status of the specified Windows SharePoint Services object designated as an attendee. - /// - public static readonly Guid AttendeeStatus = new Guid("{3329f39d-70ed-4858-b8c8-c5237634bf08}"); - /// - /// Returns a GUID that represents the field that indicates an all-day event for the specified Windows SharePoint Services calendar event object. - /// - public static readonly Guid fAllDayEvent = new Guid("{7d95d1f4-f5fd-4a70-90cd-b35abc9b5bc8}"); - /// - /// Returns a GUID that represents information about the primary spoken and written language of a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid Language = new Guid("{d81529e8-384c-4ca6-9c43-c86a256e6a44}"); - public static readonly Guid SurveyTitle = new Guid("{e6f528fb-2e22-483d-9c80-f2536acdc6de}"); - /// - /// Returns a GUID that is associated with the description of the content type of the wiki (for example, a "How To" wiki content type description) of the specified Windows SharePoint Services wiki document object. - /// - public static readonly Guid WikiField = new Guid("{c33527b4-d920-4587-b791-45024d00068a}"); - public static readonly Guid PublishedDate = new Guid("{b1b53d80-23d6-e31b-b235-3a286b9f10ea}"); - public static readonly Guid PostCategory = new Guid("{38bea83b-350a-1a6e-f34a-93a6af31338b}"); - public static readonly Guid BaseAssociationGuid = new Guid("{e9359d15-261b-48f6-a302-01419a68d4de}"); - public static readonly Guid XomlUrl = new Guid("{566da236-762b-4a76-ad1f-b08b3c703fce}"); - public static readonly Guid RulesUrl = new Guid("{ad97fbac-70af-4860-a078-5ee704946f93}"); - /// - /// Returns a GUID that represents the categories that are associated with a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Categories = new Guid("{9ebcd900-9d05-46c8-8f4d-e46e87328844}"); - /// - /// Returns a GUID that represents the address of an event that is represented by a specified Windows SharePoint Services event object. - /// - public static readonly Guid ol_EventAddress = new Guid("{493896da-0a4f-46ec-a68e-9cfd1a5fc19b}"); - /// - /// Returns a GUID that represents the completion date that is associated with a specified Windows SharePoint Services task object. - /// - public static readonly Guid DateCompleted = new Guid("{24bfa3c2-e6a0-4651-80e9-3db44bf52147}"); - /// - /// Returns a GUID that represents the total hours of work performed by a person or resource that is referenced by a specified Windows SharePoint Services object. - /// - public static readonly Guid TotalWork = new Guid("{f3c4a259-19a2-44b8-ab3d-e9145d07d538}"); - /// - /// Returns a GUID that represents the actual work value that is associated with a specified Windows SharePoint Services workflow task object. - /// - public static readonly Guid ActualWork = new Guid("{b0b3407e-1c33-40ed-a37c-2430b7a5d081}"); - public static readonly Guid TaskCompanies = new Guid("{3914f98e-6d99-4218-9ba3-af7370b9e7bc}"); - /// - /// Returns a GUID that represents mileage information that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Mileage = new Guid("{3126c2f1-063e-4892-828f-0696ec6e105f}"); - /// - /// Returns a GUID that represents the billing information that is associated with a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid BillingInformation = new Guid("{4f03f66b-fb1e-4ed2-ab8e-f6ed3fe14844}"); - /// - /// Returns a GUID that represents an organizational role description for a person who is referenced by a specified Windows SharePoint Services object. - /// - public static readonly Guid Role = new Guid("{eeaeaaf1-4110-465b-905e-df1073a7e0e6}"); - /// - /// Returns a GUID that represents the middle name of a person who is represented by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid MiddleName = new Guid("{418c8d29-6f2e-44c3-8955-2cd7ec3e2151}"); - /// - /// Returns a GUID that represents the suffix for a person who is referenced by a specified Windows SharePoint Services contact object (such as M.D., Jr., Sr., or III). - /// - public static readonly Guid Suffix = new Guid("{d886eba3-d018-4103-a322-d5780127ef8a}"); - /// - /// Returns a GUID that represents the telephone number of the assistant for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid AssistantNumber = new Guid("{f55de332-074e-4e71-a71a-b90abfad51ae}"); - /// - /// Returns a GUID that represents a second corporate telephone number that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Business2Number = new Guid("{6547d03a-76d3-4d74-9d34-f51b837c0879}"); - /// - /// Returns a GUID that represents a callback telephone number that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid CallbackNumber = new Guid("{344e9657-b17f-4344-a834-ff7c056bcc5e}"); - /// - /// Returns a GUID that represents the car identification number that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid CarNumber = new Guid("{92a011a9-fd1b-42e0-b6fa-afcfee1928fa}"); - /// - /// Returns a GUID that represents the main telephone number of a corporation that is associated with a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid CompanyNumber = new Guid("{27cb1283-bda2-4ae8-bcff-71725b674dbb}"); - /// - /// Returns a GUID that represents the second home telephone number of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Home2Number = new Guid("{8c5a385d-2fff-42da-a4c5-f6a904f2e491}"); - /// - /// Returns a GUID that represents the home facsimile telephone number of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeFaxNumber = new Guid("{c189a857-e6b0-488f-83a0-f4ee0a3ad01e}"); - /// - /// Returns a GUID that represents the Integrated Services Digital Network (ISDN) number of a person who is represented by a specified Windows SharePoint Services object. - /// - public static readonly Guid ISDNNumber = new Guid("{a579062a-6c1d-4ad3-9d5e-035f9f2c1882}"); - /// - /// Returns a GUID that represents an alternative telephone number for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherNumber = new Guid("{96e02495-f428-48bc-9f13-06d98ba58c34}"); - /// - /// Returns a GUID that represents an alternative facsimile telephone number for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherFaxNumber = new Guid("{aad15eb6-d7fd-47b8-abd4-adc0fe33a6ba}"); - /// - /// Returns a GUID that represents the number of a pager device for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid PagerNumber = new Guid("{f79bf074-daf7-4c06-a314-15b287fdf4c9}"); - /// - /// Returns a GUID that represents the primary telephone number of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid PrimaryNumber = new Guid("{d69bcc0e-57c3-4f3b-bbc5-b090edf21f0f}"); - /// - /// Returns a GUID that represents the number of a portable radio unit of a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid RadioNumber = new Guid("{d1aede4f-1352-48d9-81e2-b10097c359c1}"); - /// - /// Returns a GUID that represents the Telex number of a person who is represented by a specified Windows SharePoint Services object. - /// - public static readonly Guid TelexNumber = new Guid("{e7be7f3c-c436-481d-8865-669e5146f53c}"); - /// - /// Returns a GUID that represents the number of a TeleType (TTY) or Telephone Device for the Deaf (TDD) of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid TTYTDDNumber = new Guid("{f54697f1-0357-4c5a-a711-0cb654bc73e4}"); - /// - /// Returns a GUID that represents the instant messaging addressof a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid IMAddress = new Guid("{4cbd96f7-09c6-4b5e-ad42-1cbe123de63a}"); - /// - /// Returns a GUID that represents the home street address for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeAddressStreet = new Guid("{8c66e340-0985-4d68-af03-3050ece4862b}"); - /// - /// Returns a GUID that represents the home city of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeAddressCity = new Guid("{5aeabc56-57c6-4861-bc12-bd72c30fc6bd}"); - /// - /// Returns a GUID that represents the home state or province of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeAddressStateOrProvince = new Guid("{f5b36006-69b0-418c-bd4a-f25ca7e096bb}"); - /// - /// Returns a GUID that represents information about the home postal code of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeAddressPostalCode = new Guid("{c0e4b4c6-6245-4846-8561-b8c6c01fefc1}"); - /// - /// Returns a GUID that represents the home country of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid HomeAddressCountry = new Guid("{897ecfd7-4293-4782-b463-bd68440a5fed}"); - /// - /// Returns a GUID that represents an alternative street address of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherAddressStreet = new Guid("{dff5dfc2-e2b7-4a19-bde7-76dabc90a3d2}"); - /// - /// Returns a GUID that represents an alternative city address of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherAddressCity = new Guid("{90fa9a8e-aac0-4828-9cb4-78f98416affa}"); - /// - /// Returns a GUID that represents the alternative state or province of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherAddressStateOrProvince = new Guid("{f45883bc-8733-4b77-ab5d-43613986aa12}"); - /// - /// Returns a GUID that represents the alternative postal code of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherAddressPostalCode = new Guid("{0557c3f8-60c4-4dfb-b5ba-bf3c4e4386b1}"); - /// - /// Returns a GUID that represents an alternative country of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OtherAddressCountry = new Guid("{3c0e9e00-8fcc-479f-9d8d-3447cda34c5b}"); - /// - /// Returns a GUID that represents a second e-mail address for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Email2 = new Guid("{e232d6c8-9f49-4be2-bb28-b90570bcf167}"); - /// - /// Returns a GUID that represents a third e-mail address for a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid Email3 = new Guid("{8bd27dbd-29a0-4ccd-bcb4-03fe70c538b1}"); - /// - /// Returns a GUID that represents the department name or identifier (ID) of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid ol_Department = new Guid("{c814b2cf-84c6-4f56-b4a4-c766938a97c5}"); - /// - /// Returns a GUID that represents the identifier of the physical office of a person who is represented by a specified Windows SharePoint Services object. - /// - public static readonly Guid Office = new Guid("{26169ab2-4bd2-4870-b077-10f49c8a5822}"); - /// - /// Returns a GUID that represents the profession of a person who is referenced in a Windows SharePoint Services contact object. - /// - public static readonly Guid Profession = new Guid("{f0753a13-44b1-4269-82af-5c34c57b0c67}"); - /// - /// Returns a GUID that represents the manager's name in the corporate hierarchy for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid ManagersName = new Guid("{ba934502-d68d-4960-a54b-51e15fef5fd3}"); - /// - /// Returns a GUID that represents the name of the assistant to a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid AssistantsName = new Guid("{2aea194d-e399-4f05-95af-94f87b1f2687}"); - /// - /// Returns a GUID that represents the informal name of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Nickname = new Guid("{6b0a2cd7-a7f9-41ca-b932-f3bebb603793}"); - /// - /// Returns a GUID that represents the name of the spouse of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid SpouseName = new Guid("{f590b1de-8e28-4c17-91bc-bf4096024b7e}"); - /// - /// Returns a GUID that represents the birth date that is associated with a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Birthday = new Guid("{c4c7d925-bc1b-4f37-826d-ac49b4fb1bc1}"); - /// - /// Returns a GUID that represents the corporate start date for the specified Windows SharePoint Services user. - /// - public static readonly Guid Anniversary = new Guid("{9d76802c-13c4-484a-9872-d7f9641c4672}"); - /// - /// Returns a GUID that represents the gender of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Gender = new Guid("{23550288-91b5-4e7f-81f9-1a92661c4838}"); - /// - /// Returns a GUID that represents the initials that are associated with a person who is represented by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Initials = new Guid("{7a282f86-69d9-40ff-ae1c-c746cf21256b}"); - /// - /// Returns a GUID that represents information about the personal activities of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid Hobbies = new Guid("{203fa378-6eb8-4ed9-a4f9-221a4c1fbf46}"); - /// - /// Returns a GUID that represents a field that contains the names of children who in turn are associated with a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid ChildrensNames = new Guid("{6440b402-8ec5-4d7a-83f4-afccb556b5cc}"); - /// - /// Returns a GUID that represents the customized information in a field named UserField1 for a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid UserField1 = new Guid("{566656f5-17b3-4291-98a5-5074aadf77b3}"); - /// - /// Returns a GUID that represents the customized information in a field named UserField2 for a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid UserField2 = new Guid("{182d1b9e-1718-4e11-b279-38f7ed0a20d6}"); - /// - /// Returns a GUID that represents the customized information in a field named UserField3 for a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid UserField3 = new Guid("{a03eb53e-f123-4af9-9355-f92bd75c00b3}"); - /// - /// Returns a GUID that represents the customized information in a field named UserField4 for a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid UserField4 = new Guid("{adefa4ca-14c3-4694-b531-f51b706efe9d}"); - /// - /// Returns a GUID that represents the Government Identification number of a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid GovernmentIDNumber = new Guid("{da31d3c9-f9da-4c35-88d4-60aafa4c3f19}"); - /// - /// Returns a GUID that represents the name of a computer network for a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid ComputerNetworkName = new Guid("{86a78395-c8ad-429e-abff-be09417b523e}"); - /// - /// Returns a GUID that represents the name of the person who provided a referral for a person who is referenced by a specified Windows SharePoint Services contact object. - /// - public static readonly Guid ReferredBy = new Guid("{9b4cc5a9-1119-43e4-b2a8-412c4031f92b}"); - /// - /// Returns a GUID that represents the employee identification number or organizational identification number that applies to a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid OrganizationalIDNumber = new Guid("{0850ae15-19dd-431f-9c2f-3aff3ae292ce}"); - public static readonly Guid CustomerID = new Guid("{81368791-7cbc-4230-981a-a7669ade9801}"); - /// - /// Returns a GUID that represents the URL for the personal Web site of a person who is represented by a specified Windows SharePoint Services object. - /// - public static readonly Guid PersonalWebsite = new Guid("{5aa071d9-3254-40fb-82df-5cedeff0c41e}"); - /// - /// Returns a GUID that represents a File Transfer Protocol (FTP) URL that is associated with a person who is referenced in a specified Windows SharePoint Services contact object. - /// - public static readonly Guid FTPSite = new Guid("{d733736e-4204-4812-9565-191567b27e33}"); - public static readonly Guid ParentVersionString = new Guid("{bc1a8efb-0f4c-49f8-a38f-7fe22af3d3e0}"); - public static readonly Guid ParentLeafName = new Guid("{774eab3a-855f-4a34-99da-69dc21043bec}"); - public static readonly Guid _DCDateCreated = new Guid("{9f8b4ee0-84b7-42c6-a094-5cbde2115eb9}"); - public static readonly Guid _Identifier = new Guid("{3c76805f-ad45-483a-9c85-7ac24506ce1a}"); - public static readonly Guid _Version = new Guid("{78be84b9-d70c-447b-8275-8dcd768b6f92}"); - public static readonly Guid _Revision = new Guid("{16b4ab96-0ce5-4c82-a836-f3117e8996ff}"); - public static readonly Guid _DCDateModified = new Guid("{810dbd02-bbf5-4c67-b1ce-5ad7c5a512b2}"); - public static readonly Guid _LastPrinted = new Guid("{b835f7c6-88a0-45d5-80c9-7ab4b2888b2b}"); - public static readonly Guid _Contributor = new Guid("{370b7779-0344-4b9f-8f2d-dc1c62eae801}"); - public static readonly Guid _Coverage = new Guid("{3b1d59c0-26b1-4de6-abbd-3edb4e2c6eca}"); - public static readonly Guid _Format = new Guid("{36111fdd-2c65-41ac-b7ef-48b9b8da4526}"); - public static readonly Guid _Publisher = new Guid("{2eedd0ae-4281-4b77-99be-68f8b3ad8a7a}"); - public static readonly Guid _Relation = new Guid("{5e75c854-6e9d-405d-b6c1-f8725bae5822}"); - public static readonly Guid _RightsManagement = new Guid("{ada3f0cb-6f95-4588-bb08-d97cc0623522}"); - public static readonly Guid _Source = new Guid("{b0a3c1db-faf1-48f0-9be1-47d2fc8cb5d6}"); - public static readonly Guid _ResourceType = new Guid("{edecec70-f6e2-4c3c-a4c7-f61a515dfaa9}"); - public static readonly Guid _EditMenuTableStart2 = new Guid("{1344423c-c7f9-4134-88e4-ad842e2d723c}"); - public static readonly Guid MyEditor = new Guid("{078b9dba-eb8c-4ec5-bfdd-8d220a3fcc5d}"); - public static readonly Guid ThumbnailExists = new Guid("{1f43cd21-53c5-44c5-8675-b8bb86083244}"); - public static readonly Guid AlternateThumbnailUrl = new Guid("{f39d44af-d3f3-4ae6-b43f-ac7330b5e9bd}"); - public static readonly Guid PreviewExists = new Guid("{3ca8efcd-96e8-414f-ba90-4c8c4a8bfef8}"); - public static readonly Guid IconOverlay = new Guid("{b77cdbcf-5dce-4937-85a7-9fc202705c91}"); - public static readonly Guid UIVersion = new Guid("{8e334549-c2bd-4110-9f61-672971be6504}"); - public static readonly Guid SortBehavior = new Guid("{423874f8-c300-4bfb-b7a1-42e2159e3b19}"); - public static readonly Guid FolderChildCount = new Guid("{960ff01f-2b6d-4f1b-9c3f-e19ad8927341}"); - public static readonly Guid ItemChildCount = new Guid("{b824e17e-a1b3-426e-aecf-f0184d900485}"); - public static readonly Guid EmailHeaders = new Guid("{e6985df4-cf66-4313-bcda-d89744d3b02f}"); - public static readonly Guid Predecessors = new Guid("{c3a92d97-2b77-4a25-9698-3ab54874bc6f}"); - public static readonly Guid MobilePhone = new Guid("{bf03d3ca-aa6e-4845-809a-b4378b37ce08}"); - public static readonly Guid wic_System_Copyright = new Guid("{f08ab41d-9a03-49ae-9413-6cd284a15625}"); - public static readonly Guid PreviewOnForm = new Guid("{8c0d0aac-9b76-4951-927a-2490abe13c0b}"); - public static readonly Guid ThumbnailOnForm = new Guid("{9941082a-4160-46a1-a5b2-03394bfdf7ee}"); - public static readonly Guid NoCodeVisibility = new Guid("{a05a8639-088a-4aea-b8a9-afc888971c81}"); - public static readonly Guid AssociatedListId = new Guid("{b75067a2-e23b-499f-aa07-4ceb6c79e0b3}"); - public static readonly Guid RestrictContentTypeId = new Guid("{8b02a33c-accd-4b73-bcae-6932c7aab812}"); - public static readonly Guid WorkflowDisplayName = new Guid("{5263cd09-a770-4549-b012-d9f3df3d8df6}"); - public static readonly Guid ParticipantsPicker = new Guid("{8137f7ad-9170-4c1d-a17b-4ca7f557bc88}"); - public static readonly Guid Participants = new Guid("{453c2d71-c41e-46bc-97c1-a5a9535053a3}"); - public static readonly Guid Facilities = new Guid("{a4e7b3e1-1b0a-4ffa-8426-c94d4cb8cc57}"); - public static readonly Guid FreeBusy = new Guid("{393003f9-6ccb-4ea9-9623-704aa4748dec}"); - public static readonly Guid Overbook = new Guid("{d8cd5bcf-3768-4d6c-a8aa-fefa3c793d8d}"); - public static readonly Guid GbwLocation = new Guid("{afaa4198-9797-4e45-9825-8f7e7b0f5dd5}"); - public static readonly Guid GbwCategory = new Guid("{7fc04acf-6b4f-418c-8dc5-ecfb0085bb51}"); - public static readonly Guid WhatsNew = new Guid("{cf68a174-123b-413e-9ec1-b43e3a3175d7}"); - public static readonly Guid DueDate = new Guid("{c1e86ea6-7603-493c-ab5d-db4bbfe8f96a}"); - public static readonly Guid Confidential = new Guid("{9b0e6471-c5c5-42ef-9ade-63170bf28819}"); - public static readonly Guid AllowEditing = new Guid("{7266b59c-030b-4ca3-bc09-bb8e76ad969b}"); - public static readonly Guid V4SendTo = new Guid("{e0f298a5-7e3e-4895-9ff8-90d88ec4526d}"); - public static readonly Guid Confirmations = new Guid("{ef7465d3-5d54-487b-b081-ade80acae88e}"); - public static readonly Guid V4CallTo = new Guid("{7111aa1b-e7ae-4b69-acaf-db669b76e03a}"); - public static readonly Guid ConfirmedTo = new Guid("{1b89212c-1c67-487a-8c14-4d30bf4ef223}"); - public static readonly Guid CallBack = new Guid("{274b7e21-284a-4c49-bec6-f1f2cb6fc344}"); - public static readonly Guid Detail = new Guid("{6529a881-d745-4117-a552-3dcc7110e9b8}"); - public static readonly Guid CallTime = new Guid("{63fc6806-db53-4d0d-b18b-eaf90e96ddf5}"); - public static readonly Guid Resolved = new Guid("{a6fd2bb9-c701-4168-99cc-242e42f7671a}"); - public static readonly Guid ResolvedBy = new Guid("{b4fa187b-eb65-478e-8bc6-93b0da320f03}"); - public static readonly Guid ResolvedDate = new Guid("{c4995c71-4c5c-4e9f-afc1-a9033f2bfde5}"); - public static readonly Guid Description = new Guid("{3f155110-a6a2-4d70-926c-94648101f0e8}"); - public static readonly Guid HolidayDate = new Guid("{335e22c3-b8a4-4234-9790-7a03eeb7b0d4}"); - public static readonly Guid V4HolidayDate = new Guid("{492b1ac0-c594-4013-a2b6-ea70f5a8a506}"); - public static readonly Guid IsNonWorkingDay = new Guid("{baf7091c-01fb-4831-a975-08254f87f234}"); - public static readonly Guid UserName = new Guid("{211a8cfc-93b7-4173-9254-0bfe2d1643da}"); - public static readonly Guid Date = new Guid("{2139e5cc-6c75-4a65-b84c-00fe93027db3}"); - public static readonly Guid DayOfWeek = new Guid("{61fc45dd-b33d-4679-8646-be9e6584fadd}"); - public static readonly Guid Start = new Guid("{05e6336c-d22e-478e-9414-366762883b3f}"); - public static readonly Guid End = new Guid("{04b29608-b1e8-4ff9-90d5-5328096dd5ac}"); - public static readonly Guid In = new Guid("{ee394fd4-4c11-4d8e-baff-83270c1921aa}"); - public static readonly Guid Out = new Guid("{fde05b9b-52bf-43dc-9b96-bb35fa7aa05d}"); - public static readonly Guid Break = new Guid("{9b12fb06-254e-43b3-bfc8-8eea422ebc9f}"); - public static readonly Guid ScheduledWork = new Guid("{3bdf7bd3-f229-419e-8e12-3dfecb49ed38}"); - public static readonly Guid Overtime = new Guid("{35d79e8b-3701-4659-9c27-c070ed3c2bfa}"); - public static readonly Guid NightWork = new Guid("{aaa68c08-6276-4337-9bce-b9cd852c7328}"); - public static readonly Guid HolidayWork = new Guid("{b5a7350f-2716-46ca-9c42-66bb39d042ec}"); - public static readonly Guid HolidayNightWork = new Guid("{dc9100ec-251d-4e81-a6cb-d967a065ba24}"); - public static readonly Guid Late = new Guid("{df7f27a4-d87b-4a97-947b-13d1d4f7e6de}"); - public static readonly Guid LeaveEarly = new Guid("{a2a86efe-c28e-4dde-ab56-0afa31664bbc}"); - public static readonly Guid Oof = new Guid("{63c1c608-df6f-4cfa-bcab-fdbf9c223e31}"); - public static readonly Guid Vacation = new Guid("{dfd58778-bf8e-4769-8265-09ac03159eed}"); - public static readonly Guid NumberOfVacation = new Guid("{44e16d52-da1b-4e72-8bdb-89a3b77ec8b0}"); - public static readonly Guid ShortComment = new Guid("{691b9a4b-512e-4341-b3f1-68914130d5b2}"); - public static readonly Guid ListType = new Guid("{81dde544-1e25-4765-b5fd-ba613198d850}"); - public static readonly Guid Content = new Guid("{7650d41a-fa26-4c72-a641-af4e93dc7053}"); - public static readonly Guid MobileContent = new Guid("{53a2a512-d395-4852-8714-d4c27e7585f3}"); - public static readonly Guid Whereabout = new Guid("{e2a07293-596a-4c59-9089-5c4f9339077f}"); - public static readonly Guid From = new Guid("{4cd541b9-c8ee-468f-bee6-33f3b9baa722}"); - public static readonly Guid GoFromHome = new Guid("{6570d35e-7f0a-4123-93c9-f53ffa5810d3}"); - public static readonly Guid Until = new Guid("{fe3344ab-b468-471f-8fa5-9b506c7d1557}"); - public static readonly Guid GoingHome = new Guid("{2ead592e-f05c-41a2-9817-e06dac25bc19}"); - public static readonly Guid ContactInfo = new Guid("{e1a85174-b8d0-4962-9ce6-758f8b612725}"); - public static readonly Guid IMEDisplay = new Guid("{90244050-709c-4837-9316-93863fbd3da6}"); - public static readonly Guid IMEComment1 = new Guid("{d2433b20-3f02-4432-817d-369f104a2dcd}"); - public static readonly Guid IMEComment2 = new Guid("{e2c93917-cf32-4b29-be5c-d71f1bac7714}"); - public static readonly Guid IMEComment3 = new Guid("{7c52f61a-e1e0-4341-9e2f-9b36cddfdd7c}"); - public static readonly Guid IMEUrl = new Guid("{84b0fe85-6b16-40c3-8507-e56c5bbc482e}"); - public static readonly Guid IMEPos = new Guid("{f3cdbcfd-f456-45f4-9000-b6f34bb95d84}"); - public static readonly Guid HealthRuleService = new Guid("{2d6e61d0-be31-460c-ab8b-77d8b369f517}"); - public static readonly Guid HealthRuleType = new Guid("{7dd0a092-8704-4ed2-8253-ac309150ac59}"); - public static readonly Guid HealthRuleScope = new Guid("{e59f08c9-fa34-4f94-a00a-f6458b1d3c56}"); - public static readonly Guid HealthRuleSchedule = new Guid("{26761ba3-729d-4bfc-9658-77b55e01f8d5}"); - public static readonly Guid HealthReportServers = new Guid("{84a318aa-9035-4529-98b9-e08bb20a5da0}"); - public static readonly Guid HealthReportServices = new Guid("{e2b0b450-6795-4b86-86b7-3c21ab1797fb}"); - public static readonly Guid HealthReportCategory = new Guid("{a63505f2-f42c-4d94-b03b-78ba2c73d40e}"); - public static readonly Guid HealthReportExplanation = new Guid("{b4c8faec-5d60-49ee-a5fb-6165f5c3e6a9}"); - public static readonly Guid HealthReportRemedy = new Guid("{8aa22caa-8000-44c9-b343-a7705bbed863}"); - public static readonly Guid HealthRuleReportLink = new Guid("{cf4ff575-f1f5-4c5b-b595-54bbcccd0c62}"); - public static readonly Guid HealthReportSeverityIcon = new Guid("{89efcbd9-9796-41f0-b569-65325f1882dc}"); - public static readonly Guid HealthReportSeverity = new Guid("{505423c5-f085-48b9-9432-12073d643ba5}"); - public static readonly Guid HealthRuleAutoRepairEnabled = new Guid("{1e41a55e-ef71-4740-b65a-d11e24c1d00d}"); - public static readonly Guid HealthRuleCheckEnabled = new Guid("{7b2b1712-a73d-4ad7-a9d0-662f0291713d}"); - public static readonly Guid HealthRuleVersion = new Guid("{6b6b1455-09ee-43b7-beea-4dc97456de2f}"); - public static readonly Guid XSLStyleCategory = new Guid("{dfffbbfb-0cc3-4ce7-8cb3-a2958fb726a1}"); - public static readonly Guid XSLStyleWPType = new Guid("{4499086f-9ac1-41df-86c3-d8c1f8fc769a}"); - public static readonly Guid XSLStyleIconUrl = new Guid("{3dfb3e11-9ccd-4404-b44a-a71f6399ea56}"); - public static readonly Guid XSLStyleBaseView = new Guid("{4630e6ac-e543-4667-935a-2cc665e9b755}"); - public static readonly Guid XSLStyleRequiredFields = new Guid("{acb9088a-a171-4b99-aa7a-10388586bc74}"); - public static readonly Guid ParentID = new Guid("{fd447db5-3908-4b47-8f8c-a5895ed0aa6a}"); - public static readonly Guid AppAuthor = new Guid("{6bfaba20-36bf-44b5-a1b2-eb6346d49716}"); - public static readonly Guid AppEditor = new Guid("{e08400f3-c779-4ed2-a18c-ab7f34caa318}"); - public static readonly Guid NoCrawl = new Guid("{b0e12a3b-cf63-47d1-8418-4ef850d87a3c}"); - public static readonly Guid PrincipalCount = new Guid("{dcc67ebd-247f-4bee-8626-85ff6f69fbb6}"); - public static readonly Guid Checkmark = new Guid("{ebf1c037-47eb-4355-998d-47ce9f2cc047}"); - public static readonly Guid RelatedLinks = new Guid("{1ad7c220-c893-4c15-b95c-b69b992bdee2}"); - public static readonly Guid MUILanguages = new Guid("{fb005daa-caf9-4ecd-84d5-6bdd2eb3dce7}"); - public static readonly Guid ContentLanguages = new Guid("{58073ebd-b204-4899-bc77-54402c61e9e9}"); - public static readonly Guid UserInfoHidden = new Guid("{e8a80787-5f99-459a-af8d-b830157ed45f}"); - public static readonly Guid IsFeatured = new Guid("{5a034ff8-d7a4-4d69-ab26-5f5a043b572d}"); - public static readonly Guid DisplayTemplateJSTemplateHidden = new Guid("{3d0684f7-ca97-413d-9d03-d00f480059ae}"); - public static readonly Guid DisplayTemplateJSTargetControlType = new Guid("{0e49b273-3102-4b7d-b609-2e05dd1a17d9}"); - public static readonly Guid DisplayTemplateJSIconUrl = new Guid("{57468ccb-0c02-422c-ba0a-61a44ba41784}"); - public static readonly Guid DisplayTemplateJSTemplateType = new Guid("{d63173ac-b914-4f90-9cf8-4ff4352e41a3}"); - public static readonly Guid DisplayTemplateJSTargetScope = new Guid("{df8bd7e5-b3db-4a94-afb4-7296397d829d}"); - public static readonly Guid DisplayTemplateJSTargetListTemplate = new Guid("{9f927425-78e9-49c3-b03b-65e1211394e1}"); - public static readonly Guid DisplayTemplateJSTargetContentType = new Guid("{ed095cf7-534e-460b-965f-f14269e70f5a}"); - public static readonly Guid DisplayTemplateJSConfigurationUrl = new Guid("{0f2f686a-3921-432e-85fd-9c535bf671b2}"); - public static readonly Guid DefaultCssFile = new Guid("{cc10b158-50b4-4f02-8f3a-b9b6c3102628}"); - public static readonly Guid RelatedItems = new Guid("{d2a04afc-9a05-48c8-a7fa-fa98f9496141}"); - public static readonly Guid PreviouslyAssignedTo = new Guid("{1982e408-0f94-4149-8349-16f301d89134}"); - - // Hashset to contain the whole list of built in fields - private static HashSet builtInFieldsHashSet; - private static object builtInFieldsHashSetSyncLock = new object(); - - /// - /// This method returns a Boolean value that specifies whether or not the current object matches the specified GUID. This value is used as a file identifier for an object that is associated with a Windows SharePoint Services Web site. - /// - /// - /// - /// Returns a GUID. - /// - /// File identifier. - public static bool Contains(Guid fid) - { - if (builtInFieldsHashSet == null) - { - lock (builtInFieldsHashSetSyncLock) - { - if (builtInFieldsHashSet == null) - { - builtInFieldsHashSet = new HashSet( - new Guid[] { - DisplayTemplateJSTargetListTemplate, - Editor, - WebPage, - Profession, - IsNonWorkingDay, - CallTime, - ImageHeight, - EndDate, - Modified_x0020_By, - Last_x0020_Modified, - ThumbnailExists, - RelevantMessages, - ContentLanguages, - MiddleName, - HolidayWork, - AllowEditing, - HealthReportSeverity, - _EditMenuTableEnd, - OffsiteParticipant, - CallBack, - Location, - Comments, - ParentID, - OtherAddressCity, - LinkIssueIDNoMenu, - Created_x0020_Date, - Gender, - WorkflowDisplayName, - SpouseName, - Service, - Date, - HTML_x0020_File_x0020_Type, - Resolved, - User, - RelatedItems, - URL, - Detail, - RecurrenceID, - AppAuthor, - HealthRuleSchedule, - ParentItemEditor, - DLC_Duration, - HomeAddressStateOrProvince, - Company, - Until, - CheckoutUser, - ThreadingControls, - FirstName, - From, - DefaultCssFile, - DiscussionTitle, - FullBody, - WorkflowVersion, - VirusStatus, - FirstNamePhonetic, - DisplayTemplateJSIconUrl, - End, - EncodedAbsThumbnailUrl, - Description, - DisplayTemplateJSTargetContentType, - V4HolidayDate, - EmailSubject, - IMEComment1, - ThreadTopic, - List, - Oof, - ContactInfo, - SendEmailNotification, - _HasCopyDestinations, - ParentFolderId, - NoCodeVisibility, - AttendeeStatus, - PercentComplete, - Body, - HealthReportCategory, - _CheckinComment, - _Revision, - Expires, - Email2, - HomeAddressCity, - Whereabout, - ComputerNetworkName, - File_x0020_Type, - Out, - AdminTaskDescription, - RelatedIssues, - DisplayTemplateJSConfigurationUrl, - _ModerationStatus, - DisplayTemplateJSTargetScope, - ParentItemID, - WorkflowItemId, - ShortestThreadIndexIdLookup, - Workspace, - OrganizationalIDNumber, - ScheduledWork, - Role, - MobilePhone, - Break, - IMEComment3, - RadioNumber, - SipAddress, - _Comments, - GoFromHome, - HealthRuleReportLink, - ReferredBy, - GoingHome, - WorkState, - ImageWidth, - ShortestThreadIndexId, - UserField4, - _Publisher, - ThreadIndex, - WorkflowOutcome, - AssignedTo, - SelectedFlag, - Keywords, - SelectTitle, - HomeAddressStreet, - ID, - Thumbnail, - TaskCompanies, - LastReplyBy, - IMEComment2, - ConnectionType, - UserField3, - BaseAssociationGuid, - MyEditor, - V4SendTo, - HasCustomEmailBody, - WorkflowName, - GbwCategory, - MessageId, - PreviewOnForm, - Indentation, - OtherAddressCountry, - EmailBody, - _Coverage, - fAllDayEvent, - PendingModTime, - BillingInformation, - Combine, - URLwMenu, - FullName, - OtherAddressPostalCode, - LinkFilename, - HomeAddressCountry, - _EditMenuTableStart, - _CopySource, - Author, - EmailReferences, - Department, - HealthRuleVersion, - CustomerID, - Modified, - Priority, - RulesUrl, - _Author, - AdminTaskAction, - PersonViewMinimal, - HealthRuleAutoRepairEnabled, - LinkDiscussionTitleNoMenu, - Home2Number, - GovernmentIDNumber, - Confirmations, - WorkflowTemplate, - XSLStyleIconUrl, - PublishedDate, - OtherFaxNumber, - PrincipalCount, - ParentLeafName, - DisplayTemplateJSTargetControlType, - XSLStyleBaseView, - _Format, - NameOrTitle, - LeaveEarly, - WorkflowInstance, - _SharedFileIndex, - PagerNumber, - EncodedAbsWebImgUrl, - Participants, - RepairDocument, - HealthReportExplanation, - ContentType, - _RightsManagement, - LinkDiscussionTitle2, - Purpose, - _LastPrinted, - PersonalWebsite, - ConfirmedTo, - Group, - TaskDueDate, - ShowCombineView, - LinkTitleNoMenu, - FileDirRef, - Name, - TaskType, - FileLeafRef, - TemplateUrl, - Overtime, - AlternateThumbnailUrl, - CallbackNumber, - Hobbies, - ShortComment, - _EditMenuTableStart2, - _UIVersionString, - WorkflowInstanceID, - XMLTZone, - EmailCalendarSequence, - wic_System_Copyright, - Confidential, - WorkflowLink, - ResolvedDate, - WorkZip, - EmailTo, - Suffix, - LastNamePhonetic, - Category, - V3Comments, - Mileage, - Deleted, - SortBehavior, - WorkFax, - _Relation, - CellPhone, - WorkspaceLink, - ol_Department, - In, - EmailFrom, - Office, - CompanyNumber, - Facilities, - HolidayNightWork, - DiscussionTitleLookup, - FTPSite, - WorkCity, - XomlUrl, - ContentTypeId, - UniqueId, - StatusBar, - EmailCalendarUid, - Vacation, - FreeBusy, - _Photo, - Comment, - Overbook, - NoCrawl, - HealthRuleScope, - TimeZone, - ISDNNumber, - RecurrenceData, - EMail, - _IsCurrentVersion, - File_x0020_Size, - WorkCountry, - NightWork, - AssociatedListId, - owshiddenversion, - AdminTaskOrder, - IsAnswered, - LinkFilenameNoMenu, - DueDate, - Start, - OtherAddressStateOrProvince, - ChildrensNames, - OtherAddressStreet, - ScopeId, - IconOverlay, - Threading, - _DCDateCreated, - JobTitle, - TaskStatus, - Outcome, - AssistantsName, - MessageBody, - Initials, - IsSiteAdmin, - PermMask, - RestrictContentTypeId, - Data, - BodyAndMore, - _Level, - ExtendedProperties, - IsQuestion, - EmailHeaders, - UIVersion, - _Version, - WorkflowAssociation, - _Contributor, - CompanyPhonetic, - ResolvedBy, - DecisionStatus, - Item, - ServerUrl, - AssistantNumber, - _UIVersion, - EventCanceled, - UID, - ReplyNoGif, - IsFeatured, - BaseName, - EmailSender, - Event, - ParticipantsPicker, - IndentLevel, - ActualWork, - V4CallTo, - Occurred, - EmailCc, - ToggleQuotedText, - LinkDiscussionTitle, - Title, - CarNumber, - UserField2, - fRecurrence, - IssueStatus, - ShowRepairView, - XSLStyleCategory, - BestAnswerId, - Subject, - Email3, - Anniversary, - Order, - HealthRuleService, - TrimmedBody, - _Category, - FileRef, - LimitedBody, - ManagersName, - _Status, - MasterSeriesItemID, - WorkflowListId, - Picture, - FormURN, - TTYTDDNumber, - OtherNumber, - Attachments, - URLNoMenu, - HolidayDate, - BodyWasExpanded, - PostCategory, - _ResourceType, - Duration, - StartDate, - xd_Signature, - MobileContent, - Preview, - HealthRuleType, - ListType, - IMEPos, - Checkmark, - AppEditor, - DocIcon, - ParentVersionString, - HomeAddressPostalCode, - PersonImage, - UserField1, - PreviouslyAssignedTo, - _DCDateModified, - _Identifier, - GUID, - ProgId, - Language, - UserName, - OffsiteParticipantReason, - WorkAddress, - _ModerationComments, - EventType, - Created, - FolderChildCount, - CorrectBodyToShow, - GbwLocation, - InstanceID, - HomePhone, - WhatsNew, - RelatedLinks, - Birthday, - DiscussionLastUpdated, - DisplayTemplateJSTemplateHidden, - WikiField, - Edit, - XSLStyleWPType, - FSObjType, - _EndDate, - ShortestThreadIndex, - ol_EventAddress, - TelexNumber, - DisplayTemplateJSTemplateType, - HealthRuleCheckEnabled, - RequiredField, - IMAddress, - xd_ProgID, - TotalWork, - FileType, - Nickname, - PrimaryNumber, - ImageCreateDate, - NumberOfVacation, - SystemTask, - IsRootPost, - Late, - UserInfoHidden, - Business2Number, - Created_x0020_By, - FormData, - LinkTitle, - IMEDisplay, - Notes, - _SourceUrl, - FileSizeDisplay, - HealthReportSeverityIcon, - ThumbnailOnForm, - WorkPhone, - TaskGroup, - HealthReportRemedy, - EmailCalendarDateStamp, - MoreLink, - _Source, - MetaInfo, - DateCompleted, - Completed, - ItemChildCount, - SelectFilename, - SurveyTitle, - DayOfWeek, - EncodedAbsUrl, - DLC_Description, - QuotedTextWasExpanded, - IsActive, - MUILanguages, - HomeFaxNumber, - Categories, - ImageSize, - HealthReportServices, - HealthReportServers, - Content, - Predecessors, - PreviewExists, - LessLink, - XSLStyleRequiredFields, - IMEUrl - }); - } - } - } - - if (builtInFieldsHashSet != null) - { - return builtInFieldsHashSet.Contains(fid); - } - else - { - return false; - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReference.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReference.cs deleted file mode 100644 index 27fc5a1c39..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReference.cs +++ /dev/null @@ -1,235 +0,0 @@ -namespace CamlBuilder -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Represents a reference to a field within a query. - /// - internal class FieldReference - { - /// - /// Field alias. - /// - public string Alias { get; set; } - - /// - /// This specifies the sort order on a FieldRef. - /// - /// - /// Query defaults this to true when no value is specified. - /// - public bool? Ascending { get; set; } - - /// - /// Specifies the URL for the .aspx file that is used to create a Meeting Workspace site. - /// - public string CreateUrl { get; set; } - - /// - /// This attribute provides the display name of the field that is referenced. - /// - public string DisplayName { get; set; } - - /// - /// This attribute is only supported within the ViewFields element. True if the field is - /// explicitly declared in the view definition and is not returned in a Fields enumeration inside a view. - /// - public bool? Explicit { get; set; } - - /// - /// Field format. - /// - public string Format { get; set; } - - /// - /// Specifies the GUID that identifies the field. - /// - public string Id { get; set; } - - /// - /// If set to Primary, specifies that the field is the primary key for its table and thus - /// uniquely identifies each record in the table. - /// - public string Key { get; set; } - - /// - /// Specifies the parent foreign list when the FieldRef element is a child of an Eq element - /// in Join element. The value is an alias for the list that is defined by the ListAlias - /// attribute of the Join element - /// - public string List { get; set; } - - /// - /// When the field is a Lookup type, specifies that queries should look for the item by its - /// unique item ID rather than the field value. This can be useful, for example, when multiple - /// items have identical values in the field and you want to query for a specific item. - /// - /// - /// Query default this to false when no value is specified - /// - public bool? LookupId { get; set; } - - /// - /// This attribute provides the internal name of the field that is referenced. - /// - public string Name { get; set; } - - /// - /// Specifies the type of reference for a field in an events list. - /// - public string RefType { get; set; } - - /// - /// The ShowField attribute can be set to the field name to display. By default, a hyperlinked text - /// from the Title field of the record in the external list is displayed. But the ShowField attribute - /// can be used to override that and display another field from the external list. - /// - /// - /// The following data types are allowed as targets of a ShowField attribute: Text, Choice, and Counter. - /// - public string ShowField { get; set; } - - /// - /// Specifies that the field contains only text values. - /// - public bool? TextOnly { get; set; } - - /// - /// Specifies the function that is applied to a totals column or a calculated column. - /// - public FieldReferenceFunctionType? Type { get; set; } - - /// - /// Creates an instance of FieldReference with all it's properties set to default. - /// - public FieldReference() - { - } - - /// - /// Creates an instance of FieldReference with the initial specified . - /// - /// Internal name of the field. - public FieldReference(string name) - { - Name = name; - } - - /// - /// Uses as a field reference internal name and returns - /// a new . - /// - /// String to be used as FieldReference internal name. - public static implicit operator FieldReference(string fieldName) - { - return new FieldReference(fieldName); - } - - internal string GetCaml() - { - var values = new List>(); - - if (!string.IsNullOrEmpty(Alias)) - { - values.Add(new KeyValuePair("Alias", Alias)); - } - - if (!string.IsNullOrEmpty(CreateUrl)) - { - values.Add(new KeyValuePair("CreateURL", CreateUrl)); - } - - if (!string.IsNullOrEmpty(DisplayName)) - { - values.Add(new KeyValuePair("DisplayName", DisplayName)); - } - - if (!string.IsNullOrEmpty(Format)) - { - values.Add(new KeyValuePair("Format", Format)); - } - - if (!string.IsNullOrEmpty(Id)) - { - values.Add(new KeyValuePair("ID", Id)); - } - - if (!string.IsNullOrEmpty(Key)) - { - values.Add(new KeyValuePair("Key", Key)); - } - - if (!string.IsNullOrEmpty(List)) - { - values.Add(new KeyValuePair("List", List)); - } - - if (!string.IsNullOrEmpty(Name)) - { - values.Add(new KeyValuePair("Name", Name)); - } - - if (!string.IsNullOrEmpty(RefType)) - { - values.Add(new KeyValuePair("RefType", RefType)); - } - - if (!string.IsNullOrEmpty(ShowField)) - { - values.Add(new KeyValuePair("ShowField", ShowField)); - } - - if (Type.HasValue) - { - values.Add(new KeyValuePair("Type", GetTypeString(Type.Value))); - } - - if (Ascending.HasValue) - { - values.Add(new KeyValuePair("Ascending", Ascending.Value.ToString().ToUpperInvariant())); - } - - if (Explicit.HasValue) - { - values.Add(new KeyValuePair("Explicit", Explicit.ToString().ToUpperInvariant())); - } - - if (LookupId.HasValue) - { - values.Add(new KeyValuePair("LookupId", LookupId.Value.ToString().ToUpperInvariant())); - } - - if (TextOnly.HasValue) - { - values.Add(new KeyValuePair("TextOnly", TextOnly.Value.ToString().ToUpperInvariant())); - } - - return $" $"{kv.Key}='{kv.Value}'"))}/>"; - } - - private string GetTypeString(FieldReferenceFunctionType type) - { - switch (type) - { - case FieldReferenceFunctionType.Average: - return "AVG"; - case FieldReferenceFunctionType.Count: - return "COUNT"; - case FieldReferenceFunctionType.Maximum: - return "MAX"; - case FieldReferenceFunctionType.Minimum: - return "MIN"; - case FieldReferenceFunctionType.Sum: - return "SUM"; - case FieldReferenceFunctionType.StandardDeviation: - return "STDEV"; - case FieldReferenceFunctionType.Variance: - return "VAR"; - default: - throw new ArgumentOutOfRangeException(nameof(Type), type, null); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReferenceFunctionType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReferenceFunctionType.cs deleted file mode 100644 index 7d8f5d753b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/FieldReferenceFunctionType.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Specifies the function that is applied to a totals column or a calculated column. - /// - internal enum FieldReferenceFunctionType - { - Average, - Count, - Maximum, - Minimum, - Sum, - StandardDeviation, - Variance - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/ComplexOperator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/ComplexOperator.cs deleted file mode 100644 index 6fc2a00e4a..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/ComplexOperator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace CamlBuilder.Internal.Operators -{ - internal class ComplexOperator : Operator - { - public Value Value { get; } - - internal ComplexOperator( - OperatorType operatorType, - FieldReference fieldRef, - Value value) - : base(operatorType, fieldRef) - { - Value = value; - } - - public override string GetCaml() - { - return $@"<{OperatorTypeString}>{FieldReference.GetCaml()}{Value.GetCaml()}"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/InOperator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/InOperator.cs deleted file mode 100644 index 45059406e2..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/InOperator.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace CamlBuilder.Internal.Operators -{ - using System.Collections.Generic; - using System.Linq; - - internal class InOperator : Operator - { - public Value[] Values { get; } - - internal InOperator( - FieldReference fieldRef, - IEnumerable values) - : base(OperatorType.In, fieldRef) - { - Values = values.ToArray(); - } - - public override string GetCaml() - { - return $@"<{OperatorTypeString}>{FieldReference.GetCaml()}{string.Join("\n", Values.Select(v => v.GetCaml()))}"; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/MembershipOperator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/MembershipOperator.cs deleted file mode 100644 index 9d306abbaa..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/MembershipOperator.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace CamlBuilder.Internal.Operators -{ - using System; - - internal class MembershipOperator : Operator - { - private readonly MembershipType membershipType; - - public MembershipOperator(FieldReference fieldRef, MembershipType membershipType) - : base(OperatorType.Membership, fieldRef) - { - this.membershipType = membershipType; - } - - private string GetMembershipTypeString() - { - switch (membershipType) - { - case MembershipType.SpWebAllUsers: - return "SPWeb.AllUsers"; - case MembershipType.SpGroup: - return "SPGroup"; - case MembershipType.SpWebGroups: - return "SPWeb.Groups"; - case MembershipType.CurrentUserGroups: - return "CurrentUserGroups"; - case MembershipType.SpWebUsers: - return "SPWeb.Users"; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public override string GetCaml() - { - return $@"<{OperatorTypeString} Type='{GetMembershipTypeString()}'>{FieldReference.GetCaml()}"; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/SimpleOperator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/SimpleOperator.cs deleted file mode 100644 index 64d61d88d6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Operators/SimpleOperator.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace CamlBuilder.Internal.Operators -{ - internal class SimpleOperator : Operator - { - internal SimpleOperator( - OperatorType operatorType, - FieldReference fieldRef) - : base(operatorType, fieldRef) - { - } - - public override string GetCaml() => $@"<{OperatorTypeString}>{FieldReference.GetCaml()}"; - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/AnyValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/AnyValue.cs deleted file mode 100644 index 25c306d4d9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/AnyValue.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - internal class AnyValue : Value - { - private readonly object anyValue; - - public AnyValue(ValueType type, bool? includeTimeValue, object anyValue) - : base(type, includeTimeValue) - { - this.anyValue = anyValue; - } - - protected override string GetCamlValue() - { - return anyValue.ToString(); - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/ListPropertyValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/ListPropertyValue.cs deleted file mode 100644 index 0d5a9c497a..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/ListPropertyValue.cs +++ /dev/null @@ -1,106 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - using System.Collections.Generic; - using System.Linq; - - internal class ListPropertyValue : Value - { - private readonly ListPropertyValueItem[] items; - - public ListPropertyValue( - ValueType type, - bool? includeTimeValue, - IEnumerable items) - : base(type, includeTimeValue) - { - this.items = items.ToArray(); - } - - protected override string GetCamlValue() - { - return string.Join("\n", items.Select(GetItemElement)); - } - - private string GetItemElement(ListPropertyValueItem item) - { - var values = new List> - { - new KeyValuePair( - "Select", - item.Select) - }; - - if (item.AutoHyperLink.HasValue) - { - values.Add(new KeyValuePair( - "AutoHyperLink", - item.AutoHyperLink.Value.ToString().ToUpper())); - } - - if (item.AutoHyperLinkNoEncoding.HasValue) - { - values.Add(new KeyValuePair( - "AutoHyperLinkNoEncoding", - item.AutoHyperLinkNoEncoding.Value.ToString().ToUpper())); - } - - if (item.AutoHyperLink.HasValue) - { - values.Add(new KeyValuePair( - "AutoHyperLink", - item.AutoHyperLink.Value.ToString().ToUpper())); - } - - if (item.AutoNewLine.HasValue) - { - values.Add(new KeyValuePair( - "AutoNewLine", - item.AutoNewLine.Value.ToString().ToUpper())); - } - - if (!string.IsNullOrEmpty(item.Default)) - { - values.Add(new KeyValuePair( - "Default", - item.Default)); - } - - if (item.ExpandXml.HasValue) - { - values.Add(new KeyValuePair( - "ExpandXML", - item.ExpandXml.Value.ToString().ToUpper())); - } - - if (item.HtmlEncode.HasValue) - { - values.Add(new KeyValuePair( - "HTMLEncode", - item.HtmlEncode.Value.ToString().ToUpper())); - } - - if (item.StripWs.HasValue) - { - values.Add(new KeyValuePair( - "StripWS", - item.StripWs.Value.ToString().ToUpper())); - } - - if (item.UrlEncode.HasValue) - { - values.Add(new KeyValuePair( - "URLEncode", - item.UrlEncode.Value.ToString().ToUpper())); - } - - if (item.UrlEncodeAsUrl.HasValue) - { - values.Add(new KeyValuePair( - "URLEncodeAsURL", - item.UrlEncodeAsUrl.Value.ToString().ToUpper())); - } - - return $" $"{kv.Key}='{kv.Value}'"))}>"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/MonthValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/MonthValue.cs deleted file mode 100644 index 9a4d237875..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/MonthValue.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - internal class MonthValue : Value - { - public MonthValue(bool? includeTimeValue) - : base(ValueType.DateTime, includeTimeValue) - { - } - - protected override string GetCamlValue() - { - return ""; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/NowValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/NowValue.cs deleted file mode 100644 index f9429ea269..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/NowValue.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - internal class NowValue : Value - { - public NowValue(bool? includeTimeValue) - : base(ValueType.DateTime, includeTimeValue) - { - } - - protected override string GetCamlValue() - { - return ""; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/TodayValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/TodayValue.cs deleted file mode 100644 index d8887ec20d..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/TodayValue.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - internal class TodayValue : Value - { - private readonly int? offset; - - public TodayValue(bool? includeTimeValue, int? offset) - : base(ValueType.DateTime, includeTimeValue) - { - this.offset = offset; - } - - public TodayValue(bool? includeTimeValue) - : this(includeTimeValue, null) - { - } - - protected override string GetCamlValue() - { - return offset.HasValue - ? $"" - : ""; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/UserIdValue.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/UserIdValue.cs deleted file mode 100644 index 4828a627d7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Internal/Values/UserIdValue.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CamlBuilder.Internal.Values -{ - internal class UserIdValue : Value - { - public UserIdValue() - : base(ValueType.Integer) - { - } - - protected override string GetCamlValue() - { - return ""; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ListPropertyValueItem.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ListPropertyValueItem.cs deleted file mode 100644 index 728465a59f..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ListPropertyValueItem.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace CamlBuilder -{ - using System; - - /// - /// Represents an item to be used by ListProperty value. - /// - internal class ListPropertyValueItem - { - /// - /// True to surround text with anchor tags if the text appears like a - /// hyperlink, for example, www.microsoft.com. - /// - public bool? AutoHyperLink { get; set; } - - /// - /// True to surround text with anchor tags if the text appears like a - /// hyperlink (for example, www.microsoft.com) but without HTML encoding. - /// - public bool? AutoHyperLinkNoEncoding { get; set; } - - /// - /// True to insert break-line tags into the text stream and to - /// replace multiple spaces with a nonbreaking space (&nbsp;). - /// - public bool? AutoNewLine { get; set; } - - /// - /// Sets the default ProgID for the application that created the list. - /// - public string Default { get; set; } - - /// - /// True to re-pass the rendered content through the Collaborative Application - /// Markup Language (CAML) interpreter, which allows CAML to render CAML. - /// - public bool? ExpandXml { get; set; } - - /// - /// True to convert embedded characters so that they are displayed as text in the - /// browser. In other words, characters that could be confused with HTML tags are - /// converted to entities. - /// - public bool? HtmlEncode { get; set; } - - /// - /// Specifies a field in the List of Lists table. - /// - public string Select { get; set; } - - /// - /// True to remove white space from the beginning and end of the value returned by the element. - /// - public bool? StripWs { get; set; } - - /// - /// True to convert special characters, such as spaces, to quoted UTF-8 format, - /// for example, %c3%ab for character ë. - /// - public bool? UrlEncode { get; set; } - - /// - /// Like URLEncode, but true to specify that the string to encode is a path component of a - /// URL and not to encode the forward slash (/). - /// - public bool? UrlEncodeAsUrl { get; set; } - - /// - /// Creates an instance of ListPropertyValueItem with initial specified - /// - /// Specifies a field in the List of Lists table. - public ListPropertyValueItem(string select) - { - if (string.IsNullOrEmpty(select)) - { - throw new ArgumentNullException(nameof(select)); - } - - Select = select; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoin.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoin.cs deleted file mode 100644 index 0792fdcb2b..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoin.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace CamlBuilder -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// Defines a CAML logical join. This class has no constructors available. To instanciate a - /// new logical join use public static methods. - /// - internal class LogicalJoin : Statement - { - /// - /// Gets the logical join type. - /// - public LogicalJoinType LogicalJoinType { get; } - - private readonly List internalStatements; - - private readonly string logicalJoinTypeString; - - private LogicalJoin(LogicalJoinType logicalJoinType, IEnumerable statements) - { - LogicalJoinType = logicalJoinType; - logicalJoinTypeString = logicalJoinType.ToString(); - internalStatements = statements.ToList(); - } - - /// - /// Adds a new statement to this logical join - /// - /// Statement to be added. - public void AddStatement(Statement statement) - { - internalStatements.Add(statement); - } - - /// - /// Adds new statements to this logical join. - /// - /// Statements to be added to logical join. - public void AddStatements(IEnumerable statements) - { - internalStatements.AddRange(statements); - } - - public bool HasStatements() - { - return internalStatements.Count > 0; - } - - /// - /// Returns CAML string representation of this - /// logical join statement. - /// - /// CAML string. - public override string GetCaml() - { - if (internalStatements.Count == 0) - { - return string.Empty; - } - - if (internalStatements.Count == 1) - { - return internalStatements[0].GetCaml(); - } - - var queue = new Queue(internalStatements); - - return BuildCamlRecursively(queue); - } - - private string BuildCamlRecursively(Queue statementsQueue) - { - if (statementsQueue.Count == 2) - { - return $@"<{logicalJoinTypeString}>{statementsQueue.Dequeue().GetCaml()}{statementsQueue.Dequeue().GetCaml()}"; - } - - return $@"<{logicalJoinTypeString}>{statementsQueue.Dequeue().GetCaml()}{BuildCamlRecursively(statementsQueue)}"; - } - - /// - /// Instanciates a new And logical join with specified inner . - /// - /// And statements. - /// And logical join instance. - public static LogicalJoin And(params Statement[] statements) - { - return new LogicalJoin(LogicalJoinType.And, statements); - } - - /// - /// Instanciates a new And logical join with specified inner . - /// - /// And statements. - /// And logical join instance. - public static LogicalJoin And(IEnumerable statements) - { - return new LogicalJoin(LogicalJoinType.And, statements); - } - - /// - /// Instanciates a new Or logical join with specified inner . - /// - /// Or statements. - /// Or logical join instance. - public static LogicalJoin Or(params Statement[] statements) - { - return new LogicalJoin(LogicalJoinType.Or, statements); - } - - /// - /// Instanciates a new Or logical join with specified inner . - /// - /// Or statements. - /// Or logical join instance. - public static LogicalJoin Or(IEnumerable statements) - { - return new LogicalJoin(LogicalJoinType.Or, statements); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoinType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoinType.cs deleted file mode 100644 index ff7a77d0e9..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/LogicalJoinType.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Specifies logical join types. - /// - internal enum LogicalJoinType - { - /// - /// Indicates an Or logical join. - /// - Or, - - /// - /// Indicates an And logical join. - /// - And - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/MembershipType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/MembershipType.cs deleted file mode 100644 index dafd96802e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/MembershipType.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Defines membership types used by Membership operator. - /// - /// - internal enum MembershipType - { - /// - /// Indicates SPWeb.AllUsers membership. - /// - SpWebAllUsers, - - /// - /// Indicates SPGroup membership. - /// - SpGroup, - - /// - /// Indicates SPWebGroups membership. - /// - SpWebGroups, - - /// - /// Indicates CurrentUserGroups membership. - /// - CurrentUserGroups, - - /// - /// Indicates SPWebUsers membership. - /// - SpWebUsers - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Operator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Operator.cs deleted file mode 100644 index 504ded4f28..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Operator.cs +++ /dev/null @@ -1,364 +0,0 @@ -namespace CamlBuilder -{ - using System; - using System.Collections.Generic; - using Internal.Operators; - - /// - /// Defines a CAML operator. This is an abstract class. To instanciate an operator use public static methods. - /// -#pragma warning disable CA1716 - internal abstract class Operator : Statement - { -#pragma warning restore CA1716 - internal readonly string OperatorTypeString; - - /// - /// Gets the operator type. - /// - public OperatorType OperatorType { get; } - - /// - /// Gets the name of the field on which this operator acts on. - /// - public FieldReference FieldReference { get; private set; } - - protected internal Operator( - OperatorType operatorType, - FieldReference fieldRef) - { - OperatorType = operatorType; - FieldReference = fieldRef; - - switch (operatorType) - { - case OperatorType.Equal: - OperatorTypeString = "Eq"; - break; - case OperatorType.NotEqual: - OperatorTypeString = "Neq"; - break; - case OperatorType.GreaterThan: - OperatorTypeString = "Gt"; - break; - case OperatorType.GreaterThanOrEqualTo: - OperatorTypeString = "Geq"; - break; - case OperatorType.LowerThan: - OperatorTypeString = "Lt"; - break; - case OperatorType.LowerThanOrEqualTo: - OperatorTypeString = "Leq"; - break; - case OperatorType.IsNull: - case OperatorType.IsNotNull: - case OperatorType.BeginsWith: - case OperatorType.Contains: - case OperatorType.DateRangesOverlap: - case OperatorType.Includes: - case OperatorType.NotIncludes: - case OperatorType.In: - case OperatorType.Membership: - OperatorTypeString = operatorType.ToString(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(operatorType), operatorType, null); - } - } - - /// - /// Instanciates a new IsNull operator to perform on specified . - /// - /// Reference to the field to operate on. - /// IsNull operator instance. - public static Operator IsNull(FieldReference fieldRef) - { - return new SimpleOperator(OperatorType.IsNull, fieldRef); - } - - /// - /// Instanciates a new IsNotNull operator to perform on specified . - /// - /// Reference to the field to operate on. - /// IsNotNull operator instance. - public static Operator IsNotNull(FieldReference fieldRef) - { - return new SimpleOperator(OperatorType.IsNotNull, fieldRef); - } - - /// - /// Instanciates a new Equal operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// Equal operator instance. - public static Operator Equal(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.Equal, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new Equal operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// Equal operator instance. - public static Operator Equal(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.Equal, fieldRef, value); - } - - /// - /// Instanciates a new NotEqual operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// NotEqual operator instance. - public static Operator NotEqual(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.NotEqual, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new NotEqual operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// NotEqual operator instance. - public static Operator NotEqual(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.NotEqual, fieldRef, value); - } - - /// - /// Instanciates a new BeginsWith operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// BeginsWith operator instance. - public static Operator BeginsWith(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.BeginsWith, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new BeginsWith operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// BeginsWith operator instance. - public static Operator BeginsWith(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.BeginsWith, fieldRef, value); - } - - /// - /// Instanciates a new Contains operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// Contains operator instance. - public static Operator Contains(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.Contains, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new Contains operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// Contains operator instance. - public static Operator Contains(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.Contains, fieldRef, value); - } - - /// - /// Instanciates a new DateRangesOverlap operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// DateRangesOverlap operator instance. - public static Operator DateRangesOverlap(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.DateRangesOverlap, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new DateRangesOverlap operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// DateRangesOverlap operator instance. - public static Operator DateRangesOverlap(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.DateRangesOverlap, fieldRef, value); - } - - /// - /// Instanciates a new GreaterThan operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// GreaterThan operator instance. - public static Operator GreaterThan(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.GreaterThan, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new GreaterThan operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// GreaterThan operator instance. - public static Operator GreaterThan(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.GreaterThan, fieldRef, value); - } - - /// - /// Instanciates a new GreaterThanOrEqualTo operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// GreaterThanOrEqualTo operator instance. - public static Operator GreaterThanOrEqualTo(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.GreaterThanOrEqualTo, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new GreaterThanOrEqualTo operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// GreaterThanOrEqualTo operator instance. - public static Operator GreaterThanOrEqualTo(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.GreaterThanOrEqualTo, fieldRef, value); - } - - /// - /// Instanciates a new LowerThan operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// LowerThan operator instance. - public static Operator LowerThan(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.LowerThan, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new LowerThan operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// LowerThan operator instance. - public static Operator LowerThan(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.LowerThan, fieldRef, value); - } - - /// - /// Instanciates a new LowerThanOrEqualTo operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// LowerThanOrEqualTo operator instance. - public static Operator LowerThanOrEqualTo(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.LowerThanOrEqualTo, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new LowerThanOrEqualTo operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// LowerThanOrEqualTo operator instance. - public static Operator LowerThanOrEqualTo(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.LowerThanOrEqualTo, fieldRef, value); - } - - /// - /// Instanciates a new Includes operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// Includes operator instance. - public static Operator Includes(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.Includes, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new Includes operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// Includes operator instance. - public static Operator Includes(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.Includes, fieldRef, value); - } - - /// - /// Instanciates a new NotIncludes operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Field type - /// Value against which the value returned by the field element is compared to. - /// NotIncludes operator instance. - public static Operator NotIncludes(FieldReference fieldRef, ValueType valueType, object value) - { - return new ComplexOperator(OperatorType.NotIncludes, fieldRef, Value.ObjectValue(valueType, value)); - } - - /// - /// Instanciates a new NotIncludes operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Value against which the value returned by the field element is compared to. - /// NotIncludes operator instance. - public static Operator NotIncludes(FieldReference fieldRef, Value value) - { - return new ComplexOperator(OperatorType.NotIncludes, fieldRef, value); - } - - /// - /// Instanciates a new In operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Values against which the value returned by the field element is compared to. - /// In operator instance. - public static Operator In(FieldReference fieldRef, IEnumerable values) - { - return new InOperator(fieldRef, values); - } - - /// - /// Instanciates a new Membership operator which will perform on specified . - /// - /// Reference to the field to operate on. - /// Type of membership for the operator to use to filter for. - /// Membership operator instance. - public static Operator Membership(FieldReference fieldRef, MembershipType membershipType) - { - return new MembershipOperator(fieldRef, membershipType); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OperatorType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OperatorType.cs deleted file mode 100644 index 09ab8f22ea..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OperatorType.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Specifies operator types. - /// - internal enum OperatorType - { - /// - /// Indicates an Equal operator - /// - Equal, - - /// - /// Indicates a NotEqual operator - /// - NotEqual, - - /// - /// Indicates a GreaterThan operator - /// - GreaterThan, - - /// - /// Indicates a GreaterThanOrEqualTo operator - /// - GreaterThanOrEqualTo, - - /// - /// Indicates a LowerThan operator - /// - LowerThan, - - /// - /// Indicates a LowerThanOrEqualTo operator - /// - LowerThanOrEqualTo, - - /// - /// Indicates an IsNull operator - /// - IsNull, - - /// - /// Indicates an IsNotNull operator - /// - IsNotNull, - - /// - /// Indicates a BeginsWith operator - /// - BeginsWith, - - /// - /// Indicates a Contains operator - /// - Contains, - - /// - /// Indicates a DateRangesOverlap operator - /// - DateRangesOverlap, - - /// - /// Indicates an Includes operator - /// - Includes, - - /// - /// Indicates an NotIncludes operator - /// - NotIncludes, - - /// - /// Indicates an In operator - /// - In, - - /// - /// Indicates a Membership operator - /// - Membership - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OrderByFieldOrder.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OrderByFieldOrder.cs deleted file mode 100644 index 618d669ba7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/OrderByFieldOrder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Specifies the order of query result. - /// - internal enum OrderByFieldOrder - { - /// - /// Defines ascending order. - /// - Ascending, - /// - /// Specifies descending order. - /// - Descending - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Query.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Query.cs deleted file mode 100644 index 90e70bf91e..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Query.cs +++ /dev/null @@ -1,145 +0,0 @@ -namespace CamlBuilder -{ - using System.Collections.Generic; - using System.Text; - - /// - /// Class which represents a CAML query. - /// - /// - /// Defines a CAML query. This class has no constructors available. To instanciate a - /// new query use public static methods. - /// - internal class Query - { - private readonly List orderByFields = new List(); - - private readonly List groupByFields = new List(); - - /// - /// Gets the statement holded by this query. - /// - public Statement Statement { get; } - - private Query(Statement statement) - { - Statement = statement; - } - - /// - /// Instanciates a new Query with the specified inner - /// - /// - /// - public static Query Build(Statement statement) - { - return new Query(statement); - } - - /// - /// Returns query's CAML string representation - /// - /// true to return only query's Where; otherwise false - /// Query CAML string surrounded by Query element or only the Where clause - public string GetCaml(bool whereClauseOnly) - { - if (whereClauseOnly) - { - return GetWhereCaml(); - } - - return $@"{GetWhereCaml()}{GetGroupByCaml()}{GetOrderByCaml()}"; - } - - /// - /// Returns query's CAML string representation surrounded by Query element - /// - /// Query CAML string surrounded by Query element. - public string GetCaml() - { - return GetCaml(false); - } - - /// - /// Adds a new query sort order relatively to a specified . - /// - /// Reference to the field where to perform the ordering on. - /// Returns the query itself. - /// Use with false value to specify descending order. - public Query OrderBy(FieldReference fieldRef) - { - orderByFields.Add(fieldRef); - return this; - } - - /// - /// Adds a collection of sort orders relatively to specified . - /// - /// References to the fields where to perform the ordering on. - /// Returns the query itself. - /// Use with false value to specify descending order. - public Query OrderBy(IEnumerable fieldRefs) - { - orderByFields.AddRange(fieldRefs); - return this; - } - - /// - /// Specify the query's group-by options. Query will be grouped by specified . - /// - /// Reference to the field to group by. - /// Returns the query itself. - public Query GroupBy(FieldReference fieldRef) - { - groupByFields.Add(fieldRef); - return this; - } - - /// - /// Specify the query's group-by options. Query will be grouped by specified . - /// - /// References to the fields to group by. - /// Returns the query itself. - public Query GroupBy(IEnumerable fieldRefs) - { - groupByFields.AddRange(fieldRefs); - return this; - } - - private string GetWhereCaml() - { - if (Statement == null) - { - return string.Empty; - } - - return $@"{Statement.GetCaml()}"; - } - - private string GetOrderByCaml() - { - if (orderByFields.Count == 0) - { - return string.Empty; - } - - var sb = new StringBuilder(); - orderByFields.ForEach(o => sb.AppendLine(o.GetCaml())); - - return $@"{sb}"; - } - - private string GetGroupByCaml() - { - if (groupByFields.Count == 0) - { - return string.Empty; - } - - var sb = new StringBuilder(); - groupByFields.ForEach(g => sb.AppendLine(g.GetCaml())); - - return $@"{sb}"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Statement.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Statement.cs deleted file mode 100644 index d16a2f625f..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Statement.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Defines a CAML statement. It can be a or a . - /// - internal abstract class Statement - { - public abstract string GetCaml(); - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Value.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Value.cs deleted file mode 100644 index f54249ebe1..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/Value.cs +++ /dev/null @@ -1,223 +0,0 @@ -namespace CamlBuilder -{ - using System.Collections.Generic; - using System.Text; - using Internal.Values; - - /// - /// Defines a CAML value. This class has no constructors available.To instanciate a - /// new value use public static methods. - /// - internal abstract class Value - { - /// - /// Specifies the data type for the value contained by this element. - /// - public ValueType Type { get; } - - /// - /// Specifies to build DateTime queries based on time as well as date. If you do not set - /// this attribute, the time portion of queries that involve date and time are ignored. - /// - public bool? IncludeTimeValue { get; set; } - - protected internal Value(ValueType type) - : this(type, null) - { - } - - protected internal Value(ValueType type, bool? includeTimeValue) - { - Type = type; - IncludeTimeValue = includeTimeValue; - } - - internal string GetCaml() - { - var sb = new StringBuilder(); - - if (IncludeTimeValue.HasValue) - { - sb.Append($""); - } - else - { - sb.Append($""); - } - - sb.Append(GetCamlValue()); - sb.Append(""); - - return sb.ToString(); - } - - protected abstract string GetCamlValue(); - - /// - /// Value representng the current date and time. - /// - /// Value representng the current date and time. - public static Value Now() - { - return new NowValue(null); - } - - /// - /// Value representing the current date and time. - /// - /// True if is to be included the time part; otherwise, false - /// Value representng the current date and time. - public static Value Now(bool includeTimeValue) - { - return new NowValue(includeTimeValue); - } - - /// - /// Value representing the current month. - /// - /// Can be used in together with operator - /// to retrieve from a calendar all instances of a recurring event that occur within a month. - /// - /// Value representing the current month. - public static Value Month() - { - return new MonthValue(null); - } - - /// - /// Can be used in together with operator - /// to retrieve from a calendar all instances of a recurring event that occur within a month. - /// - /// True if is to be included the time part; otherwise, false - /// Value representing the current month. - public static Value Month(bool includeTimeValue) - { - return new MonthValue(includeTimeValue); - } - - /// - /// Value representing the current day. - /// - /// Renders the current date in the format that is relative to the server's local time zone. For servers in - /// the United States, the format is MM/DD/YYYY (for example, 1/21/2001). - /// - /// Value representing the current day. - public static Value Today() - { - return new TodayValue(null, null); - } - - /// - /// Value representing the current day. - /// - /// Renders the current date in the format that is relative to the server's local time zone. For servers in - /// the United States, the format is MM/DD/YYYY (for example, 1/21/2001). - /// - /// Adds or subtracts the number of days that are specified by the positive or negative integer value. - /// Value representing the current day. - public static Value Today(int offset) - { - return new TodayValue(null, offset); - } - - /// - /// Value representing the current day. - /// - /// Renders the current date in the format that is relative to the server's local time zone. For servers in - /// the United States, the format is MM/DD/YYYY (for example, 1/21/2001). - /// - /// True if is to be included the time part; otherwise, false - /// Value representing the current day. - public static Value Today(bool includeTimeValue) - { - return new TodayValue(includeTimeValue, null); - } - - /// - /// Value representing the current day. - /// - /// Renders the current date in the format that is relative to the server's local time zone. For servers in - /// the United States, the format is MM/DD/YYYY (for example, 1/21/2001). - /// - /// True if is to be included the time part; otherwise, false - /// Adds or subtracts the number of days that are specified by the positive or negative integer value. - /// Value representing the current day. - public static Value Today(bool includeTimeValue, int offset) - { - return new TodayValue(includeTimeValue, offset); - } - - /// - /// Can be used to represent any of value. - /// - /// Specifies the data type for the value contained by this element. - /// Value against which the value returned by the FieldRef element is compared - /// Value representing any object value. - /// - /// CamlBuilder uses ToString() on top of to build the final CAML query. - /// - public static Value ObjectValue(ValueType type, object value) - { - return new AnyValue(type, null, value); - } - - /// - /// Can be used to represent any of value. - /// - /// Specifies the data type for the value contained by this element. - /// - /// Specifies to build DateTime queries based on time as well as date. If you set this to null - /// the time portion of queries that involve date and time are ignored. - /// - /// Value against which the value returned by the FieldRef element is compared - /// Value representing any object value. - /// - /// CamlBuilder uses ToString() on top of to build the final CAML query. - /// - public static Value ObjectValue(ValueType type, bool? includeTimeValue, object value) - { - return new AnyValue(type, includeTimeValue, value); - } - - /// - /// Contains the value if the unique ID number of the currently authenticated user of a site, as - /// defined in the UserInfo table of the content database. - /// - /// Value representing the currently authenticated user unique ID number. - public static Value UserId() - { - return new UserIdValue(); - } - - /// - /// Value of a specified column in the List of Lists table. - /// - /// Specifies the data type for the value contained by this element. - /// List of properties. - /// Value representing a list of lists table. - public static Value ListProperties( - ValueType type, - IEnumerable listProperties) - { - return new ListPropertyValue(type, null, listProperties); - } - - /// - /// Value of a specified column in the List of Lists table. - /// - /// Specifies the data type for the value contained by this element. - /// - /// Specifies to build DateTime queries based on time as well as date. If you set this to null - /// the time portion of queries that involve date and time are ignored. - /// - /// List of properties. - /// Value representing a list of lists table. - public static Value ListProperties( - ValueType type, - bool? includeTimeValue, - IEnumerable listProperties) - { - return new ListPropertyValue(type, includeTimeValue, listProperties); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ValueType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ValueType.cs deleted file mode 100644 index 8b645cb8a7..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/ValueType.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace CamlBuilder -{ - /// - /// Specifies types of reference for a field in a list. - /// - internal enum ValueType - { - /// - /// Indicates a Text field type - /// - Text, - - /// - /// Indicates a DateTime field type - /// - DateTime, - - /// - /// Indicates a Integer field type - /// -#pragma warning disable CA1720 - Integer, -#pragma warning restore CA1720 - /// - /// Indicates a Note field type - /// - Note, - - /// - /// Indicates a Choice field type - /// - Choice, - - /// - /// Indicates a Number field type - /// - Number, - - /// - /// Indicates a Guid field type - /// -#pragma warning disable CA1720 - Guid, -#pragma warning restore CA1720 - /// - /// Indicates a Boolean field type - /// - Boolean, - - /// - /// Indicates a Counter field type - /// - Counter, - - /// - /// Indicates a Currency field type - /// - Currency, - - /// - /// Indicates an URL field type - /// - Url, - /// - /// Indicates a Computed field type - /// - Computed, - - /// - /// Indicates a Lookup field type - /// - Lookup, - - /// - /// Indicates a File field type - /// - File, - - /// - /// Indicates an User field type - /// - User, - - /// - /// Indicates an Attachments field type - /// - Attachments, - - /// - /// Indicates a MultiChoice field type - /// - MultiChoice, - - /// - /// Indicates a GridChoice field type - /// - GridChoice, - - /// - /// Indicates a Threading field type - /// - Threading, - - /// - /// Indicates a CrossProjectLink field type - /// - CrossProjectLink, - - /// - /// Indicates a Recurrence field type - /// - Recurrence, - - /// - /// Indicates a ModStat field type - /// - ModStat, - - /// - /// Indicates a ContentTypeId field type - /// - ContentTypeId, - - /// - /// Indicates a WorkflowStatus field type - /// - WorkflowStatus, - - /// - /// Indicates a AllDayEvent field type - /// - AllDayEvent, - - /// - /// Indicates an Error field type - /// - Error, - - /// - /// Indicates a WorkflowEventType field type - /// - WorkflowEventType - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/credits.txt b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/credits.txt deleted file mode 100644 index 7408b1beef..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/Caml/credits.txt +++ /dev/null @@ -1 +0,0 @@ -Code in this folder is a modified version from https://github.com/joaope/camlbuilder. \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/ContentByQuerySearchTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/ContentByQuerySearchTransformator.cs deleted file mode 100644 index 44a083aad0..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/ContentByQuerySearchTransformator.cs +++ /dev/null @@ -1,2140 +0,0 @@ -using CamlBuilder; -using Microsoft.SharePoint.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PnP.Core.Transformation.SharePoint.Extensions; -using PnP.Core.Transformation.SharePoint.KQL; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - #region Highlighted content properties model - - internal enum ContentRollupLayout - { - Card = 1, - List = 2, - Carousel = 3, - FilmStrip = 4, - Masonry = 5, - Custom = 999 - } - - internal class ContentRollupWebPartProperties - { - [JsonProperty(PropertyName = "displayMaps")] - public string DisplayMaps { get; set; } // will be populated from json string - [JsonProperty(PropertyName = "query")] - public SearchQuery Query { get; set; } - [JsonProperty(PropertyName = "listId")] - public string ListId { get; set; } // selected list to view the contents of - [JsonProperty(PropertyName = "lastListId")] - public string LastListId { get; set; } // used to detect list view selection changes - [JsonProperty(PropertyName = "listTitle")] - public string ListTitle { get; set; } // selected document library to view the contents of - [JsonProperty(PropertyName = "isDefaultDocumentLibrary")] - public bool? IsDefaultDocumentLibrary { get; set; } - [JsonProperty(PropertyName = "documentLibrarySortField")] - public string DocumentLibrarySortField { get; set; } - [JsonProperty(PropertyName = "caml")] - public string Caml { get; set; } - [JsonProperty(PropertyName = "templateId")] - public ContentRollupLayout? TemplateId { get; set; } - [JsonProperty(PropertyName = "maxItemsPerPage")] - public int? MaxItemsPerPage { get; set; } - [JsonProperty(PropertyName = "isSeeAllPage")] - public bool? IsSeeAllPage { get; set; } - [JsonProperty(PropertyName = "isAdvancedFilterMode")] - public bool? IsAdvancedFilterMode { get; set; } - [JsonProperty(PropertyName = "sites")] - public List Sites { get; set; } // The list of sites metadata added by user as content source - // IBaseCollectionWebPartProperties props - [JsonProperty(PropertyName = "layoutId")] // Set when there are more than one layouts to indicate which layout is in use. - public string LayoutId { get; set; } - [JsonProperty(PropertyName = "dataProviderId")] // Set when there are more than one data providers to indicate which data provider is in use. - public string DataProviderId { get; set; } - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } - [JsonProperty(PropertyName = "addToPageScreenReaderLabel")] - public string AddToPageScreenReaderLabel { get; set; } - [JsonProperty(PropertyName = "hideWebPartWhenEmpty")] - public bool? HideWebPartWhenEmpty { get; set; } - [JsonProperty(PropertyName = "webId")] - public string WebId { get; set; } - [JsonProperty(PropertyName = "siteId")] - public string SiteId { get; set; } - [JsonProperty(PropertyName = "baseUrl")] - public string BaseUrl { get; set; } // base url to locate web part resources such has file icons - [JsonProperty(PropertyName = "queryMode")] - public string QueryMode { get; set; } - [JsonProperty(PropertyName = "audienceTarget")] - public bool AudienceTarget { get; set; } - - public ContentRollupWebPartProperties() - { - this.DisplayMaps = "%%DisplayMapsPlaceholder%%"; - } - - } - - #region ISearchQuery enums and classes - - internal enum ContentLocation - { - CurrentSite = 1, - CurrentSiteCollection = 2, - AllSites = 3, - CurrentSiteDocumentLibrary = 4, - AllSitesInTheHub = 5, - CurrentSitePageLibrary = 6, - SelectedSites = 99 - } - - internal enum DocumentType - { - Word = 1, - Excel = 2, - PowerPoint = 3, - OneNote = 4, - Visio = 5, - PDF = 10, - Any = 99 - } - - internal enum FilterType - { - TitleContaining = 1, // title like "*value*" - AnyTextContaining = 2, // any field like "*value* - TaggedWith = 3, // value in list (tags) - CreatedBy = 4, // created by = value - ModifiedBy = 5, // created by = value - Field = 6, // field operator value(s) - RecentlyChanged = 7, - RecentlyAdded = 8 - } - - internal enum ContentType - { - Document = 1, - Page = 2, - Video = 3, - Event = 4, - Issue = 5, - Task = 6, - Link = 7, - Contact = 8, - Image = 9, - News = 10, - All = 99 - } - - internal enum UserType - { - CurrentUser = 1, //Indicates user is the current session user. - SpecificUser = 2 //Indicates user is who match the given match text (e.g. name). - } - - internal enum FilterOperator - { - Equals = 1, - NotEqual = 2, - BeginsWith = 3, - EndsWith = 4, - Contains = 5, - DoesNotContain = 6, - LessThan = 7, - GreaterThan = 8, - Between = 9 - } - - internal enum SortType - { - MostRecent = 1, - MostViewed = 2, - Trending = 3, - FieldAscending = 4, - FieldDescending = 5 - } - - internal class ManagedPropertiesRefinerOptions - { - [JsonProperty(PropertyName = "number")] - public int? Number { get; set; } // How many unique managed properties to return - [JsonProperty(PropertyName = "managedPropertyMatchText")] - public string ManagedPropertyMatchText { get; set; } // Text to filter managed properties by name - } - - internal class SearchQueryFilter - { - [JsonProperty(PropertyName = "filterType")] - public FilterType? FilterType { get; set; } - [JsonProperty(PropertyName = "userType")] - public UserType? UserType { get; set; } - [JsonProperty(PropertyName = "fieldNameMatchText")] - public string FieldNameMatchText { get; set; } - [JsonProperty(PropertyName = "lastFieldNameMatchText")] - public string LastFieldNameMatchText { get; set; } - [JsonProperty(PropertyName = "fieldname")] - public string Fieldname { get; set; } - [JsonProperty(PropertyName = "op")] - public FilterOperator? Op { get; set; } - [JsonProperty(PropertyName = "value")] - public object Value { get; set; } - [JsonProperty(PropertyName = "value2")] - public object Value2 { get; set; } - [JsonProperty(PropertyName = "values")] - public List Values { get; set; } - [JsonIgnore] - public FilterChainingOperator? ChainingOperatorUsedInCQWP { get; set; } - - public SearchQueryFilter() - { - this.Values = new List(); - } - - } - - internal class SearchQuery - { - [JsonProperty(PropertyName = "advancedQueryText")] - public string AdvancedQueryText { get; set; } - [JsonProperty(PropertyName = "contentLocation")] - public ContentLocation? ContentLocation { get; set; } // search scope - [JsonProperty(PropertyName = "contentTypes")] // content types to include in query result - public List ContentTypes { get; set; } - [JsonProperty(PropertyName = "documentTypes")] - public List DocumentTypes { get; set; } - [JsonProperty(PropertyName = "filters")] - public List Filters { get; set; } - [JsonProperty(PropertyName = "sortField")] - public string SortField { get; set; } - [JsonProperty(PropertyName = "sortFieldMatchText")] - public string SortFieldMatchText { get; set; } - [JsonProperty(PropertyName = "sortType")] - public SortType? SortType { get; set; } - [JsonProperty(PropertyName = "managedPropertiesRefinerOptions")] - public ManagedPropertiesRefinerOptions ManagedPropertiesRefinerOptions { get; set; } - - public SearchQuery() - { - this.ContentTypes = new List(); - this.DocumentTypes = new List(); - this.Filters = new List(); - } - } - - #endregion - - #region ISiteMetadata enums and classes - - internal class SiteReference - { - [JsonProperty(PropertyName = "WebId")] - public string WebId { get; set; } - [JsonProperty(PropertyName = "IndexId")] - public long IndexId { get; set; } - [JsonProperty(PropertyName = "ExchangeId")] - public string ExchangeId { get; set; } // eg SPO_M2NlYjY4MDctYTZlZS00NDNjLWE4M2ItZWIyNjM2YzYyZTgyLGU... - [JsonProperty(PropertyName = "Source")] - public string Source { get; set; } //'Users' | string; - [JsonProperty(PropertyName = "SiteId")] - public string SiteId { get; set; } - [JsonProperty(PropertyName = "Type")] - public string Type { get; set; } //'SiteReference' | 'GroupReference' | string; - [JsonProperty(PropertyName = "GroupId")] - public string GroupId { get; set; } // The id of the group when site reference type is 'GroupReference, It's not available for other site reference types - } - - internal class SiteMetadata - { - [JsonProperty(PropertyName = "Acronym")] - public string Acronym { get; set; } // The acronym of the site. Used in banner image if the banner image url is not available - //[JsonProperty(PropertyName = "title")] - //public string Title { get; set; } // Site title - [JsonProperty(PropertyName = "BannerColor")] - public string BannerColor { get; set; } // The color represents the site theme - [JsonProperty(PropertyName = "bannerImageUrl")] - public string BannerImageUrl { get; set; } // The url of the site logo - //[JsonProperty(PropertyName = "url")] - //public string Url { get; set; } // The url points to the site - [JsonProperty(PropertyName = "Type")] - public string Type { get; set; } //Type of the site. E.g. 'Site', 'Group'. - [JsonProperty(PropertyName = "ItemReference")] - public SiteReference ItemReference { get; set; } // Identifiers of the site - } - - #endregion - - #endregion - - #region ContentByQuery model - - internal enum SortDirection - { - Asc, - Desc - } - - internal enum FilterChainingOperator - { - /// - /// Filter is chained using an And operator - /// - And, - - /// - /// Filter is chained using an Or operator - /// - Or - } - - internal enum FilterFieldQueryOperator - { - /// - /// Equal to - /// - Eq, - - /// - /// Not equal to - /// - Neq, - - /// - /// Greater than - /// - Gt, - - /// - /// Greater than or equal to - /// - Geq, - - /// - /// Less than - /// - Lt, - - /// - /// Less than or equal to - /// - Leq, - - /// - /// Begins with - /// - BeginsWith, - - /// - /// Contains - /// - Contains, - - /// - /// Contains any of - /// - ContainsAny, - - /// - /// Contains all of - /// - ContainsAll - } - - internal class ContentByQuery - { - // query scope - - // ~sitecollection, ~site, ~sitecollection/sub1 to indicate scope from a given subsite or empty (= current site) - public string WebUrl { get; set; } - // will be set whenever the query is scoped to a list in the current web - public string ListName { get; set; } - // will be set whenever the query is scoped to a list in the current web - public string ListGuid { get; set; } - // contains list template which we're filtering on (e.g. 119 for wiki list). Must be set! - public string ServerTemplate { get; set; } - // contains prefix for content type filtering (e.g. 0x010108 for all wiki page content types and descendants) - public string ContentTypeBeginsWithId { get; set; } - - // extra filters - - // e.g.: {8553196d-ec8d-4564-9861-3dbe931050c8} - public string FilterField1 { get; set; } - // e.g.: d - public string FilterField1Value { get; set; } - // e.g.: BeginsWith - public FilterFieldQueryOperator FilterOperator1 { get; set; } - public FilterChainingOperator Filter1ChainingOperator { get; set; } - public string FilterField2 { get; set; } - public string FilterField2Value { get; set; } - public FilterFieldQueryOperator FilterOperator2 { get; set; } - public FilterChainingOperator Filter2ChainingOperator { get; set; } - public string FilterField3 { get; set; } - public string FilterField3Value { get; set; } - public FilterFieldQueryOperator FilterOperator3 { get; set; } - //public FilterChainingOperator Filter3ChainingOperator { get; set; } - - // sorting & grouping - - // e.g. {8553196d-ec8d-4564-9861-3dbe931050c8} - public string SortBy { get; set; } - public SortDirection SortByDirection { get; set; } - // e.g. {8553196d-ec8d-4564-9861-3dbe931050c8} - public string GroupBy { get; set; } - public SortDirection GroupByDirection { get; set; } - - // number of items to display (e.g. 15) - public int ItemLimit { get; set; } - - // number of columns used to present the data (e.g. 1) - public int DisplayColumns { get; set; } - - //DataMappings (= fields used for the respective Description, ImageUrl, Title and LinkUrl slots) E.g.: - // - //Description:{691b9a4b-512e-4341-b3f1-68914130d5b2},ShortComment,Text;| - //ImageUrl:{b9e6f3ae-5632-4b13-b636-9d1a2bd67120},EncodedAbsThumbnailUrl,Computed;{543bc2cf-1f30-488e-8f25-6fe3b689d9ac},PublishingRollupImage,Image;| - //Title:{fa564e0f-0c70-4ab9-b863-0177e6ddd247},Title,Text;| - //LinkUrl:{94f89715-e097-4e8b-ba79-ea02aa8b7adb},FileRef,Lookup;| - public string DataMappings { get; set; } - - } - - #endregion - - #region ContentBySearch model - - internal class ContentBySearch - { - public string DataProviderJson { get; set; } - public string SelectedPropertiesJson { get; set; } - public int ResultsPerPage { get; set; } - public string RenderTemplateId { get; set; } - } - - #endregion - - #region Result class - - internal class ContentByQuerySearchTransformatorResult - { - public string Properties { get; set; } - public string SearchablePlainTexts { get; set; } - public string ImageSources { get; set; } - public string Links { get; set; } - } - - #endregion - - /// - /// Class used to generate contentrollup (=highlighted content) web part properties coming from either a content by query or content by search web part - /// - internal class ContentByQuerySearchTransformator - { - private ContentRollupWebPartProperties properties; - private ClientContext clientContext; - private const string displayMapJson = "{\"1\":{\"headingText\":{\"sources\":[\"SiteTitle\"]},\"headingUrl\":{\"sources\":[\"SitePath\"]},\"title\":{\"sources\":[\"UserName\",\"Title\"]},\"personImageUrl\":{\"sources\":[\"ProfileImageSrc\"]},\"name\":{\"sources\":[\"Name\"]},\"initials\":{\"sources\":[\"Initials\"]},\"itemUrl\":{\"sources\":[\"WebPath\"]},\"activity\":{\"sources\":[\"ModifiedDate\"]},\"previewUrl\":{\"sources\":[\"PreviewUrl\",\"PictureThumbnailURL\"]},\"iconUrl\":{\"sources\":[\"IconUrl\"]},\"accentColor\":{\"sources\":[\"AccentColor\"]},\"cardType\":{\"sources\":[\"CardType\"]},\"tipActionLabel\":{\"sources\":[\"TipActionLabel\"]},\"tipActionButtonIcon\":{\"sources\":[\"TipActionButtonIcon\"]},\"className\":{\"sources\":[\"ClassName\"]}},\"2\":{\"column1\":{\"heading\":\"\",\"sources\":[\"FileExtension\"],\"width\":34},\"column2\":{\"heading\":\"Title\",\"sources\":[\"Title\"],\"linkUrls\":[\"WebPath\"],\"width\":250},\"column3\":{\"heading\":\"Modified\",\"sources\":[\"ModifiedDate\"],\"width\":100},\"column4\":{\"heading\":\"Modified By\",\"sources\":[\"Name\"],\"width\":150}},\"3\":{\"id\":{\"sources\":[\"UniqueID\"]},\"edit\":{\"sources\":[\"edit\"]},\"DefaultEncodingURL\":{\"sources\":[\"DefaultEncodingURL\"]},\"FileExtension\":{\"sources\":[\"FileExtension\"]},\"FileType\":{\"sources\":[\"FileType\"]},\"Path\":{\"sources\":[\"Path\"]},\"PictureThumbnailURL\":{\"sources\":[\"PictureThumbnailURL\"]},\"SiteID\":{\"sources\":[\"SiteID\"]},\"SiteTitle\":{\"sources\":[\"SiteTitle\"]},\"Title\":{\"sources\":[\"Title\"]},\"UniqueID\":{\"sources\":[\"UniqueID\"]},\"WebId\":{\"sources\":[\"WebId\"]},\"WebPath\":{\"sources\":[\"WebPath\"]},\"PreviewUrl\":{\"sources\":[\"PreviewUrl\"]}},\"4\":{\"headingText\":{\"sources\":[\"SiteTitle\"]},\"headingUrl\":{\"sources\":[\"SitePath\"]},\"title\":{\"sources\":[\"UserName\",\"Title\"]},\"personImageUrl\":{\"sources\":[\"ProfileImageSrc\"]},\"name\":{\"sources\":[\"Name\"]},\"initials\":{\"sources\":[\"Initials\"]},\"itemUrl\":{\"sources\":[\"WebPath\"]},\"activity\":{\"sources\":[\"ModifiedDate\"]},\"previewUrl\":{\"sources\":[\"PreviewUrl\",\"PictureThumbnailURL\"]},\"iconUrl\":{\"sources\":[\"IconUrl\"]},\"accentColor\":{\"sources\":[\"AccentColor\"]},\"cardType\":{\"sources\":[\"CardType\"]},\"tipActionLabel\":{\"sources\":[\"TipActionLabel\"]},\"tipActionButtonIcon\":{\"sources\":[\"TipActionButtonIcon\"]},\"className\":{\"sources\":[\"ClassName\"]}}}"; - private List queryFields; - - #region Construction - - /// - /// Instantiates the class - /// - /// Client context for the web holding the source page - public ContentByQuerySearchTransformator(ClientContext cc) - { - this.clientContext = cc; - this.properties = new ContentRollupWebPartProperties(); - this.queryFields = new List(); - - cc.Web.EnsureProperties(p => p.Id, p => p.Url); - cc.Site.EnsureProperties(p => p.Id, p => p.RootWeb); - cc.Site.RootWeb.EnsureProperty(p => p.Url); - - // Default initialization of the configuration - this.properties.WebId = cc.Web.Id.ToString(); - this.properties.SiteId = cc.Site.Id.ToString(); - this.properties.MaxItemsPerPage = 8; - this.properties.HideWebPartWhenEmpty = false; - this.properties.Sites = new List(); - } - - #endregion - - /// - /// Generate contentrollup (=highlighted content) web part properties coming from a content by search web part - /// - /// Properties for highlighted content web part - public ContentByQuerySearchTransformatorResult TransformUserDocuments() - { - ContentByQuerySearchTransformatorResult result = new ContentByQuerySearchTransformatorResult() - { - Properties = "", - ImageSources = "", - SearchablePlainTexts = "", - Links = "" - }; - - // Set basic site properties - this.properties.DataProviderId = "Search"; - this.properties.MaxItemsPerPage = 8; - this.properties.TemplateId = ContentRollupLayout.Card; - - // construct query - SearchQuery query = new SearchQuery - { - ContentLocation = MapToContentLocation(this.clientContext.Web.EnsureProperty(p => p.Url)), - }; - - query.ContentTypes.Add(ContentType.Page); - query.DocumentTypes.Add(DocumentType.Any); - - // scope to pages modified by the current user - query.Filters.Add(new SearchQueryFilter() - { - FilterType = FilterType.ModifiedBy, - UserType = UserType.CurrentUser, - }); - - query.AdvancedQueryText = ""; - - // assign query - this.properties.Query = query; - - // Prep output - result.Properties = HighlightedContentProperties(); - - // Return the json properties for the converted web part - return result; - } - - /// - /// Generate contentrollup (=highlighted content) web part properties coming from a content by search web part - /// - /// Properties coming from the content by search web part - /// Properties for highlighted content web part - public ContentByQuerySearchTransformatorResult TransformContentBySearchWebPartToHighlightedContent(ContentBySearch cbs) - { - ContentByQuerySearchTransformatorResult result = new ContentByQuerySearchTransformatorResult() - { - Properties = "", - ImageSources = "", - SearchablePlainTexts = "", - Links = "" - }; - - // Set basic site properties - this.properties.DataProviderId = "Search"; - - if (cbs.ResultsPerPage < 1) - { - cbs.ResultsPerPage = 1; - } - this.properties.MaxItemsPerPage = cbs.ResultsPerPage; - - // Load the Json - var dataProviderJson = JObject.Parse(cbs.DataProviderJson); - - // Determine needed content location - ContentLocation contentLocation = ContentLocation.CurrentSiteCollection; - if (dataProviderJson["Properties"] != null && IsPropertyAvailable(dataProviderJson["Properties"]["Scope"])) - { - string scope = GetPropertyAsString(dataProviderJson["Properties"]["Scope"]); - - if (!string.IsNullOrEmpty(scope)) - { - this.clientContext.Site.RootWeb.EnsureProperty(w => w.Url); - - // replace site collection url by ~sitecollection so we can reuse the existing logic - scope = scope.Replace(this.clientContext.Site.RootWeb.Url, "~sitecollection"); - - // TODO: seems like CSWP scopes to site and sub sites while the Site option in HCWP scopes to just the site - //if (scope.Equals("{Site.URL}", StringComparison.InvariantCultureIgnoreCase)) - //{ - // scope = "~site"; - //} - - // determine location - contentLocation = MapToContentLocation(scope); - } - - if (contentLocation == ContentLocation.SelectedSites) - { - // Fill the needed structure to scope the search query to a sub site - var parts = scope.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); - var subSiteUrl = parts[1]; - - var siteMetadata = MapToSiteMetadata(subSiteUrl, ref result); - - if (siteMetadata != null) - { - this.properties.Sites.Add(siteMetadata); - } - else - { - // something went wrong, fall back to full site collection - contentLocation = ContentLocation.CurrentSiteCollection; - } - } - } - - // construct query - SearchQuery query = new SearchQuery - { - // Libraries always equal to this - ContentLocation = contentLocation - }; - - // Layout properties - if (!string.IsNullOrEmpty(cbs.RenderTemplateId) && cbs.RenderTemplateId.ToLower().Contains("control_slideshow.js")) - { - SetLayoutTemplate(ContentRollupLayout.Carousel); - } - else - { - SetLayoutTemplate(ContentRollupLayout.Card); - } - - // There's no document type filtering in CSWP - query.DocumentTypes.Add(DocumentType.Any); - - var queryTemplate = GetPropertyAsString(dataProviderJson["QueryTemplate"]); - var sourceName = GetPropertyAsString(dataProviderJson["SourceName"]); - - string contentTypeId = null; - if (dataProviderJson["Properties"] != null && IsPropertyAvailable(dataProviderJson["Properties"]["ContentTypeId"])) - { - contentTypeId = GetPropertyAsString(dataProviderJson["Properties"]["ContentTypeId"]); - } - - // Assign content types depending on the provided source and optional contenttypeid - query.ContentTypes.AddRange(MapToContentTypesFromResourceSource(sourceName, contentTypeId)); - - // Set sort type - default is MostRecent, but can be different depending on the choosen source - query.SortType = MapToSortTypeFromResourceSource(sourceName); - - // Handle query (queryTemplate) and refinement filters - if (!string.IsNullOrEmpty(queryTemplate) || (dataProviderJson["FallbackRefinementFilters"] != null && dataProviderJson["FallbackRefinementFilters"].HasValues)) - { - MapSearchQueryToFilters(ref query, queryTemplate, dataProviderJson["FallbackRefinementFilters"]); - } - - // Keep the search query if there was one set in the web part as that allows to retain the same result set - if (!string.IsNullOrEmpty(queryTemplate)) - { - query.AdvancedQueryText = $"{queryTemplate}"; - this.properties.QueryMode = "Advanced"; - } - else - { - this.properties.QueryMode = "Basic"; - } - - // assign query - this.properties.Query = query; - - // Prep output - result.Properties = HighlightedContentProperties(); - - // Return the json properties for the converted web part - return result; - } - - /// - /// Generate contentrollup (=highlighted content) web part properties coming from a content by query web part - /// - /// Properties coming from the content by query web part - /// Properties for highlighted content web part - public ContentByQuerySearchTransformatorResult TransformContentByQueryWebPartToHighlightedContent(ContentByQuery cbq) - { - // Transformation logic - ContentByQuerySearchTransformatorResult result = new ContentByQuerySearchTransformatorResult() - { - ImageSources = "", - SearchablePlainTexts = "", - Links = "" - }; - - // Scoped to list? - if (!Guid.TryParse(cbq.ListGuid, out Guid listId)) - { - listId = Guid.Empty; - } - - if (!string.IsNullOrEmpty(cbq.ListName) || listId != Guid.Empty) - { - // Scope to list - List list = null; - if (listId != Guid.Empty) - { - list = this.clientContext.Web.GetListById(listId); - } - else - { - list = this.clientContext.Web.GetListByTitle(cbq.ListName); - } - - var defaultDocLib = this.clientContext.Web.DefaultDocumentLibrary(); - this.clientContext.Load(defaultDocLib, p => p.Id); - this.clientContext.Load(list, p => p.BaseType, p => p.Title, p => p.Id, p => p.Fields); - this.clientContext.ExecuteQueryRetry(); - - // Set basic list properties - this.properties.ListId = list.Id.ToString(); - this.properties.LastListId = this.properties.ListId; - this.properties.ListTitle = list.Title; - this.properties.DataProviderId = "List"; - - this.properties.IsDefaultDocumentLibrary = defaultDocLib.Id.Equals(list.Id); - - // TODO: verify upper limit bound - if (cbq.ItemLimit < 1) - { - cbq.ItemLimit = 1; - } - this.properties.MaxItemsPerPage = cbq.ItemLimit; - - // Layout properties - if (cbq.DisplayColumns > 1) - { - SetLayoutTemplate(ContentRollupLayout.Card); - } - else - { - SetLayoutTemplate(ContentRollupLayout.List); - } - - // construct query - SearchQuery query = new SearchQuery(); - - if (list.BaseTemplate == (int)ListTemplateType.WebPageLibrary) - { - // for SitePages library we only support the Page content type, document types are not relevant here - query.ContentLocation = ContentLocation.CurrentSitePageLibrary; - query.ContentTypes.Add(ContentType.Page); - } - else - { - query.ContentLocation = ContentLocation.CurrentSiteDocumentLibrary; - // There's no document type filtering in CWQP - query.DocumentTypes.Add(DocumentType.Any); - // Map contenttypeid to 'default' content types if possible - query.ContentTypes.AddRange(MapToContentTypesFromContentType(cbq.ContentTypeBeginsWithId)); - } - - - // Filtering - var filter1 = MapToFilter(list, cbq.FilterOperator1, cbq.FilterField1, cbq.FilterField1Value, FilterChainingOperator.And); - if (filter1 != null) - { - query.Filters.Add(filter1); - } - - var filter2 = MapToFilter(list, cbq.FilterOperator2, cbq.FilterField2, cbq.FilterField2Value, cbq.Filter1ChainingOperator); - if (filter2 != null) - { - query.Filters.Add(filter2); - } - - var filter3 = MapToFilter(list, cbq.FilterOperator3, cbq.FilterField3, cbq.FilterField3Value, cbq.Filter2ChainingOperator); - if (filter3 != null) - { - query.Filters.Add(filter3); - } - - query.AdvancedQueryText = ""; - - // Set sort field - // Possible sort fields are: Title, FileLeafRef, Author and Modified. Sort direction is always the same (Ascending=\"true\") except for sorting on Modified - if (!string.IsNullOrEmpty(cbq.SortBy)) - { - if (cbq.SortBy.Equals("Modified") || cbq.SortBy.Equals("Title") || cbq.SortBy.Equals("FileLeafRef") || cbq.SortBy.Equals("Author")) - { - this.properties.DocumentLibrarySortField = cbq.SortBy; - } - else - { - // Fall back to default if the original CBQ uses sorting that was not allowed - this.properties.DocumentLibrarySortField = "Modified"; - } - } - - // assign query - this.properties.Query = query; - - if (this.properties.Query.Filters.Any()) - { - // Use the advanced query mode when we set the Caml query ourselves, this allows for more complex operations with freedom of choosing AND/OR operators - query.AdvancedQueryText = CamlQueryBuilder(list, cbq); - this.properties.QueryMode = "Advanced"; - this.properties.Caml = ""; - } - } - else - { - // scope to site(s) - - // Set basic site properties - this.properties.DataProviderId = "Search"; - - // TODO: verify upper limit bound - if (cbq.ItemLimit < 1) - { - cbq.ItemLimit = 1; - } - this.properties.MaxItemsPerPage = cbq.ItemLimit; - - // Layout properties - if (cbq.DisplayColumns > 1) - { - SetLayoutTemplate(ContentRollupLayout.Card); - } - else - { - SetLayoutTemplate(ContentRollupLayout.List); - } - - var contentLocation = MapToContentLocation(cbq.WebUrl); - - if (contentLocation == ContentLocation.SelectedSites) - { - // Fill the needed structure to scope the search query to a sub site - var parts = cbq.WebUrl.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); - var subSiteUrl = parts[1]; - - var siteMetadata = MapToSiteMetadata(subSiteUrl, ref result); - - if (siteMetadata != null) - { - this.properties.Sites.Add(siteMetadata); - } - else - { - // something went wrong, fall back to full site collection - contentLocation = ContentLocation.CurrentSiteCollection; - } - } - - // construct query - SearchQuery query = new SearchQuery - { - // Libraries always equal to this - ContentLocation = contentLocation - }; - - // There's no document type filtering in CWQP - query.DocumentTypes.Add(DocumentType.Any); - - // Map contenttypeid to 'default' content types if possible - if (!string.IsNullOrEmpty(cbq.ServerTemplate)) - { - query.ContentTypes.AddRange(MapToContentTypesFromListTemplate(cbq.ServerTemplate)); - } - else - { - query.ContentTypes.AddRange(MapToContentTypesFromContentType(cbq.ContentTypeBeginsWithId)); - } - - if (!string.IsNullOrEmpty(cbq.FilterField1) || !string.IsNullOrEmpty(cbq.FilterField2) || !string.IsNullOrEmpty(cbq.FilterField3)) - { - // Process CBQ filters - var filter1 = MapToFilter(null, cbq.FilterOperator1, cbq.FilterField1, cbq.FilterField1Value, FilterChainingOperator.And); - if (filter1 != null) - { - query.Filters.Add(filter1); - } - - var filter2 = MapToFilter(null, cbq.FilterOperator2, cbq.FilterField2, cbq.FilterField2Value, cbq.Filter1ChainingOperator); - if (filter2 != null) - { - query.Filters.Add(filter2); - } - - var filter3 = MapToFilter(null, cbq.FilterOperator3, cbq.FilterField3, cbq.FilterField3Value, cbq.Filter2ChainingOperator); - if (filter3 != null) - { - query.Filters.Add(filter3); - } - } - else - { - // Add default filter element (needed to show up the filters pane in the web part) - query.Filters.Add(new SearchQueryFilter() - { - FilterType = FilterType.TitleContaining, - Value = "", - }); - } - - // Set sort field - // Possible sort by's are: Most recent, Most viewed, Trending, Managed property ascending, Managed property descending - if (!string.IsNullOrEmpty(cbq.SortBy)) - { - var sortField = MapToSort(cbq.SortBy); - if (sortField != null) - { - query.SortField = sortField; - query.SortFieldMatchText = sortField; - query.SortType = cbq.SortByDirection == SortDirection.Asc ? SortType.FieldAscending : SortType.FieldDescending; - } - } - else - { - // Set sort type - default is MostRecent - query.SortType = SortType.MostRecent; - } - - query.AdvancedQueryText = ""; - this.properties.QueryMode = "Basic"; - - // assign query - this.properties.Query = query; - } - - // Prep output - result.Properties = HighlightedContentProperties(); - - // Return the json properties for the converted web part - return result; - } - - #region Helper methods - - #region CAML Query Builder - - private string CamlQueryBuilder(List list, ContentByQuery cbq) - { - // Copy the CBQW filters - List filters = new List(); - filters.AddRange(this.properties.Query.Filters); - // Add the default filter - filters.Add(new SearchQueryFilter() - { - ChainingOperatorUsedInCQWP = FilterChainingOperator.And, - Fieldname = "FSObjType", - Op = FilterOperator.Equals, - Value = 0 - }); - - // Sorting: if CBQW was sorted on one of the 4 allowed fields then take over the setting, else fall back to default sort (= Modified) - string sortField = "Modified"; - if (!string.IsNullOrEmpty(cbq.SortBy)) - { - if (cbq.SortBy.Equals("Title") || cbq.SortBy.Equals("FileLeafRef") || cbq.SortBy.Equals("Author")) - { - sortField = cbq.SortBy; - } - } - - // Sort order cannot be choosen: Modified = descending, others are ascending - string sortOrder = "True"; - if (sortField == "Modified") - { - sortOrder = "False"; - } - - string query = ""; - Query queryCaml = null; - var and = LogicalJoin.And(); - var or = LogicalJoin.Or(); - - // Do we have filters to apply? - if (filters.Any()) - { - for (int i = 0; i < filters.Count; i++) - { - var queryFilter = filters[i]; - var nextQueryFilter = filters[i]; - if (i < filters.Count - 1) - { - nextQueryFilter = filters[i + 1]; - } - - if (queryFilter.ChainingOperatorUsedInCQWP == FilterChainingOperator.And && nextQueryFilter.ChainingOperatorUsedInCQWP == FilterChainingOperator.And) - { - and.AddStatement(CamlFilterBuilder(queryFilter)); - } - else - { - or.AddStatement(CamlFilterBuilder(queryFilter)); - } - } - - if (or.HasStatements()) - { - and.AddStatement(or); - } - } - - queryCaml = Query.Build(and); - query = queryCaml.GetCaml(true).Replace("\r", "").Replace("\n", ""); - return $"{query}{cbq.ItemLimit}"; - } - - private string CamlFilterValueBuilder(string fieldName, string fieldValue) - { - // default to Text - string value = fieldValue; - - var field = this.queryFields.Where(p => p.InternalName == fieldName).FirstOrDefault(); - if (field != null) - { - if (field.FieldTypeKind == FieldType.User) - { - if (fieldValue.Equals("[me]", StringComparison.InvariantCultureIgnoreCase)) - { - value = ""; - } - } - else if (field.FieldTypeKind == FieldType.DateTime && fieldValue.StartsWith("[today]", StringComparison.InvariantCultureIgnoreCase)) - { - string days = fieldValue.ToLower().Replace("[today]", ""); - - if (string.IsNullOrEmpty(days) || !int.TryParse(days, out int offset)) - { - offset = 0; - } - - value = $""; - } - } - - return value; - } - - private CamlBuilder.ValueType CamlFilterValueTypeBuilder(string fieldName, string fieldValue) - { - // default to Text - CamlBuilder.ValueType valueType = CamlBuilder.ValueType.Text; - - var field = this.queryFields.Where(p => p.InternalName == fieldName).FirstOrDefault(); - if (field != null) - { - if (field.FieldTypeKind == FieldType.User) - { - if (fieldValue.Equals("[me]", StringComparison.InvariantCultureIgnoreCase)) - { - valueType = CamlBuilder.ValueType.Integer; - } - else - { - valueType = CamlBuilder.ValueType.User; - } - } - else if (field.FieldTypeKind == FieldType.DateTime && fieldValue.StartsWith("[today]", StringComparison.InvariantCultureIgnoreCase)) - { - valueType = CamlBuilder.ValueType.DateTime; - } - } - - // Special case - if (fieldName == "FSObjType") - { - valueType = CamlBuilder.ValueType.Integer; - } - - return valueType; - } - - private Operator CamlFilterBuilder(SearchQueryFilter queryFilter) - { - if (queryFilter.Op == FilterOperator.BeginsWith) - { - return Operator.BeginsWith(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - else if (queryFilter.Op == FilterOperator.Contains) - { - return Operator.Contains(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - else if (queryFilter.Op == FilterOperator.Equals) - { - return Operator.Equal(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - else if (queryFilter.Op == FilterOperator.NotEqual) - { - return Operator.NotEqual(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - else if (queryFilter.Op == FilterOperator.GreaterThan) - { - return Operator.GreaterThan(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - else if (queryFilter.Op == FilterOperator.LessThan) - { - return Operator.LowerThan(queryFilter.Fieldname, CamlFilterValueTypeBuilder(queryFilter.Fieldname, queryFilter.Value.ToString()), CamlFilterValueBuilder(queryFilter.Fieldname, queryFilter.Value.ToString())); - } - - return null; - } - #endregion - - private static ContentLocation MapToContentLocation(string webUrl) - { - if (string.IsNullOrEmpty(webUrl) || webUrl.StartsWith("~sitecollection", StringComparison.InvariantCultureIgnoreCase)) - { - if (webUrl.StartsWith("~sitecollection", StringComparison.InvariantCultureIgnoreCase)) - { - var parts = webUrl.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length > 1 && parts[1] != "/") - { - return ContentLocation.SelectedSites; - } - } - else - { - return ContentLocation.CurrentSiteCollection; - } - } - else if (webUrl.Equals("~site", StringComparison.InvariantCultureIgnoreCase)) - { - return ContentLocation.CurrentSite; - } - - return ContentLocation.CurrentSiteCollection; - } - - private SiteMetadata MapToSiteMetadata(string subSiteUrl, ref ContentByQuerySearchTransformatorResult result) - { - SiteMetadata siteMetadata = new SiteMetadata(); - - try - { - this.clientContext.Site.EnsureProperties(p => p.Id, p => p.RootWeb); - this.clientContext.Site.RootWeb.EnsureProperties(p => p.Id, p => p.Url, p => p.ServerRelativeUrl); - - string subSiteUrlToClone = $"{this.clientContext.Site.RootWeb.Url}/{subSiteUrl}"; - using (var subSiteContext = this.clientContext.Clone(subSiteUrlToClone)) - { - subSiteContext.Web.EnsureProperties(p => p.Id, p => p.Url, p => p.ServerRelativeUrl, p => p.Title); - - // Prep site structure data - siteMetadata.Acronym = GetAcronym(subSiteContext.Web.Title); - siteMetadata.BannerColor = "#986f0b"; - siteMetadata.Type = "Site"; - siteMetadata.ItemReference = new SiteReference() - { - WebId = subSiteContext.Web.Id.ToString(), - //Source = "Users", - SiteId = this.clientContext.Site.Id.ToString(), - Type = "SiteReference", - }; - - // Prep data for in the ServerProcessedContent node - result.SearchablePlainTexts = $",\"sites[0].Title\": \"{subSiteContext.Web.Title}\""; - result.ImageSources = $"\"sites[0].BannerImageUrl\": null"; - result.Links = $",\"sites[0].Url\": \"{subSiteContext.Web.ServerRelativeUrl}\""; - } - - return siteMetadata; - } - catch (Exception) - { - return null; - } - } - - private static string GetAcronym(string text) - { - // return something if no input - if (string.IsNullOrEmpty(text)) - { - return "AB"; - } - - text = text.Trim(); - - // if we've less than 2 chars then return the text + something - if (text.Length < 2) - { - return $"{text}B"; - } - - var split = text.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - if (split.Length > 1) - { - return $"{split[0].Substring(0, 1)}{split[1].Substring(0, 1)}"; - } - else - { - // Take the first two characters from the text - return text.Substring(0, 2); - } - } - - private void MapSearchQueryToFilters(ref SearchQuery query, string searchQuery, JToken fallbackRefinementFilters) - { - var parsedQuery = new KQLParser().Parse(searchQuery); - - // if we've refiners defined then transform them to property filters - if (fallbackRefinementFilters != null) - { - foreach (var refinementFilter in fallbackRefinementFilters) - { - var filter = GetPropertyAsString(refinementFilter["n"]); - var token = GetPropertyAsString(refinementFilter["t"].First()); - - // Convert token from HEX to ASCII - token = ConvertHexToAscii(token.Trim(new char[] { 'ǂ' })); - - KQLElement k = new KQLElement() - { - Filter = filter, - Value = token, - Operator = KQLPropertyOperator.EqualTo, - Type = KQLFilterType.PropertyFilter - }; - parsedQuery.Add(k); - } - } - - if (parsedQuery == null || parsedQuery.Count == 0) - { - return; - } - - // Handle text filters - var textFilters = parsedQuery.Where(p => p.Type == KQLFilterType.Text); - if (textFilters != null && textFilters.Any()) - { - foreach (var textFilter in textFilters) - { - var s = new SearchQueryFilter - { - Value = textFilter.Value, - FilterType = FilterType.TitleContaining, - }; - - query.Filters.Add(s); - } - } - - // Handle property filters - var propertyFilters = parsedQuery.Where(p => p.Type == KQLFilterType.PropertyFilter); - if (propertyFilters != null && propertyFilters.Any()) - { - foreach (var propertyFilter in propertyFilters) - { - var s = new SearchQueryFilter - { - Fieldname = ManagedPropertyCaseFixup(propertyFilter.Filter), - Value = ManagedPropertyValueFixup(propertyFilter.Value), - Op = KQLOperatorToFilterOperator(propertyFilter.Operator), - FilterType = FilterType.Field - }; - - s.FieldNameMatchText = s.Fieldname; - query.Filters.Add(s); - } - } - } - - private static object ManagedPropertyValueFixup(string value) - { - var valueToCheck = value.ToLower(); - switch (valueToCheck) - { - case "true": return 1; - case "false": return 0; - case "yes": return 1; - case "no": return 0; - default: return value; - } - } - - private static FilterOperator KQLOperatorToFilterOperator(KQLPropertyOperator op) - { - switch (op) - { - case KQLPropertyOperator.Matches: return FilterOperator.Contains; - case KQLPropertyOperator.EqualTo: return FilterOperator.Equals; - case KQLPropertyOperator.LesserThan: return FilterOperator.Equals; - case KQLPropertyOperator.GreaterThan: return FilterOperator.BeginsWith; - case KQLPropertyOperator.GreaterThanOrEqualTo: return FilterOperator.BeginsWith; - case KQLPropertyOperator.DoesNoEqual: return FilterOperator.NotEqual; - default: return FilterOperator.Contains; - } - } - - private static SortType MapToSortTypeFromResourceSource(string resultSource) - { - if (!string.IsNullOrEmpty(resultSource)) - { - resultSource = resultSource.ToLower(); - - switch (resultSource) - { - case "recently changed items": - return SortType.MostRecent; - case "popular": - return SortType.Trending; - case "recommended items": - return SortType.MostViewed; - default: - return SortType.MostRecent; - } - } - return SortType.MostRecent; - } - - private static List MapToContentTypesFromResourceSource(string resultSource, string contentTypeId) - { - List cts = new List(); - - if (!string.IsNullOrEmpty(resultSource)) - { - resultSource = resultSource.ToLower(); - - switch (resultSource) - { - case "documents": - cts.Add(ContentType.Document); - cts.Add(ContentType.Image); - cts.Add(ContentType.Video); - break; - case "wiki": - case "pages": - cts.Add(ContentType.Page); - break; - case "pictures": - cts.Add(ContentType.Image); - break; - case "local video results": - cts.Add(ContentType.Video); - break; - case "popular": - case "recommended items": - case "items matching a content type": - case "items related to a current user": - { - cts = MapToContentTypesFromContentType(contentTypeId); - break; - } - default: - cts.Add(ContentType.All); - break; - } - } - - return cts; - } - - private static List MapToContentTypesFromListTemplate(string listTemplate) - { - List cts = new List(); - - if (!string.IsNullOrEmpty(listTemplate)) - { - if (int.TryParse(listTemplate, out int baseListTemplate)) - { - switch (baseListTemplate) - { - case (int)ListTemplateType.DocumentLibrary: - cts.Add(ContentType.Document); - cts.Add(ContentType.Image); - cts.Add(ContentType.Video); - break; - case (int)ListTemplateType.WebPageLibrary: - cts.Add(ContentType.Page); - break; - case (int)ListTemplateType.Events: - cts.Add(ContentType.Event); - break; - case (int)ListTemplateType.Announcements: - cts.Add(ContentType.News); - break; - case (int)ListTemplateType.Contacts: - cts.Add(ContentType.Contact); - break; - case (int)ListTemplateType.Tasks: - case (int)ListTemplateType.TasksWithTimelineAndHierarchy: - cts.Add(ContentType.Task); - break; - case (int)ListTemplateType.Links: - case 170: // Promoted Links - cts.Add(ContentType.Link); - break; - case (int)ListTemplateType.PictureLibrary: - cts.Add(ContentType.Image); - cts.Add(ContentType.Video); - break; - case (int)ListTemplateType.IssueTracking: - cts.Add(ContentType.Issue); - break; - default: - cts.Add(ContentType.All); - break; - } - } - } - - return cts; - } - - private static List MapToContentTypesFromContentType(string contentTypeId) - { - List cts = new List(); - - // some easy matching - - // pages - if (contentTypeId.StartsWith("0x010108") || // wiki page - contentTypeId.StartsWith("0x010109") || // web part page - contentTypeId.StartsWith("0x0101009D1CB255DA76424F860D91F20E6C4118")) // modern page and it's childs - { - cts.Add(ContentType.Page); - } - // Media assets - else if (contentTypeId.StartsWith("0x0120D520A808") || // video - contentTypeId.StartsWith("0x0101009148F5A04DDD49CBA7127AADA5FB792B")) // Rich Media Asset and it's childs - { - cts.Add(ContentType.Video); - cts.Add(ContentType.Image); - } - else if (contentTypeId.StartsWith("0x0104")) //Announcement - { - // Base document content type - cts.Add(ContentType.News); - } - else if (contentTypeId.StartsWith("0x0106")) //Contact - { - // Base document content type - cts.Add(ContentType.Contact); - } - else if (contentTypeId.StartsWith("0x0106")) //Contact - { - // Base document content type - cts.Add(ContentType.Contact); - } - else if (contentTypeId.StartsWith("0x0102")) //Event - { - // Base document content type - cts.Add(ContentType.Event); - } - else if (contentTypeId.StartsWith("0x0103")) //Issue - { - // Base document content type - cts.Add(ContentType.Issue); - } - else if (contentTypeId.StartsWith("0x0105")) //Link - { - // Base document content type - cts.Add(ContentType.Link); - } - else if (contentTypeId.StartsWith("0x0108")) //Task - { - // Base document content type - cts.Add(ContentType.Task); - } - else if (contentTypeId.StartsWith("0x0101")) - { - // Base document content type - cts.Add(ContentType.Document); - } - - if (cts.Count == 0) - { - cts.Add(ContentType.All); - } - - return cts; - } - - private string MapToSort(string sortField) - { - if (string.IsNullOrEmpty(sortField)) - { - return null; - } - - IEnumerable foundFields = null; - this.clientContext.Web.EnsureProperty(p => p.Fields); - - if (Guid.TryParse(sortField, out Guid fieldId)) - { - foundFields = this.clientContext.Web.Fields.Where(item => item.Id.Equals(fieldId)); - } - else - { - foundFields = this.clientContext.Web.Fields.Where(item => item.InternalName == sortField); - } - - if (foundFields.FirstOrDefault() != null) - { - return foundFields.FirstOrDefault().InternalName; - } - - return null; - } - - private SearchQueryFilter MapToFilter(List list, FilterFieldQueryOperator filterOperator, string filterField, string filterFieldValue, FilterChainingOperator? chainingOperatorUsedInCQWP, FilterType filterType = FilterType.Field) - { - if (string.IsNullOrEmpty(filterField)) - { - return null; - } - - IEnumerable foundFields = null; - - if (list != null) - { - foundFields = list.Context.LoadQuery(list.Fields.Where(item => item.InternalName == filterField)); - list.Context.ExecuteQueryRetry(); - } - else - { - this.clientContext.Web.EnsureProperty(p => p.Fields); - - if (Guid.TryParse(filterField, out Guid fieldId)) - { - foundFields = this.clientContext.Web.Fields.Where(item => item.Id.Equals(fieldId)); - } - else - { - foundFields = this.clientContext.Web.Fields.Where(item => item.InternalName == filterField); - } - } - - if (foundFields.FirstOrDefault() != null) - { - // Store field for future reference - this.queryFields.Add(foundFields.FirstOrDefault()); - - // Resolve user ID to name - if (foundFields.FirstOrDefault().FieldTypeKind == FieldType.User) - { - //[11;#i:0#.f|membership|kevinc@bertonline.onmicrosoft.com] - if (filterFieldValue.Contains("|")) - { - // grab UPN - string[] accountParts = filterFieldValue.Replace("[", "").Replace("]", "").Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries); - //string upn = accountParts[accountParts.Length - 1]; - if (int.TryParse(accountParts[0], out int userId)) - { - var user = this.clientContext.Web.GetUserById(userId); - this.clientContext.Load(user, p => p.Title); - this.clientContext.ExecuteQueryRetry(); - filterFieldValue = user.Title; - } - } - } - - // Build the search query filter object - var s = new SearchQueryFilter - { - Value = filterFieldValue, - FilterType = filterType, - Op = MapQueryFilterOperator(filterOperator), - Fieldname = foundFields.FirstOrDefault().InternalName, - ChainingOperatorUsedInCQWP = chainingOperatorUsedInCQWP, - FieldNameMatchText = "" - }; - - return s; - } - - return null; - } - - private static FilterOperator? MapQueryFilterOperator(FilterFieldQueryOperator filterOperator) - { - switch (filterOperator) - { - case FilterFieldQueryOperator.BeginsWith: - { - return FilterOperator.BeginsWith; - } - case FilterFieldQueryOperator.Contains: - { - return FilterOperator.Contains; - } - case FilterFieldQueryOperator.Eq: - { - return FilterOperator.Equals; - } - case FilterFieldQueryOperator.Neq: - { - return FilterOperator.NotEqual; - } - case FilterFieldQueryOperator.Gt: - case FilterFieldQueryOperator.Geq: - { - return FilterOperator.GreaterThan; - } - case FilterFieldQueryOperator.Lt: - case FilterFieldQueryOperator.Leq: - { - return FilterOperator.LessThan; - } - default: - { - return null; - } - } - } - - private void SetLayoutTemplate(ContentRollupLayout template) - { - this.properties.TemplateId = template; - this.properties.LayoutId = template.ToString(); - } - - internal string HighlightedContentProperties() - { - // Don't serialize null values - var jsonSerializerSettings = new JsonSerializerSettings() - { - MissingMemberHandling = MissingMemberHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - - var json = JsonConvert.SerializeObject(this.properties, jsonSerializerSettings); - - // Embed DisplayMaps via a placeholder replace - json = json.Replace("\"%%DisplayMapsPlaceholder%%\"", displayMapJson); - return json; - } - - private static bool IsPropertyAvailable(JToken property) - { - if (property != null && property.Value() != null) - { - return true; - } - - return false; - } - - private static string ConvertHexToAscii(String hexString) - { - try - { - string ascii = string.Empty; - - for (int i = 0; i < hexString.Length; i += 2) - { - string hs = string.Empty; - - hs = hexString.Substring(i, 2); - uint decval = Convert.ToUInt32(hs, 16); - char character = Convert.ToChar(decval); - ascii += character; - - } - - return ascii; - } - catch - { - //Eat exceptions - } - - return string.Empty; - } - - private static string GetPropertyAsString(JToken property) - { - if (IsPropertyAvailable(property)) - { - var propertyAsString = property.ToString(Formatting.None); - - if (!string.IsNullOrEmpty(propertyAsString)) - { - // Clean the surrounding escaped double quotes - propertyAsString = propertyAsString.Replace("\"", "").Replace("\\", ""); - return propertyAsString; - } - } - - return null; - } - - private static bool GetPropertyAsBoolean(JToken property) - { - if (IsPropertyAvailable(property)) - { - var propertyAsString = property.ToString(Formatting.None); - - if (!string.IsNullOrEmpty(propertyAsString)) - { - // Clean the surrounding escaped double quotes - propertyAsString = propertyAsString.Replace("\"", "").Replace("\\", ""); - - if (Boolean.TryParse(propertyAsString, out bool result)) - { - return result; - } - else - { - throw new Exception($"Property value {propertyAsString} could not be converted to boolean."); - } - } - } - - return false; - } - - private static string ManagedPropertyCaseFixup(string property) - { - var propertyToCheck = property.ToLower(); - - switch (propertyToCheck) - { - case "account": return "Account"; - case "aboutme": return "AboutMe"; - case "accountname": return "AccountName"; - case "acronymaggre": return "acronymaggre"; - case "assignedto": return "AssignedTo"; - case "attachmenttype": return "AttachmentType"; - case "author": return "Author"; - case "baseofficelocation": return "BaseOfficeLocation"; - case "categorynavigationurl": return "CategoryNavigationUrl"; - case "charset": return "charset"; - case "colleagues": return "colleagues"; - case "combineduserprofilenames": return "CombinedUserProfileNames"; - case "companies": return "companies"; - // changed - case "contentclass": return "ContentClass"; - case "contentshidden": return "ContentsHidden"; - case "contentsource": return "ContentSource"; - case "contenttype": return "ContentType"; - case "contenttypeid": return "ContentTypeId"; - case "created": return "Created"; - case "createdby": return "CreatedBy"; - case "date00": return "Date00"; - case "date01": return "Date01"; - case "date02": return "Date02"; - case "date03": return "Date03"; - case "date04": return "Date04"; - case "date05": return "Date05"; - case "date06": return "Date06"; - case "date07": return "Date07"; - case "date08": return "Date08"; - case "date09": return "Date09"; - case "decimal00": return "Decimal00"; - case "decimal01": return "Decimal01"; - case "decimal02": return "Decimal02"; - case "decimal03": return "Decimal03"; - case "decimal04": return "Decimal04"; - case "decimal05": return "Decimal05"; - case "decimal06": return "Decimal06"; - case "decimal07": return "Decimal07"; - case "decimal08": return "Decimal08"; - case "decimal09": return "Decimal09"; - case "deeplinks": return "deeplinks"; - case "defaggre": return "defaggre"; - case "department": return "Department"; - case "description": return "Description"; - case "detectedlanguage": return "DetectedLanguage"; - case "displayauthor": return "DisplayAuthor"; - case "displaydate": return "DisplayDate"; - case "dmsdocaccessright": return "DMSDocAccessRight"; - case "dmsdocauthor": return "DMSDocAuthor"; - case "dmsdoctitle": return "DMSDocTitle"; - case "docacl": return "docacl"; - case "doccomments": return "DocComments"; - case "docid": return "DocId"; - case "dockeywords": return "DocKeywords"; - case "docsignature": return "DocSignature"; - case "docsubject": return "DocSubject"; - case "documentsignature": return "DocumentSignature"; - case "domain": return "domain"; - case "double00": return "Double00"; - case "double01": return "Double01"; - case "double02": return "Double02"; - case "double03": return "Double03"; - case "double04": return "Double04"; - case "double05": return "Double05"; - case "double06": return "Double06"; - case "double07": return "Double07"; - case "double08": return "Double08"; - case "double09": return "Double09"; - case "duplicatehash": return "DuplicateHash"; - case "eduassignmentcategory": return "EduAssignmentCategory"; - case "eduassignmentformat": return "EduAssignmentFormat"; - case "edumaximumscore": return "EduMaximumScore"; - case "enddate": return "EndDate"; - case "expirationtime": return "ExpirationTime"; - case "extractedauthor": return "ExtractedAuthor"; - case "extracteddate": return "ExtractedDate"; - case "fileextension": return "FileExtension"; - case "filename": return "Filename"; - case "filetype": return "FileType"; - case "firstlevelcolleagues": return "FirstLevelColleagues"; - case "firstlevelmutualfollowings": return "FirstLevelMutualFollowings"; - case "firstname": return "FirstName"; - case "followallanchor": return "FollowAllAnchor"; - case "format": return "format"; - case "generatedtitle": return "GeneratedTitle"; - case "genre": return "Genre"; - case "hierarchyurl": return "HierarchyUrl"; - case "hithighlightedproperties": return "HitHighlightedProperties"; - case "hithighlightedsummary": return "HitHighlightedSummary"; - case "hostingpartition": return "HostingPartition"; - case "hwboost": return "hwboost"; - case "imagedatecreated": return "ImageDateCreated"; - case "importance": return "importance"; - case "int00": return "Int00"; - case "int01": return "Int01"; - case "int02": return "Int02"; - case "int03": return "Int03"; - case "int04": return "Int04"; - case "int05": return "Int05"; - case "int06": return "Int06"; - case "int07": return "Int07"; - case "int08": return "Int08"; - case "int09": return "Int09"; - case "int10": return "Int10"; - case "int11": return "Int11"; - case "int12": return "Int12"; - case "int13": return "Int13"; - case "int14": return "Int14"; - case "int15": return "Int15"; - case "int16": return "Int16"; - case "int17": return "Int17"; - case "int18": return "Int18"; - case "int19": return "Int19"; - case "int20": return "Int20"; - case "int21": return "Int21"; - case "int22": return "Int22"; - case "int23": return "Int23"; - case "int24": return "Int24"; - case "int25": return "Int25"; - case "int26": return "Int26"; - case "int27": return "Int27"; - case "int28": return "Int28"; - case "int29": return "Int29"; - case "int30": return "Int30"; - case "int31": return "Int31"; - case "int32": return "Int32"; - case "int33": return "Int33"; - case "int34": return "Int34"; - case "int35": return "Int35"; - case "int36": return "Int36"; - case "int37": return "Int37"; - case "int38": return "Int38"; - case "int39": return "Int39"; - case "int40": return "Int40"; - case "int41": return "Int41"; - case "int42": return "Int42"; - case "int43": return "Int43"; - case "int44": return "Int44"; - case "int45": return "Int45"; - case "int46": return "Int46"; - case "int47": return "Int47"; - case "int48": return "Int48"; - case "int49": return "Int49"; - case "interests": return "Interests"; - case "iscontainer": return "IsContainer"; - case "isdata": return "IsData"; - case "isdocument": return "IsDocument"; - case "ismydocuments": return "IsMyDocuments"; - case "ispublishingcatalog": return "IsPublishingCatalog"; - case "isreport": return "IsReport"; - case "jobtitle": return "JobTitle"; - case "keywords": return "Keywords"; - case "language": return "language"; - case "languages": return "languages"; - case "lastmodifiedtime": return "LastModifiedTime"; - case "lastname": return "LastName"; - case "listid": return "ListID"; - case "listitemid": return "ListItemID"; - case "listurl": return "ListUrl"; - case "location": return "Location"; - case "managedproperties": return "ManagedProperties"; - case "mediaduration": return "MediaDuration"; - case "memberships": return "Memberships"; - case "metadataauthor": return "MetadataAuthor"; - case "microblogtype": return "MicroBlogType"; - case "mobilephone": return "MobilePhone"; - case "modifiedby": return "ModifiedBy"; - case "nlcodepage": return "NLCodePage"; - case "notes": return "Notes"; - case "officenumber": return "OfficeNumber"; - case "orgnames": return "OrgNames"; - case "orgparentnames": return "OrgParentNames"; - case "orgparenturls": return "OrgParentUrls"; - case "orgurls": return "OrgUrls"; - case "ows_url": return "OWS_URL"; - // changed - case "owsmetadatafacetinfo": return "OWSMetadataFacetInfo"; - // changed - case "owstaxidmetadataalltagsinfo": return "OWSTaxIdMetadataAllTagsInfo"; - // changed - case "owstaxidproductcatalogitemcategory": return "OWSTaxIdProductCatalogItemCategory"; - case "parentlink": return "ParentLink"; - case "pastprojects": return "PastProjects"; - case "path": return "Path"; - case "people": return "People"; - case "peopleinmedia": return "PeopleInMedia"; - case "peoplekeywords": return "PeopleKeywords"; - case "phonenumber": return "PhoneNumber"; - case "pictureheight": return "PictureHeight"; - case "picturethumbnailurl": return "PictureThumbnailURL"; - case "pictureurl": return "PictureURL"; - case "picturewidth": return "PictureWidth"; - case "postauthor": return "PostAuthor"; - case "preferredname": return "PreferredName"; - case "priority": return "Priority"; - case "privatecolleagues": return "PrivateColleagues"; - case "processingtime": return "processingtime"; - case "productcataloggroupnumberowstext": return "ProductCatalogGroupNumberOWSTEXT"; - case "profileexpertise": return "ProfileExpertise"; - case "profilename": return "ProfileName"; - case "pronunciations": return "Pronunciations"; - case "purpose": return "Purpose"; - case "rankdetail": return "RankDetail"; - case "rankingweighthigh": return "RankingWeightHigh"; - case "rankingweightlow": return "RankingWeightLow"; - case "rankingweightname": return "RankingWeightName"; - case "recommendedfor": return "recommendedfor"; - case "refinabledate00": return "RefinableDate00"; - case "refinabledate01": return "RefinableDate01"; - case "refinabledate02": return "RefinableDate02"; - case "refinabledate03": return "RefinableDate03"; - case "refinabledate04": return "RefinableDate04"; - case "refinabledate05": return "RefinableDate05"; - case "refinabledate06": return "RefinableDate06"; - case "refinabledate07": return "RefinableDate07"; - case "refinabledate08": return "RefinableDate08"; - case "refinabledate09": return "RefinableDate09"; - case "refinabledate10": return "RefinableDate10"; - case "refinabledate11": return "RefinableDate11"; - case "refinabledate12": return "RefinableDate12"; - case "refinabledate13": return "RefinableDate13"; - case "refinabledate14": return "RefinableDate14"; - case "refinabledate15": return "RefinableDate15"; - case "refinabledate16": return "RefinableDate16"; - case "refinabledate17": return "RefinableDate17"; - case "refinabledate18": return "RefinableDate18"; - case "refinabledate19": return "RefinableDate19"; - case "refinabledecimal00": return "RefinableDecimal00"; - case "refinabledecimal01": return "RefinableDecimal01"; - case "refinabledecimal02": return "RefinableDecimal02"; - case "refinabledecimal03": return "RefinableDecimal03"; - case "refinabledecimal04": return "RefinableDecimal04"; - case "refinabledecimal05": return "RefinableDecimal05"; - case "refinabledecimal06": return "RefinableDecimal06"; - case "refinabledecimal07": return "RefinableDecimal07"; - case "refinabledecimal08": return "RefinableDecimal08"; - case "refinabledecimal09": return "RefinableDecimal09"; - case "refinabledouble00": return "RefinableDouble00"; - case "refinabledouble01": return "RefinableDouble01"; - case "refinabledouble02": return "RefinableDouble02"; - case "refinabledouble03": return "RefinableDouble03"; - case "refinabledouble04": return "RefinableDouble04"; - case "refinabledouble05": return "RefinableDouble05"; - case "refinabledouble06": return "RefinableDouble06"; - case "refinabledouble07": return "RefinableDouble07"; - case "refinabledouble08": return "RefinableDouble08"; - case "refinabledouble09": return "RefinableDouble09"; - case "refinableint00": return "RefinableInt00"; - case "refinableint01": return "RefinableInt01"; - case "refinableint02": return "RefinableInt02"; - case "refinableint03": return "RefinableInt03"; - case "refinableint04": return "RefinableInt04"; - case "refinableint05": return "RefinableInt05"; - case "refinableint06": return "RefinableInt06"; - case "refinableint07": return "RefinableInt07"; - case "refinableint08": return "RefinableInt08"; - case "refinableint09": return "RefinableInt09"; - case "refinableint10": return "RefinableInt10"; - case "refinableint11": return "RefinableInt11"; - case "refinableint12": return "RefinableInt12"; - case "refinableint13": return "RefinableInt13"; - case "refinableint14": return "RefinableInt14"; - case "refinableint15": return "RefinableInt15"; - case "refinableint16": return "RefinableInt16"; - case "refinableint17": return "RefinableInt17"; - case "refinableint18": return "RefinableInt18"; - case "refinableint19": return "RefinableInt19"; - case "refinableint20": return "RefinableInt20"; - case "refinableint21": return "RefinableInt21"; - case "refinableint22": return "RefinableInt22"; - case "refinableint23": return "RefinableInt23"; - case "refinableint24": return "RefinableInt24"; - case "refinableint25": return "RefinableInt25"; - case "refinableint26": return "RefinableInt26"; - case "refinableint27": return "RefinableInt27"; - case "refinableint28": return "RefinableInt28"; - case "refinableint29": return "RefinableInt29"; - case "refinableint30": return "RefinableInt30"; - case "refinableint31": return "RefinableInt31"; - case "refinableint32": return "RefinableInt32"; - case "refinableint33": return "RefinableInt33"; - case "refinableint34": return "RefinableInt34"; - case "refinableint35": return "RefinableInt35"; - case "refinableint36": return "RefinableInt36"; - case "refinableint37": return "RefinableInt37"; - case "refinableint38": return "RefinableInt38"; - case "refinableint39": return "RefinableInt39"; - case "refinableint40": return "RefinableInt40"; - case "refinableint41": return "RefinableInt41"; - case "refinableint42": return "RefinableInt42"; - case "refinableint43": return "RefinableInt43"; - case "refinableint44": return "RefinableInt44"; - case "refinableint45": return "RefinableInt45"; - case "refinableint46": return "RefinableInt46"; - case "refinableint47": return "RefinableInt47"; - case "refinableint48": return "RefinableInt48"; - case "refinableint49": return "RefinableInt49"; - case "refinablestring00": return "RefinableString00"; - case "refinablestring01": return "RefinableString01"; - case "refinablestring02": return "RefinableString02"; - case "refinablestring03": return "RefinableString03"; - case "refinablestring04": return "RefinableString04"; - case "refinablestring05": return "RefinableString05"; - case "refinablestring06": return "RefinableString06"; - case "refinablestring07": return "RefinableString07"; - case "refinablestring08": return "RefinableString08"; - case "refinablestring09": return "RefinableString09"; - case "refinablestring10": return "RefinableString10"; - case "refinablestring11": return "RefinableString11"; - case "refinablestring12": return "RefinableString12"; - case "refinablestring13": return "RefinableString13"; - case "refinablestring14": return "RefinableString14"; - case "refinablestring15": return "RefinableString15"; - case "refinablestring16": return "RefinableString16"; - case "refinablestring17": return "RefinableString17"; - case "refinablestring18": return "RefinableString18"; - case "refinablestring19": return "RefinableString19"; - case "refinablestring20": return "RefinableString20"; - case "refinablestring21": return "RefinableString21"; - case "refinablestring22": return "RefinableString22"; - case "refinablestring23": return "RefinableString23"; - case "refinablestring24": return "RefinableString24"; - case "refinablestring25": return "RefinableString25"; - case "refinablestring26": return "RefinableString26"; - case "refinablestring27": return "RefinableString27"; - case "refinablestring28": return "RefinableString28"; - case "refinablestring29": return "RefinableString29"; - case "refinablestring30": return "RefinableString30"; - case "refinablestring31": return "RefinableString31"; - case "refinablestring32": return "RefinableString32"; - case "refinablestring33": return "RefinableString33"; - case "refinablestring34": return "RefinableString34"; - case "refinablestring35": return "RefinableString35"; - case "refinablestring36": return "RefinableString36"; - case "refinablestring37": return "RefinableString37"; - case "refinablestring38": return "RefinableString38"; - case "refinablestring39": return "RefinableString39"; - case "refinablestring40": return "RefinableString40"; - case "refinablestring41": return "RefinableString41"; - case "refinablestring42": return "RefinableString42"; - case "refinablestring43": return "RefinableString43"; - case "refinablestring44": return "RefinableString44"; - case "refinablestring45": return "RefinableString45"; - case "refinablestring46": return "RefinableString46"; - case "refinablestring47": return "RefinableString47"; - case "refinablestring48": return "RefinableString48"; - case "refinablestring49": return "RefinableString49"; - case "refinablestring50": return "RefinableString50"; - case "refinablestring51": return "RefinableString51"; - case "refinablestring52": return "RefinableString52"; - case "refinablestring53": return "RefinableString53"; - case "refinablestring54": return "RefinableString54"; - case "refinablestring55": return "RefinableString55"; - case "refinablestring56": return "RefinableString56"; - case "refinablestring57": return "RefinableString57"; - case "refinablestring58": return "RefinableString58"; - case "refinablestring59": return "RefinableString59"; - case "refinablestring60": return "RefinableString60"; - case "refinablestring61": return "RefinableString61"; - case "refinablestring62": return "RefinableString62"; - case "refinablestring63": return "RefinableString63"; - case "refinablestring64": return "RefinableString64"; - case "refinablestring65": return "RefinableString65"; - case "refinablestring66": return "RefinableString66"; - case "refinablestring67": return "RefinableString67"; - case "refinablestring68": return "RefinableString68"; - case "refinablestring69": return "RefinableString69"; - case "refinablestring70": return "RefinableString70"; - case "refinablestring71": return "RefinableString71"; - case "refinablestring72": return "RefinableString72"; - case "refinablestring73": return "RefinableString73"; - case "refinablestring74": return "RefinableString74"; - case "refinablestring75": return "RefinableString75"; - case "refinablestring76": return "RefinableString76"; - case "refinablestring77": return "RefinableString77"; - case "refinablestring78": return "RefinableString78"; - case "refinablestring79": return "RefinableString79"; - case "refinablestring80": return "RefinableString80"; - case "refinablestring81": return "RefinableString81"; - case "refinablestring82": return "RefinableString82"; - case "refinablestring83": return "RefinableString83"; - case "refinablestring84": return "RefinableString84"; - case "refinablestring85": return "RefinableString85"; - case "refinablestring86": return "RefinableString86"; - case "refinablestring87": return "RefinableString87"; - case "refinablestring88": return "RefinableString88"; - case "refinablestring89": return "RefinableString89"; - case "refinablestring90": return "RefinableString90"; - case "refinablestring91": return "RefinableString91"; - case "refinablestring92": return "RefinableString92"; - case "refinablestring93": return "RefinableString93"; - case "refinablestring94": return "RefinableString94"; - case "refinablestring95": return "RefinableString95"; - case "refinablestring96": return "RefinableString96"; - case "refinablestring97": return "RefinableString97"; - case "refinablestring98": return "RefinableString98"; - case "refinablestring99": return "RefinableString99"; - case "responsibilities": return "Responsibilities"; - case "robotsnoindex": return "RobotsNoIndex"; - case "rootpostid": return "RootPostID"; - case "rootpostownerid": return "RootPostOwnerID"; - case "rootpostuniqueid": return "RootPostUniqueID"; - case "schools": return "Schools"; - case "secondaryfileextension": return "SecondaryFileExtension"; - case "secondlevelcolleagues": return "SecondLevelColleagues"; - case "serverredirectedurl": return "ServerRedirectedURL"; - case "serviceapplicationid": return "ServiceApplicationID"; - case "sharedwithinternal": return "SharedWithInternal"; - case "sipaddress": return "SipAddress"; - case "site": return "Site"; - case "siteclosed": return "SiteClosed"; - case "siteid": return "SiteID"; - case "sitename": return "sitename"; - case "sitetitle": return "SiteTitle"; - case "size": return "Size"; - case "skills": return "Skills"; - case "socialtag": return "SocialTag"; - case "socialtagtexturl": return "SocialTagTextUrl"; - case "spcontenttype": return "SPContentType"; - case "spsiteurl": return "SPSiteURL"; - case "startdate": return "StartDate"; - case "status": return "Status"; - case "tags": return "Tags"; - case "title": return "Title"; - case "tld": return "tld"; - case "urldepth": return "UrlDepth"; - case "urlkeywords": return "urlkeywords"; - case "urls": return "urls"; - case "usageanalyticsid": return "UsageAnalyticsId"; - case "usageeventitemid": return "UsageEventItemId"; - case "username": return "UserName"; - case "userprofile_guid": return "UserProfile_GUID"; - case "webid": return "WebId"; - case "webtemplate": return "WebTemplate"; - case "wikicategory": return "WikiCategory"; - case "wordcustomrefiner1": return "WordCustomRefiner1"; - case "wordcustomrefiner2": return "WordCustomRefiner2"; - case "wordcustomrefiner3": return "WordCustomRefiner3"; - case "wordcustomrefiner4": return "WordCustomRefiner4"; - case "wordcustomrefiner5": return "WordCustomRefiner5"; - case "wordexactcustomrefiner": return "WordExactCustomRefiner"; - case "wordpartcustomrefiner1": return "WordPartCustomRefiner1"; - case "wordpartcustomrefiner2": return "WordPartCustomRefiner2"; - case "wordpartcustomrefiner3": return "WordPartCustomRefiner3"; - case "wordpartcustomrefiner4": return "WordPartCustomRefiner4"; - case "wordpartcustomrefiner5": return "WordPartCustomRefiner5"; - case "wordpartexactcustomrefiner": return "WordPartExactCustomRefiner"; - case "workemail": return "WorkEmail"; - case "workphone": return "WorkPhone"; - case "yomidisplayname": return "YomiDisplayName"; - default: return property; - } - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/CookieManager.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/CookieManager.cs deleted file mode 100644 index 94850eb53a..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/CookieManager.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.SharePoint.Client; -using PnP.Core.Transformation.SharePoint.Extensions; -using System; -using System.Net; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - /// - /// Handles the "intercepting" of auth cookies that might have been added on the clientcontext object - /// - internal class CookieManager - { - private CookieContainer authCookiesContainer; - - internal CookieContainer GetCookies(ClientContext cc) - { - EventHandler cookieInterceptorHandler = CollectCookiesHandler(); - try - { - // Hookup a custom handler, assumes the original handler placing the cookies is ran first - cc.ExecutingWebRequest += cookieInterceptorHandler; - //// Trigger the handler to fire by loading something - cc.ExecuteQueryRetry(); - } - catch (Exception) - { - // Eating the exception - } - finally - { - // Disconnect the handler as we don't need it anymore - cc.ExecutingWebRequest -= cookieInterceptorHandler; - } - - if (this.authCookiesContainer != null && this.authCookiesContainer.Count > 0) - { - return this.authCookiesContainer; - } - - return null; - } - - - private EventHandler CollectCookiesHandler() - { - return (s, e) => - { - if (authCookiesContainer == null || (authCookiesContainer != null && authCookiesContainer.Count == 0)) - { - this.authCookiesContainer = CopyContainer(e.WebRequestExecutor.WebRequest.CookieContainer, e.WebRequestExecutor.WebRequest.RequestUri); - } - }; - } - - private CookieContainer CopyContainer(CookieContainer container, Uri uri) - { - if (container == null) - { - return null; - } - - var newContainer = new CookieContainer(); - newContainer.Add(container.GetCookies(uri)); - - var adminUri = new Uri($"{uri.Scheme}://{uri.Host.ToLower().Replace(".sharepoint.", "-admin.sharepoint.")}"); - newContainer.Add(container.GetCookies(adminUri)); - - return newContainer; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLElement.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLElement.cs deleted file mode 100644 index 467b57ec07..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLElement.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.KQL -{ - /// - /// Element in KQL query - /// - internal class KQLElement - { - /// - /// Filter attribute name - /// - public string Filter { get; set; } - - /// - /// Value of the filter - /// - public string Value { get; set; } - - /// - /// Type of filter - /// - public KQLFilterType Type { get; set; } - - /// - /// Filter operator - /// - public KQLPropertyOperator Operator { get; set; } - - /// - /// Filter group - /// - public int Group { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLParser.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLParser.cs deleted file mode 100644 index b93a2608a6..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLParser.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.KQL -{ - /// - /// Class to parse KQL queries - /// - internal class KQLParser - { - /// - /// Parses a KQL query and returns a list of tokens - /// - /// Query to parse - /// List of tokens - public List Parse(string searchQuery) - { - // See https://docs.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference - List parsedQuery = new List(10); - - try - { - if (string.IsNullOrEmpty(searchQuery)) - { - return parsedQuery; - } - - var parts = searchQuery.Trim().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); - - int group = 0; - bool groupOpen = false; - bool groupToClose = false; - for (int i = 0; i < parts.Length; i++) - { - var part = parts[i]; - - if (!groupOpen && part.StartsWith("(")) - { - group = group + 1; - groupOpen = true; - // Strip the ( - part = part.Substring(1); - } - - if (groupOpen && part.EndsWith(")")) - { - groupToClose = true; - - // Strip the ) - part = part.TrimEnd(new char[] { ')' }); - } - - KQLPropertyOperator op = DetectOperator(part); - - if (op != KQLPropertyOperator.Undefined) - { - string opAsString = OperatorToString(op); - KQLElement k = new KQLElement() - { - Type = KQLFilterType.PropertyFilter, - Operator = op, - Group = groupOpen ? group : 0, - Filter = part.Substring(0, part.IndexOf(opAsString)), - Value = part.Substring(part.IndexOf(opAsString) + opAsString.Length) - }; - parsedQuery.Add(k); - } - else if (part.StartsWith("{") && part.EndsWith("}")) - { - KQLElement k = new KQLElement() - { - Type = KQLFilterType.KeywordFilter, - Operator = KQLPropertyOperator.Undefined, - Group = groupOpen ? group : 0, - Filter = part.Replace("{", "").Replace("}", ""), - Value = "", - }; - parsedQuery.Add(k); - } - else - { - if (part.Equals("OR", StringComparison.InvariantCultureIgnoreCase) || - part.Equals("AND", StringComparison.InvariantCultureIgnoreCase) || - part.Equals("NEAR", StringComparison.InvariantCultureIgnoreCase) || - part.StartsWith("NEAR(", StringComparison.InvariantCultureIgnoreCase) || - part.Equals("ONENEAR", StringComparison.InvariantCultureIgnoreCase) || - part.StartsWith("ONENEAR(", StringComparison.InvariantCultureIgnoreCase) || - part.StartsWith("XRANK(", StringComparison.InvariantCultureIgnoreCase) || - part.Equals("NOT", StringComparison.InvariantCultureIgnoreCase)) - { - // Don't add operators as KQL elements - } - else - { - KQLElement k = new KQLElement() - { - Type = KQLFilterType.Text, - Operator = KQLPropertyOperator.Undefined, - Group = groupOpen ? group : 0, - Filter = "", - Value = part, - }; - parsedQuery.Add(k); - } - } - - if (groupToClose) - { - groupOpen = false; - groupToClose = false; - } - - } - } - catch(Exception) - { - // Eat exceptions for now, KQL query parsing will be obsolete when the AdvancedQuery option becomes available - } - - return parsedQuery; - } - - #region Helper methods - private string OperatorToString(KQLPropertyOperator ops) - { - switch (ops) - { - case KQLPropertyOperator.DoesNoEqual: return "<>"; - case KQLPropertyOperator.EqualTo: return "="; - case KQLPropertyOperator.GreaterThanOrEqualTo: return ">="; - case KQLPropertyOperator.GreaterThan: return ">"; - case KQLPropertyOperator.LesserThan: return "<"; - case KQLPropertyOperator.LesserThanOrEqualTo: return "<="; - case KQLPropertyOperator.Matches: return ":"; - case KQLPropertyOperator.Restriction: return ".."; - } - - return ""; - } - - private KQLPropertyOperator DetectOperator(string part) - { - if (part.Contains(">=")) - { - return KQLPropertyOperator.GreaterThanOrEqualTo; - } - else if (part.Contains("<=")) - { - return KQLPropertyOperator.LesserThanOrEqualTo; - } - else if (part.Contains("<>")) - { - return KQLPropertyOperator.DoesNoEqual; - } - else if (part.Contains("..")) - { - return KQLPropertyOperator.Restriction; - } - else if (part.Contains(">")) - { - return KQLPropertyOperator.GreaterThan; - } - else if (part.Contains("<")) - { - return KQLPropertyOperator.LesserThan; - } - else if (part.Contains("=")) - { - return KQLPropertyOperator.EqualTo; - } - else if (part.Contains(":")) - { - return KQLPropertyOperator.Matches; - } - else - { - return KQLPropertyOperator.Undefined; - } - } - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyOperator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyOperator.cs deleted file mode 100644 index 91a2163444..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyOperator.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.KQL -{ - /// - /// Operators on KQL property filters - /// - internal enum KQLPropertyOperator - { - Matches = 0, - EqualTo = 1, - LesserThan = 2, - GreaterThan = 3, - LesserThanOrEqualTo = 4, - GreaterThanOrEqualTo = 5, - DoesNoEqual = 6, - Restriction = 7, - Undefined = 100 - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyType.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyType.cs deleted file mode 100644 index ed83bbac87..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/KQL/KQLPropertyType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace PnP.Core.Transformation.SharePoint.KQL -{ - /// - /// KQL filter types - /// - internal enum KQLFilterType - { - Text = 0, - KeywordFilter = 1, - PropertyFilter = 2, - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/QuickLinksTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/QuickLinksTransformator.cs deleted file mode 100644 index 8a3d391cda..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/QuickLinksTransformator.cs +++ /dev/null @@ -1,814 +0,0 @@ -using Microsoft.SharePoint.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using PnP.Core.Transformation.SharePoint.Extensions; -using System; -using System.Collections.Generic; -using System.Web; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - #region QuickLinks property model - internal class QuickLinksWebPartProperties - { - //IQuickLinksWebPartProps - [JsonProperty(PropertyName = "items")] //The Quick Link items. - public List Items { get; set; } - [JsonProperty(PropertyName = "isMigrated")] //The flag indicates whether migration for this web part has been completed. For new web parts this value is true. - public bool IsMigrated { get; set; } - [JsonProperty(PropertyName = "shouldShowThumbnail")] //Whether web part should show thumbnail in compact card layout - public bool ShouldShowThumbnail { get; set; } - [JsonProperty(PropertyName = "buttonLayoutOptions")] //The layout option of button layout - public ButtonLayoutOptions ButtonLayoutOptions { get; set; } - [JsonProperty(PropertyName = "listLayoutOptions")] //The layout option of list layout - public ListLayoutOptions ListLayoutOptions { get; set; } - [JsonProperty(PropertyName = "waffleLayoutOptions")] //The layout option of waffle layout - public WaffleLayoutOptions WaffleLayoutOptions { get; set; } - - // IBaseCollectionWebPartProperties props - [JsonProperty(PropertyName = "layoutId")] // Set when there are more than one layouts to indicate which layout is in use. - public string LayoutId { get; set; } - [JsonProperty(PropertyName = "dataProviderId")] // Set when there are more than one data providers to indicate which data provider is in use. - public string DataProviderId { get; set; } - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } - [JsonProperty(PropertyName = "addToPageScreenReaderLabel")] - public string AddToPageScreenReaderLabel { get; set; } - [JsonProperty(PropertyName = "hideWebPartWhenEmpty")] - public bool? HideWebPartWhenEmpty { get; set; } - [JsonProperty(PropertyName = "webId")] - public string WebId { get; set; } - [JsonProperty(PropertyName = "siteId")] - public string SiteId { get; set; } - [JsonProperty(PropertyName = "baseUrl")] - public string BaseUrl { get; set; } // base url to locate web part resources such has file icons - - public QuickLinksWebPartProperties() - { - this.Items = new List(); - } - } - - internal class QuickLink - { - /** - * The info of the source item where does the quick link point to. - */ - [JsonProperty(PropertyName = "sourceItem")] - public SourceItemInfo SourceItem { get; set; } - - /** - * The info of the image selected by user. - */ - [JsonProperty(PropertyName = "image")] - public QuickLinkCustomizedImageInfo Image { get; set; } - - /** - * The info of the icon selected by user. - */ - [JsonProperty(PropertyName = "fabricReactIcon")] - public QuickLinkFabricReactIconInfo FabricReactIcon { get; set; } - - /** - * Indicate the way that thumbnail is chosen and rendered. - */ - [JsonProperty(PropertyName = "thumbnailType")] - public QuickLinkThumbnailSourceType ThumbnailType { get; set; } - - /** - * To store the preview image that doesn't have guid set. - * If quick link is not targeting a SharePoint item, the image and title will be retrieved from embed service. - * Also in on-premise environment, the image preview doesn't have guids. - */ - [JsonProperty(PropertyName = "rawPreviewImageUrl")] - public string RawPreviewImageUrl { get; set; } - - /** - * Unique id of the quick link. - */ - [JsonProperty(PropertyName = "id")] - public int Id { get; set; } - - /** - * Title of the quick link. - */ - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } - - /** - * Extra description field of the quick link item. - */ - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } - - /** - * User customized alt text to place on the image for people with disabilities. - */ - [JsonProperty(PropertyName = "altText")] - public string AltText { get; set; } - } - - internal class QuickLinkFabricReactIconInfo - { - /** - * The name of the icon. - */ - [JsonProperty(PropertyName = "iconName")] - public string IconName { get; set; } - } - - internal class GuidSet - { - [JsonProperty(PropertyName = "siteId")] - public string SiteId { get; set; } - [JsonProperty(PropertyName = "webId")] - public string WebId { get; set; } - [JsonProperty(PropertyName = "listId")] - public string ListId { get; set; } - [JsonProperty(PropertyName = "uniqueId")] - public string UniqueId { get; set; } - } - - internal class SourceItemInfo - { - /** - * Original url of the quick link. - */ - [JsonProperty(PropertyName = "url")] - public string Url { get; set; } - - /** - * Guids that represent the quick link item. - * Only SharePoint item has guids. It is "undefined" for external items. - */ - [JsonProperty(PropertyName = "guids")] - public GuidSet Guids { get; set; } - - /** - * The type of object represented by the quick link. If the type is unknown, it will be considered - * as QuickLinkType.Link. - */ - [JsonProperty(PropertyName = "itemType")] - public QuickLinkType ItemType { get; set; } - - /** - * File extension of the file represented by the quick link. Example: ".png". If the object represented - * by the quick link does not have a file extension or is not a file, this will be null or the empty string. - * E.g. Some OneNote doesn't have extensions and will need "progId" to identify them. - */ - [JsonProperty(PropertyName = "fileExtension")] - public string FileExtension { get; set; } - - /** - * Identifier of the program used to open this kind of file or folder, if this is a file or folder. - * E.g. OneNote. - */ - [JsonProperty(PropertyName = "progId")] - public string ProgId { get; set; } - } - - internal class QuickLinkCustomizedImageInfo - { - /** - * The source url of the customized image of the quick link. - */ - [JsonProperty(PropertyName = "url")] - public string Url { get; set; } - - /** - * How image is fit into the container. - */ - [JsonProperty(PropertyName = "imageFit")] - public ImageFit ImageFit { get; set; } - - /** - * Guids that can be used to load the picture from the micro-service, if specified. - */ - [JsonProperty(PropertyName = "guids")] - public GuidSet Guids { get; set; } - } - - internal class WaffleLayoutOptions - { - /** - * The size of icon displayed on each card. - */ - [JsonProperty(PropertyName = "iconSize")] - public IconSize IconSize { get; set; } - - /** - * Whether only image or icon is displayed in the waffle layout. - */ - [JsonProperty(PropertyName = "onlyShowThumbnail")] - public bool OnlyShowThumbnail { get; set; } - } - - internal class ListLayoutOptions - { - /** - * Whether show the link description text of all link items. - */ - [JsonProperty(PropertyName = "showDescription")] - public bool ShowDescription { get; set; } - - /** - * Whether show the icons of the link items. - */ - [JsonProperty(PropertyName = "showIcon")] - public bool ShowIcon { get; set; } - } - - internal class ButtonLayoutOptions - { - /** - * Whether to have icon and the position of the icon if icon is used. (IconPositionType.NoIcon | IconPositionType.IconToLeft | IconPositionType.IconToTop) - */ - [JsonProperty(PropertyName = "iconPositionType")] - public IconPositionType IconPositionType { get; set; } - - /** - * The display style of the button. - */ - [JsonProperty(PropertyName = "buttonTreatment")] - public ButtonTreatment ButtonTreatment { get; set; } - - /** - * The option is only available when IconPositionType.noIcon or IconPositionType.IconToLeft - * The text can be either top aligned or center aligned. (ContentAlignment.Top | ContentAlignment.Center) - */ - [JsonProperty(PropertyName = "textAlignmentVertical")] - public ContentAlignment TextAlignmentVertical { get; set; } - - /** - * The option is only available when IconPositionType.IconToTop - * The text can be either left aligned or center aligned. (ContentAlignment.Left | ContentAlignment.Center) - */ - [JsonProperty(PropertyName = "textAlignmentHorizontal")] - public ContentAlignment TextAlignmentHorizontal { get; set; } - - /** - * Number of lines of text that helps user to make the card more compact. Allowed: LinesOfText.SingleLine | LinesOfText.TwoLines; - */ - [JsonProperty(PropertyName = "linesOfText")] - public LinesOfText LinesOfText { get; set; } - - /** - * Whether show the link description text of all link items. - */ - [JsonProperty(PropertyName = "showDescription")] - public bool ShowDescription { get; set; } - } - - internal enum ImageFit - { - /** - * The image is not scaled. The image is centered and cropped within the content box. - */ - center = 0, - - /** - * The image is scaled to maintain its aspect ratio while being fully contained within the frame. The image will - * be centered horizontally and vertically within the frame. The space in the top and bottom or in the sides of - * the frame will be empty depending on the difference in aspect ratio between the image and the frame. - */ - contain = 1, - - /** - * The image is scaled to maintain its aspect ratio while filling the frame. Portions of the image will be cropped from - * the top and bottom, or from the sides, depending on the difference in aspect ratio between the image and the frame. - */ - cover = 2, - - /** - * Neither the image nor the frame are scaled. If their sizes do not match, the image will either be cropped or the - * frame will have empty space. - */ - none = 3, - - /** - * The image will be centered horizontally and vertically within the frame and maintains its aspect ratio. It will - * behave as ImageFit.center if the image's natural height or width is less than the Image frame's height or width, - * but if both natural height and width are larger than the frame it will behave as ImageFit.cover. - */ - centerCover = 4 - } - - internal enum QuickLinkType - { - File = 0, - Folder = 1, - Link = 2, - ListItem = 3, - List = 4, - DocumentLibrary = 5 - } - - internal enum QuickLinkThumbnailSourceType - { - /** - * A user customized image. - */ - Image = 1, - - /** - * A user picked icon from fabric-react icon set. - */ - FabricReactIcon = 2, - - /** - * The thumbnail of the item is automatically selected according to the layout type and item data. - */ - AutoSelect = 3 - } - - internal enum ContentAlignment - { - Top = 1, - Center = 2, - Left = 3 - } - - internal enum LinesOfText - { - /** - * The space of rendering the text is fixed to be one line. - */ - SingleLine = 1, - - /** - * The space of rendering the text is fixed to be two lines. - */ - TwoLines = 2, - - /** - * The maximum space of rendering the text is two lines. - * But if there is not enough text to fill two lines, the space is dynamic based on the text. - * I.e. The space could potentially be 0, 1, or 2 lines. - */ - TwoLinesAutoCollapse = 3 - } - - internal enum IconSize - { - Small, - Medium, - Large, - ExtraLarge, - Fill - } - - internal enum IconPositionType - { - /** - * No icon is displayed in button card. - */ - NoIcon = 1, - - /** - * Icon is displayed at left side of button card. - */ - IconToLeft = 2, - - /** - * Icon is displayed at top side of button card. - */ - IconToTop = 3, - - /** - * Only icon is displayed, there is no other content. - */ - IconOnly = 4 - } - - internal enum ButtonTreatment - { - /** - * No button treatment styles. - */ - None = 1, - - /** - * There will be an outline border around the button card. - */ - Outline = 2, - - /** - * The background of the button card will be filled with single color. - */ - FillColor = 3 - } - - internal enum QuickLinksLayout - { - /** - * The compact card layout renderers CompactCard under GridLayout. - */ - CompactCard, - - /** - * The filmstrip layout renderers TileCard under Carousel layout. - */ - FilmStrip, - - /** - * The button layout renders ButtonCard under GridLayout. - */ - Button, - - /** - * The list layout renders ButtonCard under GridLayout. - * The ButtonCard rendered in list layout is pre-configured to be single line text without outline. - */ - List, - - /** - * The grid layout renderers TileCard under GridLayout. - */ - Grid, - - /** - * The waffle layout which renders smaller ButtonCard under GridLayout. - */ - Waffle - } - #endregion - - #region SummaryLinks model - internal class SummaryLink - { - public string Title { get; set; } - public string Description { get; set; } - public string Url { get; set; } - public string ImageUrl { get; set; } - public string ImageAltText { get; set; } - public string ToolTip { get; set; } - public string Style { get; set; } - public bool OpenInNewWindow { get; set; } - } - #endregion - - #region Result class - internal class QuickLinksTransformatorResult - { - public string Properties { get; set; } - public string SearchablePlainTexts { get; set; } - public string ImageSources { get; set; } - public string Links { get; set; } - } - #endregion - - /// - /// Class used to generate quicklinks web part properties - /// - internal class QuickLinksTransformator - { - private QuickLinksWebPartProperties properties; - private ClientContext clientContext; - - #region Construction - /// - /// Instantiates the class - /// - /// Client context for the web holding the source page - public QuickLinksTransformator(ClientContext cc) - { - this.clientContext = cc; - this.properties = new QuickLinksWebPartProperties(); - - cc.Web.EnsureProperties(p => p.Id, p => p.Url); - cc.Site.EnsureProperties(p => p.Id, p => p.RootWeb); - cc.Site.RootWeb.EnsureProperty(p => p.Url); - } - #endregion - - /// - /// Generate quick links web part properties coming from a list of links - /// - /// Links coming from the summarylink web part - /// Json properties blob for QuickLinks web part tailoring - /// Properties for highlighted content web part - public QuickLinksTransformatorResult Transform(List summaryLinks, string quickLinksJsonProperties) - { - QuickLinksTransformatorResult result = new QuickLinksTransformatorResult() - { - Properties = "", - ImageSources = "", - SearchablePlainTexts = "", - Links = "" - }; - - SetupQuickLinksProperties(quickLinksJsonProperties); - - // Rationalize links - foreach (var link in summaryLinks) - { - if (string.IsNullOrEmpty(link.Title)) - { - link.Title = "Link"; // empty title should not be the case - } - if (link.Description == null) - { - link.Description = ""; - } - if (link.ImageAltText == null) - { - link.ImageAltText = ""; - } - if (link.Url == null) - { - link.Url = ""; - } - - link.Title = JsonEncode(link.Title); - link.Description = JsonEncode(link.Description); - link.ImageAltText = JsonEncode(link.ImageAltText); - } - - int i = 0; - // SearchablePlainTexts - string searchablePlainTexts = ""; - string searchablePlainTextsTitle = ""; - string searchablePlainTextsDescription = ""; - string searchablePlainTextsAltText = ""; - foreach (var link in summaryLinks) - { - searchablePlainTextsTitle = searchablePlainTextsTitle + $",\"items[{i}].title\": \"{link.Title}\""; - searchablePlainTextsDescription = searchablePlainTextsDescription + $",\"items[{i}].description\": \"{link.Description}\""; - searchablePlainTextsAltText = searchablePlainTextsAltText + $",\"items[{i}].altText\": \"{link.ImageAltText}\""; - i++; - } - searchablePlainTexts = $"{searchablePlainTextsTitle}{searchablePlainTextsDescription}{searchablePlainTextsAltText}"; - - i = 0; - // Links - string links = ""; - foreach (var link in summaryLinks) - { - links = links + $",\"items[{i}].sourceItem.url\": \"{link.Url}\""; - i++; - } - - i = 0; - // ImageSources - string imageSources = ""; - foreach (var link in summaryLinks) - { - if (link.ImageUrl != null) - { - imageSources = imageSources + $",\"items[{i}].image.url\": \"{link.ImageUrl}\""; - } - i++; - } - - i = 1; - // Items - foreach (var link in summaryLinks) - { - QuickLink q = new QuickLink() - { - Id = i, - ThumbnailType = QuickLinkThumbnailSourceType.AutoSelect, - SourceItem = new SourceItemInfo() - { - ItemType = QuickLinkType.Link, - FileExtension = "", - ProgId = "", - }, - }; - - if (!string.IsNullOrEmpty(link.Url)) - { - var linkInfo = FileLookup(link.Url); - - if (linkInfo != null && !string.IsNullOrEmpty(linkInfo["FileListId"]) && !string.IsNullOrEmpty(linkInfo["FileUniqueId"])) - { - q.SourceItem.ItemType = QuickLinkType.File; - q.SourceItem.FileExtension = System.IO.Path.GetExtension(link.Url); - } - } - - if (!string.IsNullOrEmpty(link.ImageUrl)) - { - var previewImageInfo = FileLookup(link.ImageUrl); - - if (!string.IsNullOrEmpty(previewImageInfo["FileListId"]) && !string.IsNullOrEmpty(previewImageInfo["FileUniqueId"])) - { - q.ThumbnailType = QuickLinkThumbnailSourceType.Image; - q.Image = new QuickLinkCustomizedImageInfo() - { - Guids = new GuidSet() - { - ListId = previewImageInfo["FileListId"], - UniqueId = previewImageInfo["FileUniqueId"], - WebId = previewImageInfo["FileWebId"], - SiteId = previewImageInfo["FileSiteId"], - }, - ImageFit = ImageFit.cover, - }; - } - } - - this.properties.Items.Add(q); - i++; - } - - // Prep output - result.Properties = QuickLinksProperties(); - result.SearchablePlainTexts = searchablePlainTexts; - result.ImageSources = imageSources.TrimStart(new char[] { ',' }); - result.Links = links; - - // Return the json properties for the converted web part - return result; - } - - #region Helper methods - private void SetupQuickLinksProperties(string quickLinksJsonProperties) - { - // Do default setup - // base properties setup - this.properties.LayoutId = QuickLinksLayout.List.ToString(); - this.properties.ShouldShowThumbnail = true; - this.properties.IsMigrated = true; - this.properties.HideWebPartWhenEmpty = true; - this.properties.DataProviderId = "QuickLinks"; - this.properties.WebId = this.clientContext.Web.Id.ToString(); - this.properties.SiteId = this.clientContext.Site.Id.ToString(); - this.properties.ButtonLayoutOptions = new ButtonLayoutOptions() - { - ShowDescription = false, - ButtonTreatment = ButtonTreatment.Outline, - IconPositionType = IconPositionType.IconToLeft, - TextAlignmentVertical = ContentAlignment.Center, - TextAlignmentHorizontal = ContentAlignment.Center, - LinesOfText = LinesOfText.TwoLines, - }; - this.properties.ListLayoutOptions = new ListLayoutOptions() - { - ShowDescription = true, - ShowIcon = true, - }; - this.properties.WaffleLayoutOptions = new WaffleLayoutOptions() - { - IconSize = IconSize.Medium, - OnlyShowThumbnail = true, - }; - - if (!string.IsNullOrEmpty(quickLinksJsonProperties)) - { - // Override defaults with properties obtained from the JSON blob - try - { - var parsedJson = JObject.Parse(quickLinksJsonProperties); - - if (parsedJson["isMigrated"] != null) - { - this.properties.IsMigrated = ((JValue)parsedJson["isMigrated"]).Value(); - } - if (parsedJson["layoutId"] != null) - { - this.properties.LayoutId = ((JValue)parsedJson["layoutId"]).Value(); - } - if (parsedJson["shouldShowThumbnail"] != null) - { - this.properties.ShouldShowThumbnail = ((JValue)parsedJson["shouldShowThumbnail"]).Value(); - } - if (parsedJson["hideWebPartWhenEmpty"] != null) - { - this.properties.HideWebPartWhenEmpty = ((JValue)parsedJson["hideWebPartWhenEmpty"]).Value(); - } - if (parsedJson["buttonLayoutOptions"] != null) - { - if (parsedJson["buttonLayoutOptions"]["showDescription"] != null) - { - this.properties.ButtonLayoutOptions.ShowDescription = ((JValue)parsedJson["buttonLayoutOptions"]["showDescription"]).Value(); - } - if (parsedJson["buttonLayoutOptions"]["buttonTreatment"] != null) - { - this.properties.ButtonLayoutOptions.ButtonTreatment = (ButtonTreatment)Enum.Parse(typeof(ButtonTreatment), ((JValue)parsedJson["buttonLayoutOptions"]["buttonTreatment"]).Value()); - } - if (parsedJson["buttonLayoutOptions"]["iconPositionType"] != null) - { - this.properties.ButtonLayoutOptions.IconPositionType = (IconPositionType)Enum.Parse(typeof(IconPositionType), ((JValue)parsedJson["buttonLayoutOptions"]["iconPositionType"]).Value()); - } - if (parsedJson["buttonLayoutOptions"]["textAlignmentVertical"] != null) - { - this.properties.ButtonLayoutOptions.TextAlignmentVertical = (ContentAlignment)Enum.Parse(typeof(ContentAlignment), ((JValue)parsedJson["buttonLayoutOptions"]["textAlignmentVertical"]).Value()); - } - if (parsedJson["buttonLayoutOptions"]["textAlignmentHorizontal"] != null) - { - this.properties.ButtonLayoutOptions.TextAlignmentHorizontal = (ContentAlignment)Enum.Parse(typeof(ContentAlignment), ((JValue)parsedJson["buttonLayoutOptions"]["textAlignmentHorizontal"]).Value()); - } - if (parsedJson["buttonLayoutOptions"]["linesOfText"] != null) - { - this.properties.ButtonLayoutOptions.LinesOfText = (LinesOfText)Enum.Parse(typeof(LinesOfText), ((JValue)parsedJson["buttonLayoutOptions"]["linesOfText"]).Value()); - } - } - if (parsedJson["listLayoutOptions"] != null) - { - if (parsedJson["listLayoutOptions"]["showDescription"] != null) - { - this.properties.ListLayoutOptions.ShowDescription = ((JValue)parsedJson["listLayoutOptions"]["showDescription"]).Value(); - } - if (parsedJson["listLayoutOptions"]["showIcon"] != null) - { - this.properties.ListLayoutOptions.ShowIcon = ((JValue)parsedJson["listLayoutOptions"]["showIcon"]).Value(); - } - } - if (parsedJson["waffleLayoutOptions"] != null) - { - if (parsedJson["waffleLayoutOptions"]["iconSize"] != null) - { - this.properties.WaffleLayoutOptions.IconSize = (IconSize)Enum.Parse(typeof(IconSize), ((JValue)parsedJson["waffleLayoutOptions"]["iconSize"]).Value()); - } - if (parsedJson["waffleLayoutOptions"]["onlyShowThumbnail"] != null) - { - this.properties.WaffleLayoutOptions.OnlyShowThumbnail = ((JValue)parsedJson["waffleLayoutOptions"]["onlyShowThumbnail"]).Value(); - } - } - } - catch (Exception) - { - // Let's not fail transformation in this case but do log this - // TODO: Find a replacement - // LogWarning(string.Format(LogStrings.Warning_OverridingQuickLinksDefaultsFailed, ex.Message), LogStrings.Heading_BuiltInFunctions); - } - } - } - - private string JsonEncode(string input) - { - return HttpUtility.JavaScriptStringEncode(input); - } - - private Dictionary FileLookup(string serverRelativeFilePath) - { - - bool stop = false; - if (string.IsNullOrEmpty(serverRelativeFilePath)) - { - stop = true; - } - - this.clientContext.Web.EnsureProperty(p => p.ServerRelativeUrl); - - // Check if this url is pointing to content living in this site - if (!stop && !serverRelativeFilePath.StartsWith(this.clientContext.Web.ServerRelativeUrl, StringComparison.InvariantCultureIgnoreCase)) - { - stop = true; - } - - Dictionary results = new Dictionary(); - - if (stop) - { - results.Add("FileListId", ""); - results.Add("FileUniqueId", ""); - results.Add("FileWebId", ""); - results.Add("FileSiteId", ""); - return results; - } - - try - { - var file = this.clientContext.Web.GetFileByServerRelativeUrl(serverRelativeFilePath); - this.clientContext.Load(file, p => p.UniqueId, p => p.ListId); - this.clientContext.ExecuteQueryRetry(); - - results.Add("FileListId", $"{{{file.ListId.ToString()}}}"); - results.Add("FileUniqueId", file.UniqueId.ToString()); - results.Add("FileWebId", this.properties.WebId); - results.Add("FileSiteId", this.properties.SiteId); - return results; - } - catch (ServerException ex) - { - if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - { - // Provided image was not found, should not happen - return null; - } - else - { - throw; - } - } - } - - internal string QuickLinksProperties() - { - // Don't serialize null values - var jsonSerializerSettings = new JsonSerializerSettings() - { - MissingMemberHandling = MissingMemberHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - - var json = JsonConvert.SerializeObject(this.properties, jsonSerializerSettings); - - return json; - } - #endregion - } -} - diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SitePagesBuiltInFieldIds.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SitePagesBuiltInFieldIds.cs deleted file mode 100644 index 05789fdbe1..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SitePagesBuiltInFieldIds.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - /// - /// Defines a list of IDs for built in fields of Site Pages library - /// - public class SitePagesBuiltInFieldIds - { - /// - /// Wiki Content - /// - public static readonly Guid WikiFieldID = new Guid("c33527b4-d920-4587-b791-45024d00068a"); - - /// - /// Authoring Canvas Content - /// - public static readonly Guid CanvasContent1ID = new Guid("4966388e-6e12-4bc6-8990-5b5b66153eae"); - - /// - /// Banner Image URL - /// - public static readonly Guid BannerImageUrlID = new Guid("5baf6db5-9d25-4738-b15e-db5789298e82"); - - /// - /// Description - /// - public static readonly Guid DescriptionID = new Guid("3f155110-a6a2-4d70-926c-94648101f0e8"); - - /// - /// Promoted State - /// - public static readonly Guid PromotedStateID = new Guid("f5ad16a2-85be-46b2-b5f0-2bb8b4a5074a"); - - /// - /// First Published Date - /// - public static readonly Guid FirstPublishedDateID = new Guid("c84f8697-331e-457d-884a-c4fb8f30ea74"); - - /// - /// Page Layout Content - /// - public static readonly Guid LayoutWebpartsContentID = new Guid("261075db-0525-4fb8-a6ea-772014186599"); - - /// - /// Author Byline - /// - public static readonly Guid AuthorBylineID = new Guid("1a7348e7-1bb7-4a47-9790-088e7cb20b58"); - - /// - /// Topic header - /// - public static readonly Guid TopicHeaderID = new Guid("d60d65ff-ff42-4044-a684-ac3f7a5e598c"); - - /// - /// Site Page Flags - /// - public static readonly Guid SPSitePageFlagsID = new Guid("9de685c5-fdf5-4319-b987-3edf55efb36f"); - - /// - /// Original Source Url - /// - public static readonly Guid OriginalSourceUrlID = new Guid("0e7b982f-698a-4d0c-aacb-f16906f66d30"); - - /// - /// Original Source Site ID - /// - public static readonly Guid OriginalSourceSiteIdID = new Guid("36193413-dd5c-4096-8c1e-1b40098b9ba3"); - - /// - /// Original Source Web ID - /// - public static readonly Guid OriginalSourceWebIdID = new Guid("3477a5bc-c605-4b2e-a7c1-8db8f13c017e"); - - /// - /// Original Source List ID - /// - public static readonly Guid OriginalSourceListIdID = new Guid("139da674-dbf6-439f-98e0-4eb05fa9a669"); - - /// - /// Original Source Item ID - /// - public static readonly Guid OriginalSourceItemIdID = new Guid("91e86a43-75f2-426f-80da-35edfb47d55d"); - - /// - /// Compliance Asset Id - /// - public static readonly Guid ComplianceAssetId = new Guid("3a6b296c-3f50-445c-a13f-9c679ea9dda3"); - - /// - /// _SPCallToAction - /// - public static readonly Guid SPCallToAction = new Guid("9889a80f-c9ec-41d8-a359-ac5fb5c4cfa2"); - - /// - /// SharedWithUsers - /// - public static readonly Guid SharedWithUsers = new Guid("ef991a83-108d-4407-8ee5-ccc0c3d836b9"); - - /// - /// SharedWithDetails - /// - public static readonly Guid SharedWithDetails = new Guid("d3c9caf7-044c-4c71-ae64-092981e54b33"); - - /// - /// _ComplianceFlags - /// - public static readonly Guid ComplianceFlags = new Guid("ccc1037f-f65e-434a-868e-8c98af31fe29"); - - /// - /// _ComplianceTag - /// - public static readonly Guid ComplianceTag = new Guid("d4b6480a-4bed-4094-9a52-30181ea38f1d"); - - /// - /// _ComplianceTagWrittenTime - /// - public static readonly Guid ComplianceTagWrittenTime = new Guid("92be610e-ddbb-49f4-b3b1-5c2bc768df8f"); - - /// - /// _ComplianceTagUserId - /// - public static readonly Guid ComplianceTagUserId = new Guid("418d7676-2d6f-42cf-a16a-e43d2971252a"); - - /// - /// _CommentCount - /// - public static readonly Guid CommentCount = new Guid("d307dff3-340f-44a2-9f4b-fbfe1ba07459"); - - /// - /// _LikeCount - /// - public static readonly Guid LikeCount = new Guid("db8d9d6d-dc9a-4fbd-85f3-4a753bfdc58c"); - - /// - /// _DisplayName - /// - public static readonly Guid DisplayName = new Guid("1a53ab5a-11f9-4b92-a377-8cfaaf6ba7be"); - - // Hashset to contain the whole list of built in fields - private static HashSet builtInFieldsHashSet; - private static object builtInFieldsHashSetSyncLock = new object(); - - /// - /// This method returns a Boolean value that specifies whether or not the current object matches the specified GUID. This value is used as a file identifier for an object that is associated with a Windows SharePoint Services Web site. - /// - /// - /// - /// Returns a GUID. - /// - /// File identifier. - public static bool Contains(Guid fid) - { - if (builtInFieldsHashSet == null) - { - lock (builtInFieldsHashSetSyncLock) - { - if (builtInFieldsHashSet == null) - { - builtInFieldsHashSet = new HashSet( - new Guid[] { - WikiFieldID, - CanvasContent1ID, - BannerImageUrlID, - DescriptionID, - PromotedStateID, - FirstPublishedDateID, - LayoutWebpartsContentID, - AuthorBylineID, - TopicHeaderID, - SPSitePageFlagsID, - OriginalSourceUrlID, - OriginalSourceSiteIdID, - OriginalSourceWebIdID, - OriginalSourceListIdID, - OriginalSourceItemIdID, - ComplianceAssetId, - SPCallToAction, - SharedWithUsers, - SharedWithDetails, - ComplianceFlags, - ComplianceTag, - ComplianceTagWrittenTime, - ComplianceTagUserId, - CommentCount, - LikeCount, - DisplayName - }); - } - } - } - - if (builtInFieldsHashSet != null) - { - return builtInFieldsHashSet.Contains(fid); - } - else - { - return false; - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SummaryLinksHtmlTransformator.cs b/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SummaryLinksHtmlTransformator.cs deleted file mode 100644 index 738441cbf0..0000000000 --- a/src/sdk/PnP.Core.Transformation.SharePoint/Utilities/SummaryLinksHtmlTransformator.cs +++ /dev/null @@ -1,233 +0,0 @@ -using AngleSharp.Dom; -using AngleSharp.Html.Parser; -using System.Collections.Generic; -using System.Linq; - -namespace PnP.Core.Transformation.SharePoint.Utilities -{ - /// - /// This class is used to rewrite the html coming out of the SummaryLinks web part - /// - internal class SummaryLinksHtmlTransformator - { - private HtmlParser parser; - private string webPartTitle; - - #region Construction - /// - /// Default constructor - /// - public SummaryLinksHtmlTransformator() - { - // Instantiate the AngleSharp Html parser - parser = new HtmlParser(new HtmlParserOptions() { IsEmbedded = true }); - } - #endregion - - #region Properties - /// - /// Title of the summary links web part - /// - public string WebPartTitle - { - get - { - return this.webPartTitle; - } - set - { - this.webPartTitle = value; - } - } - #endregion - - /// - /// Transforms the passed summarylinks html to be usable by the client side text part - /// - /// Summarylinks html to be transformed - /// - /// Html that can be used and edited via the client side text part - public string Transform(string text, bool usePlaceHolder) - { - using (var document = this.parser.ParseDocument(text)) - { - using (var newDocument = this.parser.ParseDocument("")) - { - // Iterate over the divs - var divs = document.QuerySelectorAll("div").Where(p => p.GetAttribute("title") == "_link"); - IElement header = null; - IElement list = null; - foreach (var div in divs) - { - var summaryLinkHeader = div.Children.Where(p => p.GetAttribute("title") == "_groupstyle").FirstOrDefault(); - if (summaryLinkHeader != null) - { - // Header - header = newDocument.CreateElement("div"); - var strong = newDocument.CreateElement("strong"); - var title = div.Children.Where(p => p.GetAttribute("title") == "_title").FirstOrDefault(); - if (title != null) - { - strong.TextContent = title.TextContent; - } - header.AppendChild(strong); - newDocument.DocumentElement.Children[1].AppendChild(header); - - // reset list - list = null; - } - else - { - // Link - if (list == null) - { - list = newDocument.CreateElement("ul"); - - if (header == null) - { - header = newDocument.CreateElement("div"); - newDocument.DocumentElement.Children[1].AppendChild(header); - } - - header.AppendChild(list); - } - - - // Link - var title = div.Children.Where(p => p.GetAttribute("title") == "_title").FirstOrDefault(); - var linkUrl = div.Children.Where(p => p.GetAttribute("title") == "_linkurl").FirstOrDefault(); - var openInNewWindow = div.Children.Where(p => p.GetAttribute("title") == "_openinnewwindow").FirstOrDefault(); - - if (linkUrl != null && title != null) - { - // ListItem - var item = newDocument.CreateElement("li"); - - var link = newDocument.CreateElement("a"); - var href = linkUrl.Children.Where(p => p.HasAttribute("href")).FirstOrDefault(); - link.SetAttribute("href", href != null ? href.GetAttribute("href") : ""); - link.TextContent = title.TextContent; - - if (openInNewWindow != null) - { - if (bool.TryParse(openInNewWindow.TextContent, out bool openInNewWindowValue)) - { - if (openInNewWindowValue) - { - link.SetAttribute("target", "_blank"); - link.SetAttribute("data-interception", "off"); - } - } - } - - item.AppendChild(link); - list.AppendChild(item); - } - } - } - - // Return the transformed html - if (newDocument.DocumentElement.Children.Length > 1) - { - string htmlContent = newDocument.DocumentElement.Children[1].InnerHtml; - - // Add the web part title is that's required - if (!string.IsNullOrEmpty(this.webPartTitle)) - { - htmlContent = $"

    {this.webPartTitle}

    {htmlContent}"; - } - - return htmlContent; - } - else - { - if (!string.IsNullOrEmpty(this.webPartTitle)) - { - text = $"

    {this.webPartTitle}

    {text}
    "; - } - - return text; - } - } - } - } - - - /// - /// Transforms the passed summarylinks html a list of links - /// - /// Summarylinks html to be transformed - /// List of links - public List GetLinks(string text) - { - List links = new List(); - - using (var document = this.parser.ParseDocument(text)) - { - // Iterate over the divs - var divs = document.QuerySelectorAll("div").Where(p => p.GetAttribute("title") == "_link"); - foreach (var div in divs) - { - var summaryLinkHeader = div.Children.Where(p => p.GetAttribute("title") == "_groupstyle").FirstOrDefault(); - if (summaryLinkHeader != null) - { - // no support for headers - } - else - { - // Link - var linkTitle = div.Children.Where(p => p.GetAttribute("title") == "_title").FirstOrDefault(); - var linkDescription = div.Children.Where(p => p.GetAttribute("title") == "_description").FirstOrDefault(); - var linkUrl = div.Children.Where(p => p.GetAttribute("title") == "_linkurl").FirstOrDefault(); - var linkImageUrl = div.Children.Where(p => p.GetAttribute("title") == "_imageurl").FirstOrDefault(); - var linkImageAltText = div.Children.Where(p => p.GetAttribute("title") == "_imageurlalttext").FirstOrDefault(); - var linkToolTip = div.Children.Where(p => p.GetAttribute("title") == "_linktooltip").FirstOrDefault(); - var linkStyle = div.Children.Where(p => p.GetAttribute("title") == "_style").FirstOrDefault(); - var openInNewWindow = div.Children.Where(p => p.GetAttribute("title") == "_openinnewwindow").FirstOrDefault(); - - SummaryLink l = new SummaryLink() - { - Title = linkTitle?.TextContent, - Description = linkDescription?.TextContent, - ImageAltText = linkImageAltText?.TextContent, - ToolTip = linkToolTip?.TextContent, - Style = linkStyle?.TextContent - }; - - // Handle Url fields - if (linkUrl != null) - { - var href = linkUrl.Children.Where(p => p.HasAttribute("href")).FirstOrDefault(); - if (href != null) - { - l.Url = href.GetAttribute("href"); - } - } - if (linkImageUrl != null) - { - var href = linkImageUrl.Children.Where(p => p.HasAttribute("href")).FirstOrDefault(); - if (href != null) - { - l.ImageUrl = href.GetAttribute("href"); - } - } - - // Handle bool fields - if (openInNewWindow != null) - { - if (bool.TryParse(openInNewWindow.TextContent, out bool openInNewWindowValue)) - { - l.OpenInNewWindow = openInNewWindowValue; - } - } - - // Add to collection - links.Add(l); - } - } - } - - return links; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation.SharePoint/nugeticon.png b/src/sdk/PnP.Core.Transformation.SharePoint/nugeticon.png deleted file mode 100644 index 14962c3be3..0000000000 Binary files a/src/sdk/PnP.Core.Transformation.SharePoint/nugeticon.png and /dev/null differ diff --git a/src/sdk/PnP.Core.Transformation.Test/.editorconfig b/src/sdk/PnP.Core.Transformation.Test/.editorconfig deleted file mode 100644 index e5acfbb32f..0000000000 --- a/src/sdk/PnP.Core.Transformation.Test/.editorconfig +++ /dev/null @@ -1,3 +0,0 @@ - -[*.{cs,vb}] -dotnet_diagnostic.CA2007.severity=none \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.Test/.gitignore b/src/sdk/PnP.Core.Transformation.Test/.gitignore deleted file mode 100644 index 4c9a757a25..0000000000 --- a/src/sdk/PnP.Core.Transformation.Test/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore file that specifies which test environment to load -**/env.txt - -# Ignore user specific appsettings -**/appsettings.*.json \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation.Test/PnP.Core.Transformation.Test.csproj b/src/sdk/PnP.Core.Transformation.Test/PnP.Core.Transformation.Test.csproj deleted file mode 100644 index 08b0365fa4..0000000000 --- a/src/sdk/PnP.Core.Transformation.Test/PnP.Core.Transformation.Test.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net10.0 - - false - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - diff --git a/src/sdk/PnP.Core.Transformation.Test/SetupTests.cs b/src/sdk/PnP.Core.Transformation.Test/SetupTests.cs deleted file mode 100644 index 638126d786..0000000000 --- a/src/sdk/PnP.Core.Transformation.Test/SetupTests.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using PnP.Core.Transformation.Services.Builder.Configuration; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Services; - -namespace PnP.Core.Transformation.Test -{ - [TestClass] - public class SetupTests - { - - [TestMethod] - public void DefaultServices() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddPnPTransformation() - .WithTargetPageUriResolver() - .WithMappingProvider(); - - var provider = services.BuildServiceProvider(); - - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(DefaultPageTransformator)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(InMemoryTransformationStateManager)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(InProcessTransformationExecutor)); - } - - [TestMethod] - public void CustomServices() - { - var services = new ServiceCollection(); - services.AddLogging(); - - services.AddPnPTransformation(o => o.DisableTelemetry = true, p => p.CopyPageMetadata = true) - .WithPageOptions(o => o.DisablePageComments = true) - - .WithMappingProvider() - - .WithPageTransformator() - - .AddPagePreTransformation() - .AddPagePreTransformation() - .AddPagePostTransformation() - .AddPagePostTransformation() - - .WithTransformationDistiller() - .WithTransformationStateManager() - .WithTransformationExecutor() - .WithTargetPageUriResolver(); - - var provider = services.BuildServiceProvider(); - - var pnpTransformationOptions = provider.GetRequiredService>().Value; - Assert.IsTrue(pnpTransformationOptions.DisableTelemetry); - - var transformationOptions = provider.GetRequiredService>().Value; - Assert.IsTrue(transformationOptions.DisablePageComments); - - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - - Assert.AreEqual(2, provider.GetServices().Count()); - Assert.AreEqual(2, provider.GetServices().Count()); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - Assert.IsInstanceOfType(provider.GetRequiredService(), typeof(Mock)); - } - - #region Mock - - private class Mock : - IMappingProvider, - IPageTransformator, - ITransformationDistiller, - ITransformationStateManager, - ITransformationExecutor, - IHtmlMappingProvider, - IPagePreTransformation, - IPagePostTransformation, - ITargetPageUriResolver - { - Task IMappingProvider.MapAsync(MappingProviderInput input, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task IPageTransformator.TransformAsync(PageTransformationTask task, CancellationToken token) - { - throw new NotImplementedException(); - } - - IAsyncEnumerable ITransformationDistiller.GetPageTransformationTasksAsync(ISourceProvider sourceProvider, PnPContext targetContext, - CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.WriteProcessStatusAsync(TransformationProcessStatus status, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.WriteTaskStatusAsync(TransformationProcessTaskStatus status, CancellationToken token) - { - throw new NotImplementedException(); - } - - IAsyncEnumerable ITransformationStateManager.GetProcessTasksStatus(Guid processId, TasksStatusQuery query, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.ReadProcessStatusAsync(Guid processId, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.ReadTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.RemoveProcessStatusAsync(Guid processId, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationStateManager.RemoveTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationExecutor.CreateTransformationProcessAsync(CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITransformationExecutor.LoadTransformationProcessAsync(Guid processId, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task IHtmlMappingProvider.MapHtmlAsync(HtmlMappingProviderInput input, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task IPagePreTransformation.PreTransformAsync(PagePreTransformationContext context, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task IPagePostTransformation.PostTransformAsync(PagePostTransformationContext context, CancellationToken token) - { - throw new NotImplementedException(); - } - - Task ITargetPageUriResolver.ResolveAsync(ISourceItem sourceItem, PnPContext targetContext, CancellationToken token) - { - throw new NotImplementedException(); - } - } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation/.gitignore b/src/sdk/PnP.Core.Transformation/.gitignore deleted file mode 100644 index 99ed0d431c..0000000000 --- a/src/sdk/PnP.Core.Transformation/.gitignore +++ /dev/null @@ -1 +0,0 @@ -notes.txt diff --git a/src/sdk/PnP.Core.Transformation/AssemblyInfo.cs b/src/sdk/PnP.Core.Transformation/AssemblyInfo.cs deleted file mode 100644 index c94e9d3cd6..0000000000 --- a/src/sdk/PnP.Core.Transformation/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Resources; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("PnP.Core.Transformation.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] -[assembly: InternalsVisibleTo("PnP.Core.Transformation.SharePoint.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] - -[assembly: NeutralResourcesLanguage("en")] \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Extensions/DictionaryExtensions.cs b/src/sdk/PnP.Core.Transformation/Extensions/DictionaryExtensions.cs deleted file mode 100644 index e87034cf25..0000000000 --- a/src/sdk/PnP.Core.Transformation/Extensions/DictionaryExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Extensions -{ - /// - /// Extension class to provide additional functionalities on top of Dictionary - /// - internal static class DictionaryExtensions - { - internal static Dictionary Merge(this Dictionary source, Dictionary merger) - { - var result = new Dictionary(); - - foreach (var item in source) - { - result.Add(item.Key, item.Value); - } - - if (merger != null) - { - foreach (var item in merger) - { - if (!result.ContainsKey(item.Key)) - { - result.Add(item.Key, item.Value); - } - } - } - - return result; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Extensions/StringExtensions.cs b/src/sdk/PnP.Core.Transformation/Extensions/StringExtensions.cs deleted file mode 100644 index 83e16e98f5..0000000000 --- a/src/sdk/PnP.Core.Transformation/Extensions/StringExtensions.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Linq; -using System.Text; - -namespace PnP.Core.Transformation -{ - /// - /// Class holding extension methods on the System.string class - /// - public static class StringExtensions - { - /// - /// Determines if a string exists in another string regardless of casing - /// - /// original string - /// string to compare with - /// - public static bool ContainsIgnoringCasing(this string value, string comparedWith) - { - return value.IndexOf(comparedWith, StringComparison.InvariantCultureIgnoreCase) >= 0; - } - - ///// - ///// Prepends string to another including null checking - ///// - ///// - ///// - ///// - //public static string PrependIfNotNull(this string value, string prependString) - //{ - // if (!string.IsNullOrEmpty(value) && !value.ContainsIgnoringCasing(prependString)) - // { - // value = prependString + value; - // } - - // return value; // Fall back - //} - - ///// - ///// Removes a relative section of by string where context not available - ///// - ///// - ///// - //public static string StripRelativeUrlSectionString(this string value) - //{ - // if (!string.IsNullOrEmpty(value)) - // { - // var sitesColl = "/sites/"; - // var teamsColl = "/teams/"; - // var containsSites = value.IndexOf(sitesColl, StringComparison.InvariantCultureIgnoreCase); - // var containsTeams = value.IndexOf(teamsColl, StringComparison.InvariantCultureIgnoreCase); - // if (containsSites > -1 || containsTeams > -1) - // { - // if (containsSites > -1) - // { - // var result = value.TrimStart(sitesColl.ToCharArray()); - // if (result.IndexOf('/') > -1) - // { - // return result.Substring(result.IndexOf('/')); - // } - // } - // else if (containsTeams > -1) - // { - // var result = value.TrimStart(teamsColl.ToCharArray()); - // if (result.IndexOf('/') > -1) - // { - // return result.Substring(result.IndexOf('/')); - // } - // } - // } - // } - - // return value; - //} - - /// - /// Gets base url from string - /// - /// - /// - public static string GetBaseUrl(this string url) - { - try - { - if (!string.IsNullOrEmpty(url) && (url.ContainsIgnoringCasing("https://") || url.ContainsIgnoringCasing("http://"))) - { - Uri siteUri = new Uri(url); - string host = $"{siteUri.Scheme}://{siteUri.DnsSafeHost}"; - return host; - } - } - catch (Exception) - { - //Swallow - } - - return string.Empty; - } - - /// - /// Get type in short form - /// - /// - /// - public static string GetTypeShort(this string typeValue) - { - string name = typeValue; - var typeSplit = typeValue.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); - if (typeSplit.Length > 0) - { - name = typeSplit[0]; - } - - return $"{name}"; - } - - /// - /// Gets the user name from a provided login name - /// - /// Login name - /// User name - public static string GetUserName(this string loginName) - { - // Possible inputs - // joe@contoso.com - // i:0#.w|contoso\joe - // i:0#.f|membership|joe@contoso.com - - if (!loginName.Contains("|")) - { - return loginName; - } - - var parts = loginName.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - return parts[parts.Length - 1]; - } - - /// - /// Gets classname from type - /// - /// - /// - public static string InferClassNameFromNameSpace(this string typeName) - { - string shortType = typeName; - string className = string.Empty; - if (typeName.Contains(",")) - { - shortType = typeName.GetTypeShort(); - } - - var typeShortSplit = shortType.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries); - if (typeShortSplit.Length > 0) - { - className = typeShortSplit.Last(); - } - - return className; - } - - /// - /// Returns a new string in which all occurrences of a specified string in the current instance are replaced with another - /// specified string according the type of search to use for the specified string. - /// Copied from https://stackoverflow.com/questions/6275980/string-replace-ignoring-case - /// - /// The string performing the replace method. - /// The string to be replaced. - /// The string replace all occurrences of . - /// If value is equal to null, than all occurrences of will be removed from the . - /// One of the enumeration values that specifies the rules for the search. - /// A string that is equivalent to the current string except that all instances of are replaced with . - /// If is not found in the current instance, the method returns the current instance unchanged. - public static string Replace(this string str, string oldValue, string @newValue, StringComparison comparisonType) - { - - // Check inputs. - if (str == null) - { - // Same as original .NET C# string.Replace behavior. - throw new ArgumentNullException(nameof(str)); - } - if (str.Length == 0) - { - // Same as original .NET C# string.Replace behavior. - return str; - } - if (oldValue == null) - { - // Same as original .NET C# string.Replace behavior. - throw new ArgumentNullException(nameof(oldValue)); - } - if (oldValue.Length == 0) - { - // Same as original .NET C# string.Replace behavior. - throw new ArgumentException("String cannot be of zero length."); - } - - // Prepare string builder for storing the processed string. - // Note: StringBuilder has a better performance than String by 30-40%. - StringBuilder resultStringBuilder = new StringBuilder(str.Length); - - // Analyze the replacement: replace or remove. - bool isReplacementNullOrEmpty = string.IsNullOrEmpty(@newValue); - - // Replace all values. - const int valueNotFound = -1; - int foundAt; - int startSearchFromIndex = 0; - while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound) - { - - // Append all characters until the found replacement. - int @charsUntilReplacment = foundAt - startSearchFromIndex; - bool isNothingToAppend = @charsUntilReplacment == 0; - if (!isNothingToAppend) - { - resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilReplacment); - } - - // Process the replacement. - if (!isReplacementNullOrEmpty) - { - resultStringBuilder.Append(@newValue); - } - - // Prepare start index for the next search. - // This needed to prevent infinite loop, otherwise method always start search - // from the start of the string. For example: if an oldValue == "EXAMPLE", newValue == "example" - // and comparisonType == "any ignore case" will conquer to replacing: - // "EXAMPLE" to "example" to "example" to "example" … infinite loop. - startSearchFromIndex = foundAt + oldValue.Length; - if (startSearchFromIndex == str.Length) - { - // It is end of the input string: no more space for the next search. - // The input string ends with a value that has already been replaced. - // Therefore, the string builder with the result is complete and no further action is required. - return resultStringBuilder.ToString(); - } - } - - // Append the last part to the result. - int @charsUntilStringEnd = str.Length - startSearchFromIndex; - resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilStringEnd); - - return resultStringBuilder.ToString(); - } - - /// - /// Prepares a string for json inclusion - /// Copied from https://stackoverflow.com/questions/1242118/how-to-escape-json-string - /// - /// string to prepare - /// json ready string - public static string CleanForJSON(this string s) - { - if (s == null || s.Length == 0) - { - return ""; - } - - char c = '\0'; - int i; - int len = s.Length; - StringBuilder sb = new StringBuilder(len + 4); - string t; - - for (i = 0; i < len; i += 1) - { - c = s[i]; - switch (c) - { - case '\\': - case '"': - sb.Append('\\'); - sb.Append(c); - break; - case '/': - sb.Append('\\'); - sb.Append(c); - break; - case '\b': - sb.Append("\\b"); - break; - case '\t': - sb.Append("\\t"); - break; - case '\n': - sb.Append("\\n"); - break; - case '\f': - sb.Append("\\f"); - break; - case '\r': - sb.Append("\\r"); - break; - default: - if (c < ' ') - { -#pragma warning disable CA2241 - t = "000" + string.Format("X", c); -#pragma warning restore CA2241 - sb.Append("\\u" + t.Substring(t.Length - 4)); - } - else - { - sb.Append(c); - } - break; - } - } - return sb.ToString(); - } - - /// - /// Returns a string enriched with the current Task Correlation ID - /// - /// The ID to correlate the string to - /// The string to correlate - /// - public static string CorrelateString(this string input, Guid taskId) - { - return $"[{taskId}] {input}"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Extensions/UserExtensions.cs b/src/sdk/PnP.Core.Transformation/Extensions/UserExtensions.cs deleted file mode 100644 index 1a1c339efa..0000000000 --- a/src/sdk/PnP.Core.Transformation/Extensions/UserExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using PnP.Core.Model.Security; -using PnP.Core.Transformation.Model; - -namespace PnP.Core.Transformation -{ - /// - /// Class holding extension methods for the PnP.Core.Model.Security.ISharePointUser class - /// - internal static class UserExtensions - { - /// - /// Determines if a string exists in another string regardless of casing - /// - /// original user - /// The user transformed into a User Entity - internal static UserEntity ToUserEntity(this ISharePointUser user) - { - var result = new UserEntity - { - Id = user.Id.ToString(), - Upn = user.UserPrincipalName, - Name = user.Title, - // result.Role = ??? - LoginName = user.LoginName, - IsGroup = user.PrincipalType == PrincipalType.SharePointGroup || - user.PrincipalType == PrincipalType.SecurityGroup - }; - - return result; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/CanvasControl.cs b/src/sdk/PnP.Core.Transformation/Model/CanvasControl.cs deleted file mode 100644 index 797a517767..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/CanvasControl.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines an abstraction for a Canvas Control in a Column of a Section of a target modern page - /// - public class CanvasControl - { - private Dictionary properties = new Dictionary(); - - /// - /// Type of the canvas control - /// - public CanvasControlType ControlType { get; set; } - - /// - /// Ordinal position of the canvas control - /// - public int Order { get; set; } - - /// - /// Properties of the canvas control - /// - public object this[string index] - { - get - { - if (this.properties.ContainsKey(index)) - { - return this.properties[index]; - } - else - { - return null; - } - } - set - { - this.properties[index] = value; - } - } - } - - /// - /// Defines the flavors of canvas controls - /// - public enum CanvasControlType - { - /// - /// Client-side Text - /// - ClientSideText, - /// - /// Custom client-side web part - /// - CustomClientSideWebPart, - /// - /// Native default web part - /// - DefaultWebPart - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/Column.cs b/src/sdk/PnP.Core.Transformation/Model/Column.cs deleted file mode 100644 index 934cbeeec4..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/Column.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines an abstraction for a Column in a Section of a target modern page - /// - public class Column - { - /// - /// The list of Canvas Controls controls in the column - /// - public List Controls { get; } = new List(); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/FieldData.cs b/src/sdk/PnP.Core.Transformation/Model/FieldData.cs deleted file mode 100644 index a6ac6da484..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/FieldData.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines a field to copy - /// - public class FieldData - { - /// - /// Internal name of the field - /// - public string Name { get; set; } - - /// - /// Id of the field - /// - public Guid Id { get; set; } - - /// - /// Type of the field - /// - public string Type { get; set; } - - /// - /// Value of the field instance - /// - public object Value { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/ListItemPermission.cs b/src/sdk/PnP.Core.Transformation/Model/ListItemPermission.cs deleted file mode 100644 index 6229ae243c..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/ListItemPermission.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Class used to temporarily hold list item level permissions that need to be re-applied - /// - public class ListItemPermission - { - /// - /// List of members with role assignments - /// - public Dictionary Members { get; } = new Dictionary(); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/Page.cs b/src/sdk/PnP.Core.Transformation/Model/Page.cs deleted file mode 100644 index 0c51d567ab..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/Page.cs +++ /dev/null @@ -1,67 +0,0 @@ -using PnP.Core.Model.SharePoint; -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines an abstraction for a target modern page - /// - public class Page - { - /// - /// Defines if the the page to create should be the Home Page of the site - /// - public bool IsHomePage { get; set; } - - /// - /// The Author of the page - /// - public string Author { get; set; } - - /// - /// The last Editor of the page - /// - public string Editor { get; set; } - - /// - /// The Creation date time of the page - /// - public DateTime Created { get; set; } - - /// - /// The last Update date time of the page - /// - public DateTime Modified { get; set; } - - /// - /// The name of the page - /// - public string PageName { get; set; } - - /// - /// The parent Folder of the page, if any - /// - public string Folder { get; set; } - - /// - /// Configuration of the page header to apply - /// - public IPageHeader PageHeader { get; set; } - - /// - /// The title of the target page - /// - public string PageTitle { get; set; } - - /// - /// Sets the page author in the page header similar to the original page author - /// - public bool SetAuthorInPageHeader { get; set; } - - /// - /// The sections of the page - /// - public List
    Sections { get; } = new List
    (); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/Section.cs b/src/sdk/PnP.Core.Transformation/Model/Section.cs deleted file mode 100644 index 57b462c765..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/Section.cs +++ /dev/null @@ -1,36 +0,0 @@ -using PnP.Core.Model.SharePoint; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines an abstraction for a target section of a modern page - /// - public class Section - { - /// - /// The canvas template for the sections to render in the target modern page - /// - public CanvasSectionTemplate CanvasTemplate { get; set; } - - /// - /// Defines the order of the section in the target page - /// - public int Order { get; set; } - - /// - /// The columns in the current section - /// - public List Columns { get; set; } = new List(); - - /// - /// The emphasis for the zone - /// - public int ZoneEmphasis { get; set; } - - /// - /// The emphasis for the vertical zone - /// - public int VerticalSectionZoneEmphasis { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/SharePointConstants.cs b/src/sdk/PnP.Core.Transformation/Model/SharePointConstants.cs deleted file mode 100644 index b27ec53b6f..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/SharePointConstants.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace PnP.Core.Transformation.Model -{ - /// - /// Defines a set of standard field names for SharePoint items - /// - internal static class SharePointConstants - { - public const string ModifiedField = "Modified"; - public const string ModifiedByField = "Editor"; - public const string CreatedField = "Created"; - public const string CreatedByField = "Author"; - public const string PromotedStateField = "PromotedState"; - public const string FirstPublishedDateField = "FirstPublishedDate"; - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/UserEntity.cs b/src/sdk/PnP.Core.Transformation/Model/UserEntity.cs deleted file mode 100644 index d07d6036f2..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/UserEntity.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Class to hold information about a given user - /// - public class UserEntity - { - /// - /// Id of the user - /// - [JsonPropertyName("id")] - public string Id { get; set; } - - /// - /// Upn of the user - /// - [JsonPropertyName("upn")] - public string Upn { get; set; } - - /// - /// Name of the user - /// - [JsonPropertyName("name")] - public string Name { get; set; } - - /// - /// Role of the user - /// - [JsonPropertyName("role")] - public string Role { get; set; } - - /// - /// Loginname of the user - /// - [JsonIgnore] - public string LoginName { get; set; } - - /// - /// Is this a group? - /// - [JsonIgnore] - public bool IsGroup { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/WebPartEntity.cs b/src/sdk/PnP.Core.Transformation/Model/WebPartEntity.cs deleted file mode 100644 index 777c0f11d0..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/WebPartEntity.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Model -{ - /// - /// Entity to describe a web part on a wiki or webpart page - /// - public class WebPartEntity - { - /// - /// Web part type - /// - public string Type { get; set; } - - /// - /// Web part id - /// - public Guid Id { get; set; } - - /// - /// Web part server control id - /// - public string ServerControlId { get; set; } - - /// - /// Web part zone id - /// - public string ZoneId { get; set; } - - /// - /// Is this a visible web part or not - /// - public bool Hidden { get; set; } - - /// - /// Is this web part closed or not - /// - public bool IsClosed { get; set; } - - /// - /// Title of the web part - /// - public string Title { get; set; } - - /// - /// Web part position: row - /// - public int Row { get; set; } - - /// - /// Web part position: column - /// - public int Column { get; set; } - - /// - /// Web part position: order - /// - public int Order { get; set; } - - /// - /// Web part position: zone index - /// - public uint ZoneIndex { get; set; } - - /// - /// Dictionary with web part properties - /// - public Dictionary Properties { get; set; } - - /// - /// The XML representation of the Web Part - /// - public string WebPartXml { get; set; } - - /// - /// Returns the shortened web part type name - /// - /// Shortened web part type name - public string TypeShort() - { - return Type.GetTypeShort(); - } - - /// - /// Returns this instance as Json - /// - /// Json serialized string of this web part instance - public string Json() - { - return System.Text.Json.JsonSerializer.Serialize(this); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Model/WebParts.cs b/src/sdk/PnP.Core.Transformation/Model/WebParts.cs deleted file mode 100644 index 5a3e9d3e1d..0000000000 --- a/src/sdk/PnP.Core.Transformation/Model/WebParts.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Collections.Generic; - -namespace PnP.Core.Transformation -{ - /// - /// Web part type constants - /// - public static class WebParts - { -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public const string WikiText = "SharePointPnP.Modernization.WikiTextPart"; - public const string WikiImage = "SharePointPnP.Modernization.WikiImagePart"; - public const string WikiVideo = "SharePointPnP.Modernization.WikiVideoPart"; - public const string WikiEmbed = "SharePointPnP.Modernization.WikiEmbedPart"; - public const string PageProperties = "SharePointPnP.Modernization.PageProperties"; - public const string PageAcceptanceBanner = "SharePointPnP.Modernization.PageAcceptanceBanner"; - public const string XsltListView = "Microsoft.SharePoint.WebPartPages.XsltListViewWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SiteFeed = "Microsoft.SharePoint.Portal.WebControls.SiteFeedWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ContentEditor = "Microsoft.SharePoint.WebPartPages.ContentEditorWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ScriptEditor = "Microsoft.SharePoint.WebPartPages.ScriptEditorWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Image = "Microsoft.SharePoint.WebPartPages.ImageWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ContentByQuery = "Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ContentBySearch = "Microsoft.Office.Server.Search.WebControls.ContentBySearchWebPart, Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ResultScript = "Microsoft.Office.Server.Search.WebControls.ResultScriptWebPart, Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SearchNavigation = "Microsoft.Office.Server.Search.WebControls.SearchNavigationWebPart, Microsoft.Office.Server.Search,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string CatalogItemReuse = "Microsoft.Office.Server.Search.WebControls.CatalogItemReuseWebPart, Microsoft.Office.Server.Search,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string TaxonomyRefinementScript = "Microsoft.Office.Server.Search.WebControls.TaxonomyRefinementScriptWebPart, Microsoft.Office.Server.Search,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string AdvancedSearchBox = "Microsoft.Office.Server.Search.WebControls.AdvancedSearchBox, Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SearchBoxScript = "Microsoft.Office.Server.Search.WebControls.SearchBoxScriptWebPart, Microsoft.Office.Server.Search,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string RefinementScript = "Microsoft.Office.Server.Search.WebControls.RefinementScriptWebPart, Microsoft.Office.Server.Search,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string Xml = "Microsoft.SharePoint.WebPartPages.XmlWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ListView = "Microsoft.SharePoint.WebPartPages.ListViewWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string DataForm = "Microsoft.SharePoint.WebPartPages.DataFormWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string RSSAggregator = "Microsoft.SharePoint.Portal.WebControls.RSSAggregatorWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string WikiContent = "Microsoft.SharePoint.WebPartPages.WikiContentWebpart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string PageViewer = "Microsoft.SharePoint.WebPartPages.PageViewerWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Media = "Microsoft.SharePoint.Publishing.WebControls.MediaWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string TableOfContents = "Microsoft.SharePoint.Publishing.WebControls.TableOfContentsWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SummaryLink = "Microsoft.SharePoint.Publishing.WebControls.SummaryLinkWebPart, Microsoft.SharePoint.Publishing, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ContactField = "Microsoft.SharePoint.Portal.WebControls.ContactFieldControl, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ExcelWebRenderer = "Microsoft.Office.Excel.WebUI.ExcelWebRenderer, Microsoft.Office.Excel.WebUI, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string VisioWebAccess = "Microsoft.Office.Visio.Server.WebControls.VisioWebAccess, Microsoft.Office.Visio.Server, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SocialComment = "Microsoft.SharePoint.Portal.WebControls.SocialCommentWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ProfileBrowser = "Microsoft.SharePoint.Portal.WebControls.ProfileBrowser, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string UserTasks = "Microsoft.SharePoint.WebPartPages.UserTasksWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string PictureLibrarySlideshow = "Microsoft.SharePoint.WebPartPages.PictureLibrarySlideshowWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Silverlight = "Microsoft.SharePoint.WebPartPages.SilverlightWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SimpleForm = "Microsoft.SharePoint.WebPartPages.SimpleFormWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BrowserForm = "Microsoft.Office.InfoPath.Server.Controls.WebUI.BrowserFormWebPart, Microsoft.Office.InfoPath.Server, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SPTimeline = "Microsoft.SharePoint.WebPartPages.SPTimelineWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string TermProperty = "Microsoft.SharePoint.Taxonomy.TermProperty, Microsoft.SharePoint.Taxonomy, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string UserDocs = "Microsoft.SharePoint.WebPartPages.UserDocsWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SiteDocuments = "Microsoft.SharePoint.Portal.WebControls.SiteDocuments, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string GettingStarted = "Microsoft.SharePoint.WebPartPages.GettingStartedWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string TagCloud = "Microsoft.SharePoint.Portal.WebControls.TagCloudWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataList = "Microsoft.SharePoint.Portal.WebControls.BusinessDataListWebPart, Microsoft.SharePoint.Portal,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataFilter = "Microsoft.SharePoint.Portal.WebControls.BusinessDataFilterWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataDetails = "Microsoft.SharePoint.Portal.WebControls.BusinessDataDetailsWebPart, Microsoft.SharePoint.Portal,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataAssociation = "Microsoft.SharePoint.Portal.WebControls.BusinessDataAssociationWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataActions = "Microsoft.SharePoint.Portal.WebControls.BusinessDataActionsWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BusinessDataItemBuilder = "Microsoft.SharePoint.Portal.WebControls.BusinessDataItemBuilder, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ScorecardFilter = "Microsoft.SharePoint.Portal.WebControls.ScorecardFilterWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ApplyFilters = "Microsoft.SharePoint.Portal.WebControls.ApplyFiltersWebPart, Microsoft.Office.Server.FilterControls, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string DateFilter = "Microsoft.SharePoint.Portal.WebControls.DateFilterWebPart, Microsoft.Office.Server.FilterControls, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string UserContextFilter = "Microsoft.SharePoint.Portal.WebControls.UserContextFilterWebPart, Microsoft.Office.Server.FilterControls,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string SPSlicerText = "Microsoft.SharePoint.Portal.WebControls.SPSlicerTextWebPart, Microsoft.Office.Server.FilterControls, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SpListFilter = "Microsoft.SharePoint.Portal.WebControls.SpListFilterWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string QueryStringFilter = "Microsoft.SharePoint.Portal.WebControls.QueryStringFilterWebPart, Microsoft.Office.Server.FilterControls,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string PageContextFilter = "Microsoft.SharePoint.Portal.WebControls.PageContextFilterWebPart, Microsoft.Office.Server.FilterControls,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string SPSlicerChoices = "Microsoft.SharePoint.Portal.WebControls.SPSlicerChoicesWebPart,Microsoft.Office.Server.FilterControls,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string BlogLinks = "Microsoft.SharePoint.WebPartPages.BlogLinksWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BlogAdmin = "Microsoft.SharePoint.WebPartPages.BlogAdminWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BlogMonthQuickLaunch = "Microsoft.SharePoint.WebPartPages.BlogMonthQuickLaunch, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string BlogYearArchive = "Microsoft.SharePoint.WebPartPages.BlogYearArchive, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Members = "Microsoft.SharePoint.WebPartPages.MembersWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ProjectSummary = "Microsoft.SharePoint.Portal.WebControls.ProjectSummaryWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Indicator = "Microsoft.SharePoint.Portal.WebControls.IndicatorWebpart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string KPIList = "Microsoft.SharePoint.Portal.WebControls.KPIListWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string CommunityAdmin = "Microsoft.SharePoint.Portal.WebControls.CommunityAdminWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string CommunityJoin = "Microsoft.SharePoint.Portal.WebControls.CommunityJoinWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Dashboard = "Microsoft.SharePoint.Portal.WebControls.DashboardWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string AboutUs = "Microsoft.SharePoint.Portal.WebControls.AboutUsWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string MyMembership = "Microsoft.SharePoint.Portal.WebControls.MyMembershipWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string DocIdSearch = "Microsoft.Office.Server.WebControls.DocIdSearchWebPart, Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string DocumentSetProperties = "Microsoft.Office.Server.WebControls.DocumentSetPropertiesWebPart, Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string DocumentSetContents = "Microsoft.Office.Server.WebControls.DocumentSetContentsWebPart, Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string OWAInbox = "Microsoft.SharePoint.Portal.WebControls.OWAInboxPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string OWACalendar = "Microsoft.SharePoint.Portal.WebControls.OWACalendarPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string OWAContacts = "Microsoft.SharePoint.Portal.WebControls.OWAContactsPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string OWATasks = "Microsoft.SharePoint.Portal.WebControls.OWATasksPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Category = "Microsoft.SharePoint.Portal.WebControls.CategoryWebPart, Microsoft.SharePoint.Portal,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string CategoryResults = "Microsoft.SharePoint.Portal.WebControls.CategoryResultsWebPart, Microsoft.SharePoint.Portal,Version=16.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"; - public const string AnnouncementTiles = "Microsoft.SharePoint.Portal.WebControls.AnnouncementTilesWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string TasksAndTools = "Microsoft.SharePoint.Portal.WebControls.TasksAndToolsWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ThisWeekInPictures = "Microsoft.SharePoint.Portal.WebControls.ThisWeekInPicturesWebPart, Microsoft.SharePoint.Portal, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string WhatsPopular = "Microsoft.Office.Server.WebAnalytics.Reporting.WhatsPopularWebPart, Microsoft.Office.Server.WebAnalytics.UI, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Chart = "Microsoft.Office.Server.WebControls.ChartWebPart, Microsoft.Office.Server.Chart, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Error = "Microsoft.SharePoint.WebPartPages.ErrorWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string TitleBar = "Microsoft.SharePoint.WebPartPages.TitleBarWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string GettingStartedWithAppCatalogSite = "Microsoft.SharePoint.WebPartPages.GettingStartedWithAppCatalogSiteWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string SPUserCode = "Microsoft.SharePoint.WebPartPages.SPUserCodeWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string ClientSide = "Microsoft.SharePoint.WebPartPages.ClientSideWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; - public const string Client = "Microsoft.SharePoint.WebPartPages.ClientWebPart, Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"; -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - - - /// - /// Use reflection to read the object fields in a list of strings - /// - public static List GetListOfWebParts(string InNameSpace = "") - { - List webPartsList = new List(); - - try - { - - var fields = typeof(WebParts).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - - foreach (var field in fields) - { - if (field.FieldType == typeof(string)) - { - var value = field.GetValue(null) as string; - - if (!string.IsNullOrEmpty(InNameSpace)) - { - if (value.Contains(InNameSpace)) - { - webPartsList.Add(value); - } - } - else - { - webPartsList.Add(value); - } - } - } - } - catch - { - // Swallow - } - - return webPartsList; - - } - - } -} diff --git a/src/sdk/PnP.Core.Transformation/PnP.Core.Transformation.csproj b/src/sdk/PnP.Core.Transformation/PnP.Core.Transformation.csproj deleted file mode 100644 index 6ed4ed4697..0000000000 --- a/src/sdk/PnP.Core.Transformation/PnP.Core.Transformation.csproj +++ /dev/null @@ -1,84 +0,0 @@ - - - - netstandard2.0;net8.0;net9.0;net10.0 - 10.0 - PnP.Core.Transformation - PnP.Core.Transformation - 1.15.0 - false - PnP 2025 - PnP - PnP - [EXPERIMENTAL - Not yet officially released!] The PnP Core Transformation library provides a set of tools for the PnP Transformation Framework. Internally it is based on PnP.Core and PnP.Core.Auth. - https://aka.ms/pnp/coresdk - https://github.com/pnp/pnpcore - git - true - - - true - snupkg - true - true - Debug;Release - nugeticon.png - MIT - true - ..\pnp.core.snk - - - - TRACE - obj\Debug\PnP.Core.Transformation.xml - Nightly - - - True - - - - obj\Debug\PnP.Core.Transformation.xml - True - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - - - - True - \ - - - - - - - - - - True - True - TransformationResources.resx - - - - - - ResXFileCodeGenerator - TransformationResources.Designer.cs - - - - diff --git a/src/sdk/PnP.Core.Transformation/Services/Builder/Configuration/PnPTransformationOptions.cs b/src/sdk/PnP.Core.Transformation/Services/Builder/Configuration/PnPTransformationOptions.cs deleted file mode 100644 index 109046703e..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Builder/Configuration/PnPTransformationOptions.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace PnP.Core.Transformation.Services.Builder.Configuration -{ - /// - /// Options for configuring PnP Core SDK - /// - public class PnPTransformationOptions - { - /// - /// Turns on/off telemetry, can be customized via configuration. Defaults to false. - /// - public bool DisableTelemetry { get; set; } - - /// - /// Defines the connection string for the configured persistence provider - /// - public string PersistenceProviderConnectionString { get; set; } - - /// - /// Defines the path where to store logs, if any - /// - public string LogStoragePath { get; set; } - - // TODO: Here we can configure all the "generic" configuration settings for the engine - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Builder/IPnPTransformationBuilder.cs b/src/sdk/PnP.Core.Transformation/Services/Builder/IPnPTransformationBuilder.cs deleted file mode 100644 index 2061d0a7be..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Builder/IPnPTransformationBuilder.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using System; - -namespace PnP.Core.Transformation.Services.Builder -{ - /// - /// Used to configure PnP Core Transformation - /// - public interface IPnPTransformationBuilder - { - /// - /// Collection of services for Dependecy Injection - /// - IServiceCollection Services { get; } - - /// - /// Allows configuring a custom to use to handle the state of a transformation process - /// - /// - /// - IPnPTransformationBuilder WithTransformationStateManager() - where T : class, ITransformationStateManager; - - /// - /// Allows configuring a custom to defines a list of pages to transform - /// - /// - /// - IPnPTransformationBuilder WithTransformationDistiller() - where T : class, ITransformationDistiller; - - /// - /// Allows configuring a custom to use to transform pages - /// - /// - /// - IPnPTransformationBuilder WithPageTransformator() - where T : class, IPageTransformator; - - /// - /// Allows configuring a custom that manages the transformation of one or more pages - /// - /// - /// - IPnPTransformationBuilder WithTransformationExecutor() - where T : class, ITransformationExecutor; - - /// - /// Allows configuring a custom which resolves target page uri - /// - /// - /// - IPnPTransformationBuilder WithTargetPageUriResolver() - where T : class, ITargetPageUriResolver; - - /// - /// Allows configuring the default transformation options for pages - /// - /// - /// - IPnPTransformationBuilder WithPageOptions(Action options); - - /// - /// Allows configuring a custom to use for all transformations - /// - /// - /// - IPnPTransformationBuilder WithMappingProvider() - where T : class, IMappingProvider; - - /// - /// Adds an object to intercept a page before the transformation - /// - /// - /// - IPnPTransformationBuilder AddPagePreTransformation() - where T : class, IPagePreTransformation; - - /// - /// Adds an object to intercept a page after the transformation - /// - /// - /// - IPnPTransformationBuilder AddPagePostTransformation() - where T : class, IPagePostTransformation; - - /// - /// Allows configuring a custom to use in order to get the source items - /// - /// - /// - IPnPTransformationBuilder WithSourceProvider() - where T : class, ISourceProvider; - - /// - /// Allows configuring a custom that manages the persistence of assets onto a target persistence storage - /// - /// - /// - IPnPTransformationBuilder WithPersistenceProvider() - where T : class, IAssetPersistenceProvider; - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationBuilder.cs b/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationBuilder.cs deleted file mode 100644 index fd0e193a8b..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationBuilder.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using PnP.Core.Transformation.Services.Core; -using PnP.Core.Transformation.Services.MappingProviders; -using System; - -namespace PnP.Core.Transformation.Services.Builder -{ - /// - /// Used to configure PnP Core Transformation - /// - public class PnPTransformationBuilder : IPnPTransformationBuilder - { - /// - /// Constructor - /// - /// The services being configured. - public PnPTransformationBuilder(IServiceCollection services) => Services = services; - - /// - /// The services being configured - /// - public virtual IServiceCollection Services { get; } - - /// - /// Allows configuring a custom to use to handle the state of a transformation process - /// - /// - /// - public IPnPTransformationBuilder WithTransformationStateManager() - where T : class, ITransformationStateManager - { - Services.RemoveAll(); - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring a custom to defines a list of pages to transform - /// - /// - /// - public IPnPTransformationBuilder WithTransformationDistiller() - where T : class, ITransformationDistiller - { - Services.RemoveAll(); - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring a custom to use to transform pages - /// - /// - /// - public IPnPTransformationBuilder WithPageTransformator() - where T : class, IPageTransformator - { - Services.RemoveAll(); - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring a custom that manages the transformation of one or more pages - /// - /// - /// - public IPnPTransformationBuilder WithTransformationExecutor() - where T : class, ITransformationExecutor - { - Services.RemoveAll(); - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring a custom which resolves target page uri - /// - /// - /// - public IPnPTransformationBuilder WithTargetPageUriResolver() where T : class, ITargetPageUriResolver - { - Services.RemoveAll(); - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring the default transformation options for pages - /// - /// - /// - public IPnPTransformationBuilder WithPageOptions(Action options) - { - Services.Configure(options); - return this; - } - - /// - /// Allows configuring a custom to use for all transformations - /// - /// - /// - public IPnPTransformationBuilder WithMappingProvider() - where T : class, IMappingProvider - { - Services.RemoveAll(); - Services.AddTransient(); - - return this; - } - - /// - /// Adds an object to intercept a page before the transformation - /// - /// - /// - public IPnPTransformationBuilder AddPagePreTransformation() - where T : class, IPagePreTransformation - { - Services.AddTransient(); - return this; - } - - /// - /// Adds an object to intercept a page after the transformation - /// - /// - /// - public IPnPTransformationBuilder AddPagePostTransformation() - where T : class, IPagePostTransformation - { - Services.AddTransient(); - return this; - } - - /// - /// Allows configuring a custom to use in order to get the source items - /// - /// - /// - public IPnPTransformationBuilder WithSourceProvider() - where T : class, ISourceProvider - { - Services.RemoveAll(); - Services.AddTransient(); - - return this; - } - - /// - /// Allows configuring a custom that manages the persistence of assets onto a target persistence storage - /// - /// - /// - public IPnPTransformationBuilder WithPersistenceProvider() - where T : class, IAssetPersistenceProvider - { - Services.RemoveAll(); - Services.AddTransient(); - - return this; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationServiceCollectionExtensions.cs b/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationServiceCollectionExtensions.cs deleted file mode 100644 index 3a8b3f8a53..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Builder/PnPTransformationServiceCollectionExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Microsoft.Extensions.DependencyInjection.Extensions; -using PnP.Core.Transformation.Services.Builder; -using PnP.Core.Transformation.Services.Builder.Configuration; -using PnP.Core.Transformation.Services.Core; -using System; -using System.Linq; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Extension methods for setting up PnP Transformation services in a . - /// - public static class PnPTransformationServiceCollectionExtensions - { - /// - /// Configures PnP Transformation with default options - /// - /// The collection of services in a - /// A PnPTransformationBuilder instance - public static IPnPTransformationBuilder AddPnPTransformation(this IServiceCollection services) - { - return AddPnPTransformation(services, null, null); - } - - /// - /// Configures PnP Transformation with custom options - /// - /// The collection of services in a - /// An Action to configure the PnP Transformation options - /// An Action to configure the Page Transformation options - /// A PnPTransformationBuilder instance - public static IPnPTransformationBuilder AddPnPTransformation(this IServiceCollection services, - Action options, - Action pageOptions) - { - if (services == null) - { - throw new ArgumentNullException(nameof(services)); - } - - if (options != null) - { - services.Configure(options); - } - - if (pageOptions != null) - { - services.Configure(pageOptions); - } - - // TODO: Consider using the distributed one or providing an option to use the distributed one - // Add the caching services - services.AddMemoryCache(); - - var builder = new PnPTransformationBuilder(services); - - // Set default implementations - builder.AddDefaults(); - - return builder; - } - - /// - /// Adds default implementations provided by the transformation framework - /// - /// The services builder - /// A PnPTransformationBuilder instance - public static IPnPTransformationBuilder AddDefaults(this IPnPTransformationBuilder builder) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - builder.Services.TryAddTransient(); - - // Register the Telemetry Service - builder.Services.AddTransient(); - - // Register all the token definitions as services - var tokenDefinitionInterface = typeof(ITokenDefinition); - var tokenDefinitions = typeof(PnPTransformationServiceCollectionExtensions).Assembly.GetTypes() - .Where(t => tokenDefinitionInterface.IsAssignableFrom(t) && !t.IsInterface); - - foreach (var tokenDefinition in tokenDefinitions) - { - builder.Services.AddTransient(typeof(ITokenDefinition), tokenDefinition); - } - - return builder; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageGenerator.cs b/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageGenerator.cs deleted file mode 100644 index 2753278595..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageGenerator.cs +++ /dev/null @@ -1,789 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json.Linq; -using PnP.Core.Model; -using PnP.Core.Model.SharePoint; -using PnP.Core.QueryModel; -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.MappingProviders; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Implements the concrete PageGenerator (this is the core of PnP Transformation Framework) - /// - public class DefaultPageGenerator : IPageGenerator - { - private readonly ILogger logger; - private readonly PageTransformationOptions defaultPageTransformationOptions; - private readonly IMemoryCache memoryCache; - private readonly IServiceProvider serviceProvider; - private readonly TokenParser tokenParser; - - /// - /// Constructor with DI support - /// - /// The logger interface - /// The options - /// Service provider - public DefaultPageGenerator( - ILogger logger, - IOptions pageTransformationOptions, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.defaultPageTransformationOptions = pageTransformationOptions?.Value ?? throw new ArgumentNullException(nameof(pageTransformationOptions)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - this.tokenParser = this.serviceProvider.GetService(); - } - - /// - /// Translates the provided input into a page - /// - /// Page transformation options - /// The mapping provider output - /// The url for the target page - /// Cancellation token - /// The result of the page generation - /// - /// - /// - public async Task GenerateAsync(PageTransformationContext context, MappingProviderOutput mappingOutput, Uri targetPageUri, CancellationToken token = default) - { - #region Validate input arguments - - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (mappingOutput == null) - { - throw new ArgumentNullException(nameof(mappingOutput)); - } - - if (targetPageUri == null) - { - throw new ArgumentNullException(nameof(targetPageUri)); - } - - #endregion - - var result = new PageGeneratorOutput(); - - #region Validate target site - - // Ensure to have the needed target web properties - var targetWeb = context.Task.TargetContext.Web; - await targetWeb.EnsurePropertiesAsync(w => w.WebTemplate).ConfigureAwait(false); - - // Check target site - if (targetWeb.WebTemplate != "SITEPAGEPUBLISHING" && - targetWeb.WebTemplate != "STS" && - targetWeb.WebTemplate != "GROUP" && - targetWeb.WebTemplate != "BDR" && - targetWeb.WebTemplate != "DEV") - { - logger.LogError( - TransformationResources.Error_CrossSiteTransferTargetsNonModernSite - .CorrelateString(context.Task.Id)); - throw new ArgumentException(TransformationResources.Error_CrossSiteTransferTargetsNonModernSite); - } - - #endregion - - #region Validate and create the target page - - // Ensure PostAsNews is used together with PagePublishing - if (this.defaultPageTransformationOptions.PostAsNews && !this.defaultPageTransformationOptions.PublishPage) - { - this.defaultPageTransformationOptions.PublishPage = true; - logger.LogWarning( - TransformationResources.Warning_PostingAPageAsNewsRequiresPagePublishing - .CorrelateString(context.Task.Id)); - } - - // Check if the target page already exists - string targetPageUriString = targetPageUri.IsAbsoluteUri ? targetPageUri.AbsolutePath : targetPageUri.ToString(); - IFile targetFile = null; - var targetFileExists = false; - try - { - targetFile = await targetWeb.GetFileByServerRelativeUrlAsync(targetPageUriString).ConfigureAwait(false); - targetFileExists = true; - } - catch (SharePointRestServiceException) - { - // Simply ignore this exception and assume that the page does not exist - targetFileExists = false; - } - if (targetFileExists) - { - logger.LogInformation( - TransformationResources.Info_PageAlreadyExistsInTargetLocation - .CorrelateString(context.Task.Id), - targetPageUri); - - if (!this.defaultPageTransformationOptions.Overwrite) - { - // Raise an exception and stop the process if Overwrite is not allowed - var errorMessage = string.Format(System.Globalization.CultureInfo.InvariantCulture, - TransformationResources.Error_PageNotOverwriteIfExists, targetPageUri.ToString()); - - logger.LogError(errorMessage - .CorrelateString(context.Task.Id)); - throw new ApplicationException(errorMessage); - } - } - - // Create the client side page using PnP Core SDK and save it - var targetPage = await targetWeb.NewPageAsync().ConfigureAwait(false); - - var targetPageFilePath = $"{mappingOutput.TargetPage.Folder}{mappingOutput.TargetPage.PageName}"; - await targetPage.SaveAsync(targetPageFilePath).ConfigureAwait(false); - - // Reload the generated file - targetFile = await targetWeb.GetFileByServerRelativeUrlAsync(targetPageUriString).ConfigureAwait(false); - - #endregion - - #region Check if the page is the home page - - logger.LogDebug( - TransformationResources.Debug_TransformCheckIfPageIsHomePage - .CorrelateString(context.Task.Id)); - - // Check if the page is the home page - bool replacedByOOBHomePage = false; - - // Check if the transformed page is the web's home page - if (mappingOutput.TargetPage.IsHomePage) - { - targetPage.LayoutType = PnP.Core.Model.SharePoint.PageLayoutType.Home; - if (this.defaultPageTransformationOptions.ReplaceHomePageWithDefaultHomePage) - { - targetPage.KeepDefaultWebParts = true; - replacedByOOBHomePage = true; - - logger.LogInformation( - TransformationResources.Info_TransformSourcePageHomePageUsingStock - .CorrelateString(context.Task.Id)); - } - } - - // If it is not the home, let's define the actual page structure - if (!replacedByOOBHomePage) - { - logger.LogInformation( - TransformationResources.Info_TransformSourcePageAsArticlePage - .CorrelateString(context.Task.Id)); - - #region Configure header from target page - if (mappingOutput.TargetPage.PageHeader == null || (mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.None) - { - logger.LogInformation( - TransformationResources.Info_TransformArticleSetHeaderToNone - .CorrelateString(context.Task.Id)); - - if (mappingOutput.TargetPage.SetAuthorInPageHeader) - { - targetPage.SetDefaultPageHeader(); - targetPage.PageHeader.LayoutType = PageHeaderLayoutType.NoImage; - - logger.LogInformation( - TransformationResources.Info_TransformArticleSetHeaderToNoneWithAuthor - .CorrelateString(context.Task.Id)); - await SetAuthorInPageHeaderAsync(context, mappingOutput, targetPage, context.Task.Id, token).ConfigureAwait(false); - } - else - { - targetPage.RemovePageHeader(); - } - } - else if ((mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.Default) - { - logger.LogInformation( - TransformationResources.Info_TransformArticleSetHeaderToDefault - .CorrelateString(context.Task.Id)); - targetPage.SetDefaultPageHeader(); - } - else if ((mappingOutput.TargetPage.PageHeader as PageHeader).Type == PageHeaderType.Custom) - { - var infoMessage = $"{TransformationResources.Info_TransformArticleSetHeaderToCustom} {TransformationResources.Info_TransformArticleHeaderImageUrl} {mappingOutput.TargetPage.PageHeader.ImageServerRelativeUrl}"; - logger.LogInformation(infoMessage - .CorrelateString(context.Task.Id)); - - targetPage.SetCustomPageHeader(mappingOutput.TargetPage.PageHeader.ImageServerRelativeUrl, - mappingOutput.TargetPage.PageHeader.TranslateX, - mappingOutput.TargetPage.PageHeader.TranslateY); - } - #endregion - } - - #endregion - - // Set page title - targetPage.PageTitle = mappingOutput.TargetPage.PageTitle; - - // Create the web parts and the transformed content - - // Process all the sections, columns, and controls - await GenerateTargetCanvasControlsAsync(targetWeb, targetPage, mappingOutput, context.Task.Id).ConfigureAwait(false); - - // Process metadata - await CopyPageMetadataAsync(targetFile, mappingOutput, context.Task.Id).ConfigureAwait(false); - - // TODO: Process permissions - - #region Leftover code, most likely to be removed - - //if (transformationInformation.SkipHiddenWebParts) - //{ - // webParts = webParts.Where(c => !c.Hidden).ToList(); - //} - - // Persist the new client side page - - // Set metadata fields for list item of the page - - // Configure page item permissions - // if (KeepPageSpecificPermissions) { } - - // Other page settings - //#region Page Publishing - //// Tag the file with a page modernization version stamp - //string serverRelativePathForModernPage = savedTargetPage.ListItemAllFields[Constants.FileRefField].ToString(); - //bool pageListItemWasReloaded = false; - //try - //{ - // var targetPageFile = context.Web.GetFileByServerRelativeUrl(serverRelativePathForModernPage); - // context.Load(targetPageFile, p => p.Properties); - // targetPageFile.Properties["sharepointpnp_pagemodernization"] = this.version; - // targetPageFile.Update(); - - // if (!pageTransformationInformation.KeepPageCreationModificationInformation && - // !pageTransformationInformation.PostAsNews && - // pageTransformationInformation.PublishCreatedPage) - // { - // // Try to publish, if publish is not needed/possible (e.g. when no minor/major versioning set) then this will return an error that we'll be ignoring - // targetPageFile.Publish(LogStrings.PublishMessage); - // } - - // // Ensure we've the most recent page list item loaded, must be last statement before calling ExecuteQuery - // context.Load(savedTargetPage.ListItemAllFields); - // // Send both the property update and publish as a single operation to SharePoint - // context.ExecuteQueryRetry(); - // pageListItemWasReloaded = true; - //} - //catch (Exception) - //{ - // // Eat exceptions as this is not critical for the generated page - // LogWarning(LogStrings.Warning_NonCriticalErrorDuringVersionStampAndPublish, LogStrings.Heading_ArticlePageHandling); - //} - - //// Update flags field to indicate this is a "migrated" page - //try - //{ - // // If for some reason the reload batched with the previous request did not finish then do it again - // if (!pageListItemWasReloaded) - // { - // context.Load(savedTargetPage.ListItemAllFields); - // context.ExecuteQueryRetry(); - // } - - // // Only perform the update when the field was not yet set - // bool skipSettingMigratedFromServerRendered = false; - // if (savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] != null) - // { - // skipSettingMigratedFromServerRendered = (savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] as string[]).Contains("MigratedFromServerRendered"); - // } - - // if (!skipSettingMigratedFromServerRendered) - // { - // savedTargetPage.ListItemAllFields[Constants.SPSitePageFlagsField] = ";#MigratedFromServerRendered;#"; - // // Don't use UpdateOverWriteVersion as the listitem already exists - // // resulting in an "Additions to this Web site have been blocked" error - // savedTargetPage.ListItemAllFields.SystemUpdate(); - // context.Load(savedTargetPage.ListItemAllFields); - // context.ExecuteQueryRetry(); - // } - //} - //catch (Exception) - //{ - // // Eat any exception - //} - - //// Disable page comments on the create page, if needed - //if (pageTransformationInformation.DisablePageComments) - //{ - // targetPage.DisableComments(); - // LogInfo(LogStrings.TransformDisablePageComments, LogStrings.Heading_ArticlePageHandling); - //} - - //#endregion - - // Swap pages? - - //#region Restore page author/editor/created/modified - //if ((pageTransformationInformation.SourcePage != null && pageTransformationInformation.KeepPageCreationModificationInformation && this.SourcePageAuthor != null && this.SourcePageEditor != null) || - // pageTransformationInformation.PostAsNews) - //{ - // UpdateTargetPageWithSourcePageInformation(finalListItemToUpdate, pageTransformationInformation, finalListItemToUpdate[Constants.FileRefField].ToString(), hasTargetContext); - //} - //#endregion - - // Log generation completed - - #endregion - - // Save the generated page - await targetPage.SaveAsync(targetPageFilePath).ConfigureAwait(false); - - // Restore page author/editor/created/modified - if ((this.defaultPageTransformationOptions.KeepPageCreationModificationInformation - && mappingOutput.TargetPage.Author != null - && mappingOutput.TargetPage.Editor != null) || - this.defaultPageTransformationOptions.PostAsNews) - { - await UpdateTargetPageWithSourcePageInformationAsync(targetFile, mappingOutput, context.Task.Id).ConfigureAwait(false); - } - - // Return the generated page URL - var generatedPageFile = await targetPage.GetPageFileAsync(f => f.ServerRelativeUrl).ConfigureAwait(false); - var generatedPageUri = new Uri($"{targetWeb.Url.Scheme}://{targetWeb.Url.Host}{generatedPageFile.ServerRelativeUrl}"); - - // Validate the URI of the output page - if (!generatedPageUri.AbsoluteUri.Equals(targetPageUri.AbsoluteUri, StringComparison.InvariantCultureIgnoreCase)) - { - throw new ApplicationException(TransformationResources.Error_InvalidTargetPageUri); - } - - result.GeneratedPageUrl = targetPageUri; - - return result; - } - - private async Task UpdateTargetPageWithSourcePageInformationAsync(IFile targetFile, MappingProviderOutput mappingOutput, Guid taskId) - { - try - { - // Ensure the properties to define the cache key - var targetWeb = targetFile.PnPContext.Web; - await targetWeb.EnsurePropertiesAsync(w => w.Id).ConfigureAwait(false); - - // Load the list item corresponding to the file - await targetFile.LoadAsync(f => f.ListItemAllFields).ConfigureAwait(false); - var targetItem = targetFile.ListItemAllFields; - - // Update the target page information properties - var pageAuthorUser = await targetWeb.EnsureUserAsync(mappingOutput.TargetPage.Author).ConfigureAwait(false); - var pageEditorUser = await targetWeb.EnsureUserAsync(mappingOutput.TargetPage.Editor).ConfigureAwait(false); - - // Prep a new FieldUserValue object instance and update the list item - var pageAuthor = new FieldUserValue() - { - LookupValue = pageAuthorUser.Title, - LookupId = pageAuthorUser.Id - }; - - var pageEditor = new FieldUserValue() - { - LookupValue = pageEditorUser.Title, - LookupId = pageEditorUser.Id - }; - - if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation || - this.defaultPageTransformationOptions.PostAsNews) - { - if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation) - { - // All 4 fields have to be set! - targetItem[SharePointConstants.CreatedByField] = pageAuthor; - targetItem[SharePointConstants.ModifiedByField] = pageEditor; - targetItem[SharePointConstants.CreatedField] = mappingOutput.TargetPage.Created; - targetItem[SharePointConstants.ModifiedField] = mappingOutput.TargetPage.Modified; - } - - if (this.defaultPageTransformationOptions.PostAsNews) - { - targetItem[SharePointConstants.PromotedStateField] = "2"; - - // Determine what will be the publishing date that will show up in the news rollup - if (this.defaultPageTransformationOptions.KeepPageCreationModificationInformation) - { - targetItem[SharePointConstants.FirstPublishedDateField] = mappingOutput.TargetPage.Modified; - } - else - { - targetItem[SharePointConstants.FirstPublishedDateField] = targetItem[SharePointConstants.ModifiedField]; - } - } - - await targetItem.UpdateOverwriteVersionAsync().ConfigureAwait(false); - - if (this.defaultPageTransformationOptions.PublishPage) - { - await targetFile.PublishAsync(TransformationResources.Info_PublishMessage).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - // Eat exceptions as this is not critical for the generated page - logger.LogWarning( - TransformationResources.Warning_NonCriticalErrorDuringPublish - .CorrelateString(taskId), ex.Message); - } - } - - private async Task CopyPageMetadataAsync(IFile targetFile, MappingProviderOutput mappingOutput, Guid taskId) - { - // Ensure the properties to define the cache key - var targetWeb = targetFile.PnPContext.Web; - await targetWeb.EnsurePropertiesAsync(w => w.Id).ConfigureAwait(false); - var targetSite = targetFile.PnPContext.Site; - await targetSite.EnsurePropertiesAsync(s => s.Id).ConfigureAwait(false); - await targetFile.EnsurePropertiesAsync(f => f.ListId).ConfigureAwait(false); - - // Retrieve the list of fields from cache - var fieldsToCopy = await GetFieldsFromCache(targetFile, targetWeb, targetSite).ConfigureAwait(false); - - //bool listItemWasReloaded = false; - if (fieldsToCopy.Count > 0) - { - // Load the list item corresponding to the file - await targetFile.LoadAsync(f => f.ListItemAllFields).ConfigureAwait(false); - var targetItem = targetFile.ListItemAllFields; - - // Initially set the content type Id - Are we sure about this excerpt? - //targetItem[PageConstants.ContentTypeId] = mappingOutput.Metadata[PageConstants.ContentTypeId]?.Value; - //await targetItem.UpdateOverwriteVersionAsync().ConfigureAwait(false); - - // TODO: Complete metadata fields handling (taxonomy with mapping provider and other fields) - foreach (var fieldToCopy in fieldsToCopy.Where(f => f.Type == "TaxonomyFieldTypeMulti" || f.Type == "TaxonomyFieldType")) - { - logger.LogInformation( - TransformationResources.Info_MappingTaxonomyField - .CorrelateString(taskId), fieldToCopy.Name); - - // https://pnp.github.io/pnpcore/using-the-sdk/listitems-fields.html#taxonomy-fields - } - - foreach (var fieldToCopy in fieldsToCopy.Where(f => f.Type != "TaxonomyFieldTypeMulti" && f.Type != "TaxonomyFieldType")) - { - logger.LogInformation( - TransformationResources.Info_MappingRegularField - .CorrelateString(taskId), fieldToCopy.Name); - - // https://pnp.github.io/pnpcore/using-the-sdk/listitems-fields.html - } - } - } - - private async Task> GetFieldsFromCache(IFile targetFile, IWeb targetWeb, ISite targetSite) - { - // Define the cache key - var fieldsCacheKey = $"Fields|{targetSite.Id}|{targetWeb.Id}|{targetFile.ListId}"; - - List result = null; - if (!this.memoryCache.TryGetValue(fieldsCacheKey, out result)) - { - // Get the fields of the current list - var targetList = await targetFile.PnPContext.Web.Lists.GetByIdAsync(targetFile.ListId, - l => l.Id, - l => l.Fields.QueryProperties(f => f.Id, f => f.StaticName, f => f.TypeAsString, f => f.Hidden) - ).ConfigureAwait(false); - - // Exclude hidden and built-in fields - result = targetList.Fields.AsRequested() - .Where(f => !f.Hidden && !BuiltInFields.Contains(f.StaticName)).Select(f => new FieldData - { - Id = f.Id, - Name = f.StaticName, - Type = f.TypeAsString - }).ToList(); - - // Store the list of fields in cache for future use - this.memoryCache.Set(fieldsCacheKey, result); - } - - return result; - } - - private async Task GenerateTargetCanvasControlsAsync(IWeb targetWeb, IPage targetPage, MappingProviderOutput mappingOutput, Guid taskId) - { - // Prepare global tokens - var globalTokens = await PrepareGlobalTokensAsync(targetWeb).ConfigureAwait(false); - - // Get the list of components available in the current site - var componentsToAdd = await GetClientSideComponentsAsync(targetPage).ConfigureAwait(false); - - int sectionOrder = 0; - foreach (var section in mappingOutput.TargetPage.Sections) - { - section.Order = sectionOrder; - targetPage.AddSection(section.CanvasTemplate, sectionOrder); - var targetSection = targetPage.Sections[sectionOrder]; - sectionOrder++; - - int columnOrder = 0; - int controlOrder = 0; - foreach (var column in section.Columns) - { - var targetColumn = targetSection.Columns[columnOrder]; - columnOrder++; - controlOrder++; - - foreach (var control in column.Controls) - { - GenerateTargetCanvasControl(targetPage, componentsToAdd, controlOrder, targetColumn, control, globalTokens, taskId); - } - } - } - } - - private void GenerateTargetCanvasControl(IPage targetPage, List componentsToAdd, int controlOrder, ICanvasColumn targetColumn, Model.CanvasControl control, Dictionary globalTokens, Guid taskId) - { - // Prepare a web part control container - IPageComponent baseControl = null; - - switch (control.ControlType) - { - case Model.CanvasControlType.ClientSideText: - // Here we add a text control - var text = targetPage.NewTextPart(); - text.Text = control["Text"] as string; - - // Add the actual text control to the page - targetPage.AddControl(text, targetColumn, controlOrder); - - // Log the just executed action - logger.LogInformation( - TransformationResources.Info_CreatedTextControl - .CorrelateString(taskId)); - - break; - case Model.CanvasControlType.CustomClientSideWebPart: - // Parse the control ID to support generic web part placement scenarios - var ControlId = control["ControlId"] as string; - // Check if this web part belongs to the list of "usable" web parts for this site - baseControl = componentsToAdd.FirstOrDefault(p => p.Id.Equals($"{{{ControlId}}}", StringComparison.InvariantCultureIgnoreCase)); - - logger.LogInformation( - TransformationResources.Info_UsingCustomModernWebPart - .CorrelateString(taskId)); - - break; - case Model.CanvasControlType.DefaultWebPart: - // Determine the actual default web part - var webPartType = (DefaultWebPart)Enum.Parse(typeof(DefaultWebPart), control["WebPartType"] as string); - var webPartName = targetPage.DefaultWebPartToWebPartId(webPartType); - var webPartTitle = control["Title"] as string; - var webPartProperties = control["Properties"] as Dictionary; - var jsonControlData = control["JsonControlData"] as string; - - if (webPartType == DefaultWebPart.ClientWebPart) - { - var addinComponents = componentsToAdd.Where(p => p.Name.Equals(webPartName, StringComparison.InvariantCultureIgnoreCase)); - foreach (var addin in addinComponents) - { - // Find the right add-in web part via title matching...maybe not bullet proof but did find anything better for now - JObject wpJObject = JObject.Parse(addin.Manifest); - - // As there can be multiple classic web parts (via provider hosted add ins or SharePoint hosted add ins) we're looping to find the first one with a matching title - foreach (var addinEntry in wpJObject["preconfiguredEntries"]) - { - if (addinEntry["title"]["default"].Value() == webPartTitle) - { - baseControl = addin; - - var jsonProperties = addinEntry; - - // Fill custom web part properties in this json. Custom properties are listed as child elements under clientWebPartProperties, - // replace their "default" value with the value we got from the web part's properties - jsonProperties = PopulateAddInProperties(jsonProperties, webPartProperties); - - // Override the JSON data we read from the model as this is fully dynamic due to the nature of the add-in client part - jsonControlData = jsonProperties.ToString(Newtonsoft.Json.Formatting.None); - - logger.LogInformation( - TransformationResources.Info_ContentUsingAddinWebPart - .CorrelateString(taskId), baseControl.Name); - - break; - } - } - } - - } - else - { - baseControl = componentsToAdd.FirstOrDefault(p => p.Name.Equals(webPartName, StringComparison.InvariantCultureIgnoreCase)); - - logger.LogInformation( - TransformationResources.Info_ContentUsingModernWebPart - .CorrelateString(taskId), webPartType); - } - - // If we found the web part as a possible candidate to use then add it - if (baseControl != null) - { - var jsonDecoded = WebUtility.HtmlDecode(this.tokenParser.ReplaceTargetTokens(targetPage.PnPContext, jsonControlData, webPartProperties, globalTokens)); - - var myWebPart = targetPage.NewWebPart(baseControl); - myWebPart.Order = controlOrder; - myWebPart.PropertiesJson = jsonDecoded; - - // Add the actual text control to the page - targetPage.AddControl(myWebPart, targetColumn, controlOrder); - - logger.LogInformation( - TransformationResources.Info_AddedClientSideWebPartToPage - .CorrelateString(taskId), webPartTitle); - } - else - { - logger.LogWarning( - TransformationResources.Warning_ContentWarnModernNotFound - .CorrelateString(taskId)); - } - - break; - default: - break; - } - } - - private async Task> GetClientSideComponentsAsync(IPage targetPage) - { - var siteToComponentMapping = new Dictionary(); - - var componentsToAdd = (await targetPage.AvailablePageComponentsAsync().ConfigureAwait(false)) - .Cast().ToList(); - - // TODO: Consider adding back caching - - return componentsToAdd; - } - - private JToken PopulateAddInProperties(JToken jsonProperties, Dictionary webPartProperties) - { - foreach (JToken property in jsonProperties["properties"]["clientWebPartProperties"]) - { - var wpProp = property["name"].Value(); - if (!string.IsNullOrEmpty(wpProp)) - { - if (webPartProperties.ContainsKey(wpProp)) - { - if (jsonProperties["properties"]["userDefinedProperties"][wpProp] != null) - { - jsonProperties["properties"]["userDefinedProperties"][wpProp] = webPartProperties[wpProp].ToString(); - } - else - { - JToken newProp = JObject.Parse($"{{\"{wpProp}\": \"{webPartProperties[wpProp].ToString()}\"}}"); - (jsonProperties["properties"]["userDefinedProperties"] as JObject).Merge(newProp); - } - } - } - } - - return jsonProperties; - } - - - internal async Task SetAuthorInPageHeaderAsync(PageTransformationContext context, MappingProviderOutput mappingOutput, IPage targetClientSidePage, Guid taskId, CancellationToken token = default) - { - // Try to get th - var userMappingProvider = serviceProvider.GetService(); - - if (userMappingProvider == null) - { - throw new ApplicationException(TransformationResources.Error_MissingUserMappingProvider); - } - - try - { - var mappedAuthorOutput = await userMappingProvider.MapUserAsync(new UserMappingProviderInput(context, mappingOutput.TargetPage.Author), token).ConfigureAwait(false); - var ensuredPageAuthorUser = await targetClientSidePage.PnPContext.Web.EnsureUserAsync(mappedAuthorOutput.UserPrincipalName).ConfigureAwait(false); - - if (ensuredPageAuthorUser != null) - { - var author = ensuredPageAuthorUser.ToUserEntity(); - - if (author != null) - { - if (!author.IsGroup) - { - // Don't serialize null values - var jsonSerializerOptions = new JsonSerializerOptions() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; - - var json = JsonSerializer.Serialize(author, jsonSerializerOptions); - - if (!string.IsNullOrEmpty(json)) - { - targetClientSidePage.PageHeader.Authors = json; - } - } - } - else - { - logger.LogWarning( - TransformationResources.Warning_PageHeaderAuthorNotSet - .CorrelateString(taskId), - mappingOutput.TargetPage.Author); - } - } - else - { - logger.LogWarning( - TransformationResources.Warning_PageHeaderAuthorNotSet - .CorrelateString(taskId), - mappingOutput.TargetPage.Author); - } - } - catch (Exception ex) - { - logger.LogWarning( - TransformationResources.Warning_PageHeaderAuthorNotSetGenericError - .CorrelateString(taskId), ex.Message); - } - } - - /// - /// Prepares global tokens for target environment - /// - /// - private async Task> PrepareGlobalTokensAsync(IWeb web) - { - Dictionary globalTokens = new Dictionary(5); - - await web.EnsurePropertiesAsync(w => w.Id, w => w.Url, w => w.ServerRelativeUrl).ConfigureAwait(false); - var site = web.PnPContext.Site; - await site.EnsurePropertiesAsync(s => s.Id, s => s.RootWeb.QueryProperties(rw => rw.ServerRelativeUrl)).ConfigureAwait(false); - - // Add the fixed properties - globalTokens.Add("Host", $"{web.Url.Scheme}://{web.Url.DnsSafeHost}"); - globalTokens.Add("Web", web.ServerRelativeUrl.TrimEnd('/')); - globalTokens.Add("SiteCollection", site.RootWeb.ServerRelativeUrl.TrimEnd('/')); - globalTokens.Add("WebId", web.Id.ToString()); - globalTokens.Add("SiteId", site.Id.ToString()); - - return globalTokens; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageTransformator.cs b/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageTransformator.cs deleted file mode 100644 index 2ab367735b..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/DefaultPageTransformator.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.MappingProviders; -using PnP.Core.Transformation.Extensions; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Implements the concrete PageTransformator (this is the core of PnP Transformation Framework) - /// - public class DefaultPageTransformator : IPageTransformator - { - private readonly ILogger logger; - private readonly IMappingProvider mappingProvider; - private readonly ITargetPageUriResolver targetPageUriResolver; - private readonly IEnumerable pagePreTransformations; - private readonly IEnumerable pagePostTransformations; - private readonly IOptions defaultPageTransformationOptions; - private readonly IPageGenerator pageGenerator; - private readonly TelemetryService telemetry; - - /// - /// Constructor with DI support - /// - /// The logger interface - /// The mapping provider interface - /// The target page uri resolver - /// The list of post transformations to call - /// The list of pre transformations to call - /// The options - /// The page generator to create the actual SPO modern page - /// The telemetry service - public DefaultPageTransformator( - ILogger logger, - IMappingProvider mappingProvider, - ITargetPageUriResolver targetPageUriResolver, - IEnumerable pagePreTransformations, - IEnumerable pagePostTransformations, - IOptions pageTransformationOptions, - IPageGenerator pageGenerator, - TelemetryService telemetry) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.mappingProvider = mappingProvider ?? throw new ArgumentNullException(nameof(mappingProvider)); - this.targetPageUriResolver = targetPageUriResolver ?? throw new ArgumentNullException(nameof(targetPageUriResolver)); - this.pagePreTransformations = pagePreTransformations ?? throw new ArgumentNullException(nameof(pagePreTransformations)); - this.pagePostTransformations = pagePostTransformations ?? throw new ArgumentNullException(nameof(pagePostTransformations)); - this.defaultPageTransformationOptions = pageTransformationOptions ?? throw new ArgumentNullException(nameof(pageTransformationOptions)); - this.pageGenerator = pageGenerator ?? throw new ArgumentNullException(nameof(pageGenerator)); - this.telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry)); - } - - /// - /// Transforms a page from the configured data source to a modern SharePoint Online page - /// - /// The context of the transformation process - /// The cancellation token, if any - /// The URL of the transformed page - public virtual async Task TransformAsync(PageTransformationTask task, CancellationToken token = default) - { - if (task == null) throw new ArgumentNullException(nameof(task)); - - logger.LogInformation( - TransformationResources.Info_RunningTransformationTask.CorrelateString(task.Id), - task.Id, task.SourceItemId.Id); - - // Get the source item by id - var sourceItem = await task.SourceProvider.GetItemAsync(task.SourceItemId, token).ConfigureAwait(false); - - // Resolve the target page uri - var targetPageUri = await targetPageUriResolver.ResolveAsync(sourceItem, task.TargetContext, token).ConfigureAwait(false); - - // Call pre transformations handlers - var preContext = new PagePreTransformationContext(task, sourceItem, targetPageUri); - foreach (var pagePreTransformation in pagePreTransformations) - { - await pagePreTransformation.PreTransformAsync(preContext, token).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - } - - // Save start date and time for telemetry - DateTime transformationStartDateTime = DateTime.Now; - - // Invoke the configured main mapping provider - var context = new PageTransformationContext(task, sourceItem, targetPageUri); - var input = new MappingProviderInput(context); - MappingProviderOutput output = await mappingProvider.MapAsync(input, token).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - - // Here we generate the actual SPO modern page in SharePoint Online - var generatedPage = await pageGenerator.GenerateAsync(context, output, targetPageUri, token).ConfigureAwait(false); - var generatedPageUri = generatedPage.GeneratedPageUrl; - token.ThrowIfCancellationRequested(); - - // Save duration for telemetry - TimeSpan duration = DateTime.Now.Subtract(transformationStartDateTime); - - // Save telemetry - var telemetryProperties = output.TelemetryProperties.Merge(generatedPage.TelemetryProperties); - - // Add global telemetry properties - telemetryProperties.Add(TelemetryService.CorrelationId, task.Id.ToString()); - telemetryProperties.Add(TelemetryService.AADTenantId, context.Task.TargetContext.GlobalOptions.AADTenantId.ToString()); - - this.telemetry.LogTransformationCompleted(duration, telemetryProperties); - - // Call post transformations handlers - var postContext = new PagePostTransformationContext(task, sourceItem, generatedPageUri); - foreach (var pagePostTransformation in pagePostTransformations) - { - await pagePostTransformation.PostTransformAsync(postContext, token).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - } - - return generatedPageUri; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/FileSystemAssetPersistenceProvider.cs b/src/sdk/PnP.Core.Transformation/Services/Core/FileSystemAssetPersistenceProvider.cs deleted file mode 100644 index b2e217ee4f..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/FileSystemAssetPersistenceProvider.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Microsoft.Extensions.Options; -using PnP.Core.Transformation.Services.Builder.Configuration; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Implements the basic functionality of an asset persistence provider using the local file system - /// - public class FileSystemAssetPersistenceProvider : IAssetPersistenceProvider - { - private readonly IOptions pnpTransformationOptions; - - /// - /// Public constructor for dependency injection - /// - /// PnP Transformation options to customize the behavior of the persistence provider - public FileSystemAssetPersistenceProvider(IOptions pnpTransformationOptions) - { - this.pnpTransformationOptions = pnpTransformationOptions ?? throw new ArgumentNullException(nameof(pnpTransformationOptions)); - } - - /// - /// Writes the binary blob of an asset onto the file system - /// - /// The blob content of the file - /// The name of the file to persist - /// The path of the persisted file - public async Task WriteAssetAsync(Stream content, string fileName) - { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } - - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - if (!content.CanSeek) - { - throw new Exception(TransformationResources.Error_InvalidAssetStreamToPersist); - } - - // Determine the destination path for the file - var path = Path.Combine(this.pnpTransformationOptions.Value.PersistenceProviderConnectionString, fileName); - - // Simply copy the blob stream into a file on the file system - using (var fs = File.Create(path)) - { - content.Seek(0, SeekOrigin.Begin); - await content.CopyToAsync(fs).ConfigureAwait(false); - } - - // Return the actual file name - return path; - } - - /// - /// Reads the binary blob of an asset from the file system - /// - /// The name of the persisted file - /// The blob content of the file - public async Task ReadAssetAsync(string fileName) - { - if (fileName == null) - { - throw new ArgumentNullException(nameof(fileName)); - } - - // Prepare an empty result - var result = new MemoryStream(); - - // Determine the source path for the file - var path = Path.Combine(this.pnpTransformationOptions.Value.PersistenceProviderConnectionString, fileName); - - // Simply read the file from the file system - using (var fs = File.OpenRead(path)) - { - // And copy its binary content into an in-memory stream - await fs.CopyToAsync(result).ConfigureAwait(false); - result.Position = 0; - } - - return result; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/IAssetPersistenceProvider.cs b/src/sdk/PnP.Core.Transformation/Services/Core/IAssetPersistenceProvider.cs deleted file mode 100644 index 9d2609e954..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/IAssetPersistenceProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.IO; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines the basic interface for an asset persistence provider - /// - public interface IAssetPersistenceProvider - { - /// - /// Writes the binary blob of an asset onto the persistence storage - /// - /// The blob content of the file - /// The name of the file to persist - /// The path of the persisted file - Task WriteAssetAsync(Stream content, string fileName); - - /// - /// Reads the binary blob of an asset from the persistence storage - /// - /// The name of the persisted file - /// The blob content of the file - Task ReadAssetAsync(string fileName); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/IPageGenerator.cs b/src/sdk/PnP.Core.Transformation/Services/Core/IPageGenerator.cs deleted file mode 100644 index 5f9469ead8..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/IPageGenerator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using PnP.Core.Transformation.Services.MappingProviders; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Provides the basic interface for a SPO modern Page Generator - /// - public interface IPageGenerator - { - /// - /// Generates a page from the output of a data source mapping to SPO modern - /// - /// The context of the transformation process - /// The output of the initial mapping from the data source - /// The URI of the target page to create - /// The cancellation token, if any - /// The output of the transformed page - Task GenerateAsync(PageTransformationContext context, MappingProviderOutput mappingOutput, Uri targetPageUri, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/IPagePostTransformation.cs b/src/sdk/PnP.Core.Transformation/Services/Core/IPagePostTransformation.cs deleted file mode 100644 index 841e8cc088..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/IPagePostTransformation.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Interface used to intercept a page after a transformation - /// - public interface IPagePostTransformation - { - /// - /// Method called when a page has been transformed - /// - /// The context contained all information related to the transformation - /// The cancellation token, if any - /// - Task PostTransformAsync(PagePostTransformationContext context, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/IPagePreTransformation.cs b/src/sdk/PnP.Core.Transformation/Services/Core/IPagePreTransformation.cs deleted file mode 100644 index 714fd5824b..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/IPagePreTransformation.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Interface used to intercept a page before a transformation - /// - public interface IPagePreTransformation - { - /// - /// Method called when a page is going to be transformed - /// - /// The context contained all information related to the transformation - /// The cancellation token, if any - /// - Task PreTransformAsync(PagePreTransformationContext context, CancellationToken token); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/IPageTransformator.cs b/src/sdk/PnP.Core.Transformation/Services/Core/IPageTransformator.cs deleted file mode 100644 index 586ec8cf67..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/IPageTransformator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Provides the basic interface for a Page Transformator - /// - public interface IPageTransformator - { - /// - /// Transforms a page from classic to modern - /// - /// The context of the transformation process - /// The cancellation token, if any - /// The URL of the transformed page - Task TransformAsync(PageTransformationTask task, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItem.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItem.cs deleted file mode 100644 index e48d659aa4..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItem.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Represents the source item - /// - public interface ISourceItem - { - /// - /// Gets the id of the source item - /// - ISourceItemId Id { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItemId.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItemId.cs deleted file mode 100644 index f3061f659f..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceItemId.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Represents the id of a source item - /// - public interface ISourceItemId - { - /// - /// Gets the string representation of the item - /// - string Id { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceProvider.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ISourceProvider.cs deleted file mode 100644 index 0379a52501..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ISourceProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Interface to implement in order to provide source items to transform - /// - public interface ISourceProvider - { - /// - /// Gets the id of each available item - /// - /// - /// The cancellation token to use, if any - IAsyncEnumerable GetItemsIdsAsync(CancellationToken token = default); - - /// - /// Get the item and related information based on its id - /// - /// - /// The cancellation token to use, if any - /// - Task GetItemAsync(ISourceItemId id, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITargetPageUriResolver.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITargetPageUriResolver.cs deleted file mode 100644 index a80ab17321..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITargetPageUriResolver.cs +++ /dev/null @@ -1,22 +0,0 @@ -using PnP.Core.Services; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Interface used to resolve the SharePoint target uri - /// - public interface ITargetPageUriResolver - { - /// - /// Resolves the SharePoint target uri for the specified source item - /// - /// The source item to transform - /// The target context to transform the item to - /// The cancellation token to use, if any - /// - Task ResolveAsync(ISourceItem sourceItem, PnPContext targetContext, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITokenDefinition.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITokenDefinition.cs deleted file mode 100644 index 33f351b5a5..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITokenDefinition.cs +++ /dev/null @@ -1,23 +0,0 @@ -using PnP.Core.Services; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Represents the basic signature of a target token definition that will be processed by the TokenParser - /// - internal interface ITokenDefinition - { - /// - /// Defines the name of the token - /// - string Name { get; } - - /// - /// Method to retrieve the value of specialized token instance - /// - /// The target PnP Context - /// A string input argument useful to resolve the token value - /// - string GetValue(PnPContext context, string argument); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationDistiller.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationDistiller.cs deleted file mode 100644 index 9cf65902b6..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationDistiller.cs +++ /dev/null @@ -1,21 +0,0 @@ -using PnP.Core.Services; -using System.Collections.Generic; -using System.Threading; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Abstract interface for a service that defines a list of items to transform - /// - public interface ITransformationDistiller - { - /// - /// Defines a list of Page Transformation Tasks - /// - /// A list of PageTransformationTask to transform - IAsyncEnumerable GetPageTransformationTasksAsync( - ISourceProvider sourceProvider, - PnPContext targetContext, - CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationExecutor.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationExecutor.cs deleted file mode 100644 index 600ef45b09..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationExecutor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Abstract interface for a service that manages the transformation of one or more pages - /// - public interface ITransformationExecutor - { - /// - /// Creates a Page Transformation process - /// - /// The cancellation token, if any - /// The transformation process - Task CreateTransformationProcessAsync(CancellationToken token = default); - - /// - /// Loads a Page Transformation process - /// - /// The ID of the process to load - /// The cancellation token, if any - /// The transformation process - Task LoadTransformationProcessAsync(Guid processId, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationProcess.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationProcess.cs deleted file mode 100644 index c0a6b9e6ca..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationProcess.cs +++ /dev/null @@ -1,66 +0,0 @@ -using PnP.Core.Services; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Represents a Transformation Process - /// - public interface ITransformationProcess - { - /// - /// The unique ID of a Transformation Process - /// - public Guid Id { get; } - - /// - /// Starts the Transformation Process - /// - /// The source provider to use - /// The PnPContext of the target site - /// The cancellation token, if any - Task StartProcessAsync(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token = default); - - /// - /// Stops the Transformation Process - /// - /// The cancellation token, if any - Task StopProcessAsync(CancellationToken token = default); - - /// - /// Allows monitoring the progress of the Transformation Process - /// - Func Progress { get; set; } - - /// - /// Allows monitoring the progress of each tasks - /// - Func TasksProgress { get; set; } - - /// - /// Gets the status of the Transformation Process - /// - /// The cancellation token, if any - /// The status of the Transformation Process - Task GetStatusAsync(CancellationToken token = default); - - /// - /// Gets the status of a single task - /// - /// The id of the task - /// The cancellation token, if any - /// The status of the Transformation Task - Task GetTaskStatusAsync(Guid id, CancellationToken token = default); - - /// - /// Gets the list of the tasks using the criteria specified into the query - /// - /// Query to use for filtering - /// The cancellation token, if any - /// The list of task statuses matching the query criteria - IAsyncEnumerable GetTasksStatusAsync(TasksStatusQuery query, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationStateManager.cs b/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationStateManager.cs deleted file mode 100644 index 5c3b24a715..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/ITransformationStateManager.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Abstract interface to handle the state of a transformation process managed by an implementation of ITransformationExecutor - /// - public interface ITransformationStateManager - { - /// - /// Allows to write the process status - /// - /// The status to write - /// The cancellation token, if any - /// - Task WriteProcessStatusAsync(TransformationProcessStatus status, CancellationToken token = default); - - /// - /// Allows to write the task status - /// - /// The status to write - /// The cancellation token, if any - /// - Task WriteTaskStatusAsync(TransformationProcessTaskStatus status, CancellationToken token = default); - - /// - /// Returns the list of tasks using the query specified - /// - /// The process id - /// A custom query to apply - /// The cancellation token, if any - /// A list of task statuses - IAsyncEnumerable GetProcessTasksStatus(Guid processId, TasksStatusQuery query, CancellationToken token = default); - - /// - /// Allows to read the process status by id - /// - /// The process id - /// The cancellation token, if any - /// The value of the state variable - Task ReadProcessStatusAsync(Guid processId, CancellationToken token = default); - - /// - /// Allows to read the task status by process and task ids - /// - /// The process id - /// The task id - /// The cancellation token, if any - /// The value of the status variable for the specified task - Task ReadTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default); - - /// - /// Allows to remove a process status by id - /// - /// The process id - /// The cancellation token, if any - Task RemoveProcessStatusAsync(Guid processId, CancellationToken token = default); - - /// - /// Allows to remove a task status by process and task ids - /// - /// The process id - /// The task id - /// The cancellation token, if any - Task RemoveTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/InMemoryTransformationStateManager.cs b/src/sdk/PnP.Core.Transformation/Services/Core/InMemoryTransformationStateManager.cs deleted file mode 100644 index b548737086..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/InMemoryTransformationStateManager.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// In memory implementation of - /// This class is thread safe - /// - public class InMemoryTransformationStateManager : ITransformationStateManager - { - private readonly ConcurrentDictionary taskStatuses = - new ConcurrentDictionary(); - - private readonly ConcurrentDictionary processStatuses = - new ConcurrentDictionary(); - - /// - /// Allows to write the process status - /// - /// The status to write - /// The cancellation token, if any - /// - public Task WriteProcessStatusAsync(TransformationProcessStatus status, CancellationToken token = default) - { - if (status == null) throw new ArgumentNullException(nameof(status)); - - processStatuses.AddOrUpdate(status.ProcessId, status, (k, o) => status); - return Task.CompletedTask; - } - - /// - /// Allows to write the task status - /// - /// The status to write - /// The cancellation token, if any - /// - public Task WriteTaskStatusAsync(TransformationProcessTaskStatus status, CancellationToken token = default) - { - if (status == null) throw new ArgumentNullException(nameof(status)); - - taskStatuses.AddOrUpdate(status.Id, status, (k, o) => status); - return Task.CompletedTask; - } - - /// - /// Returns the list of tasks statuses using the query specified - /// - /// The ID of the transformation process to query - /// The query to retrieve the tasks statuses related to the process - /// The cancellation token, if any - /// An asynchronous enumerable of tasks statuses -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async IAsyncEnumerable GetProcessTasksStatus(Guid processId, TasksStatusQuery query, [EnumeratorCancellation] CancellationToken token = default) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - if (query == null) throw new ArgumentNullException(nameof(query)); - - foreach (var pair in taskStatuses) - { - if (!query.State.HasValue || pair.Value.State == query.State.Value) - { - yield return pair.Value; - } - } - } - - /// - /// Allows to read the process status by id - /// - /// The process id - /// The cancellation token, if any - /// The value of the state variable - public Task ReadProcessStatusAsync(Guid processId, CancellationToken token = default) - { - processStatuses.TryGetValue(processId, out var result); - - return Task.FromResult(result); - } - - /// - /// Allows to read the task status by process and task ids - /// - /// The process id - /// The task id - /// The cancellation token, if any - /// The value of the state variable - public Task ReadTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default) - { - taskStatuses.TryGetValue(taskId, out var result); - - return Task.FromResult(result); - } - - /// - /// Allows to remove a process status by id - /// - /// The process id - /// The cancellation token, if any - public Task RemoveProcessStatusAsync(Guid processId, CancellationToken token = default) - { - bool result = taskStatuses.TryRemove(processId, out _); - - return Task.FromResult(result); - } - - /// - /// Allows to remove a task status by process and task ids - /// - /// The process id - /// The task id - /// The cancellation token, if any - public Task RemoveTaskStatusAsync(Guid processId, Guid taskId, CancellationToken token = default) - { - bool result = processStatuses.TryRemove(taskId, out _); - - return Task.FromResult(result); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationExecutor.cs b/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationExecutor.cs deleted file mode 100644 index 0c638d30de..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationExecutor.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Implementation of that works sequentially and in process - /// - /// This class is thread safe - public class InProcessTransformationExecutor : ITransformationExecutor, IDisposable - { - private readonly IServiceProvider serviceProvider; - - private readonly ConcurrentDictionary processes = new ConcurrentDictionary(); - - /// - /// Constructor with Dependency Injection - /// - public InProcessTransformationExecutor(IServiceProvider serviceProvider) - { - this.serviceProvider = serviceProvider; - } - - /// - /// Creates a Page Transformation process - /// - /// The cancellation token, if any - /// The transformation process - public Task CreateTransformationProcessAsync(CancellationToken token = default) - { - var transformationDistiller = serviceProvider.GetRequiredService(); - var pageTransformator = serviceProvider.GetRequiredService(); - var logger = serviceProvider.GetRequiredService>(); - - ITransformationProcess result = new InProcessTransformationProcess( - Guid.NewGuid(), - logger, - transformationDistiller, - pageTransformator); - - processes.TryAdd(result.Id, result); - - return Task.FromResult(result); - } - - /// - /// Loads a Page Transformation process - /// - /// The ID of the process to load - /// The cancellation token, if any - /// The transformation process - public Task LoadTransformationProcessAsync(Guid processId, - CancellationToken token = default) - { - if (processes.TryGetValue(processId, out ITransformationProcess tp)) - { - return Task.FromResult(tp); - } - - throw new ArgumentException($"Cannot find process with id {processId}"); - } - - /// - /// Overridable dispose pattern - /// - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - foreach (var pair in processes) - { - (pair.Value as IDisposable)?.Dispose(); - } - processes.Clear(); - } - } - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationProcess.cs b/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationProcess.cs deleted file mode 100644 index b8c7c2a969..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/InProcessTransformationProcess.cs +++ /dev/null @@ -1,215 +0,0 @@ -using Microsoft.Extensions.Logging; -using PnP.Core.Services; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// In process implementation of - /// - /// This class is thread safe - public class InProcessTransformationProcess : TransformationProcessBase, IDisposable - { - private readonly ILogger logger; - private readonly ITransformationDistiller transformationDistiller; - private readonly IPageTransformator pageTransformator; - - private CancellationTokenSource cancellationTokenSource; - private TransformationProcessStatus status; - private readonly ConcurrentDictionary tasksStatuses; - - /// - /// Public constructor - /// - /// The id of the process - /// A custom logger - /// The Page Transformation Distiller to collect items to process - /// The Page Transformator instance to use - public InProcessTransformationProcess( - Guid id, - ILogger logger, - ITransformationDistiller transformationDistiller, - IPageTransformator pageTransformator) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.transformationDistiller = transformationDistiller ?? throw new ArgumentNullException(nameof(transformationDistiller)); - this.pageTransformator = pageTransformator ?? throw new ArgumentNullException(nameof(pageTransformator)); - - Id = id; - - status = new TransformationProcessStatus(Id, TransformationExecutionState.Pending); - tasksStatuses = new ConcurrentDictionary(); - } - - /// - /// The unique ID of a Transformation Process - /// - public override Guid Id { get; } - - /// - /// Starts the Transformation Process - /// - /// The source provider to use - /// The PnPContext of the target site - /// The cancellation token, if any - public override async Task StartProcessAsync(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token = default) - { - if (cancellationTokenSource != null) - { - throw new InvalidOperationException("Process cannot start twice"); - } - - cancellationTokenSource = new CancellationTokenSource(); - - await ChangeProcessStateAsync(TransformationExecutionState.Running).ConfigureAwait(false); - - var runToken = cancellationTokenSource.Token; - _ = Task.Run(() => Run(sourceProvider, targetContext, runToken), token); - } - - private async Task Run(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token) - { - try - { - // Iterate through each task - await foreach (var task in transformationDistiller.GetPageTransformationTasksAsync(sourceProvider, targetContext, token).ConfigureAwait(false)) - { - - var taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, DateTimeOffset.Now, DateTimeOffset.Now, null, TransformationTaskExecutionState.Running); - try - { - // Save the task status to pending - await ChangeTaskStatusAsync(taskStatus).ConfigureAwait(false); - - // Execute the actual transformation task - await pageTransformator.TransformAsync(task, token).ConfigureAwait(false); - - // Save the task status to completed - taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, TransformationTaskExecutionState.Completed); - await ChangeTaskStatusAsync(taskStatus).ConfigureAwait(false); - } - catch (Exception ex) - { - logger.LogError(ex, "Error while transforming task {id}", task.Id); - - // Save the task status to faulted - taskStatus = TransformationProcessTaskStatus.CreateFaulted(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, ex); - await ChangeTaskStatusAsync(taskStatus).ConfigureAwait(false); - } - - } - - // Mark the process as completed - await ChangeProcessStateAsync(TransformationExecutionState.Completed).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Mark the process as aborted and ignore this kind of error - await ChangeProcessStateAsync(TransformationExecutionState.Aborted).ConfigureAwait(false); - } - catch (Exception ex) - { - logger.LogError(ex, "The entire process has failed"); - // Mark the process as faulted - await ChangeProcessStateAsync(TransformationExecutionState.Faulted).ConfigureAwait(false); - } - } - - /// - /// Stops the Transformation Process - /// - /// The cancellation token, if any - public override Task StopProcessAsync(CancellationToken token = default) - { - cancellationTokenSource?.Cancel(); - - return Task.CompletedTask; - } - - /// - /// Allows to retrieve the status of the Transformation Process - /// - /// The cancellation token, if any - /// The status of the Transformation Process - public override Task GetStatusAsync(CancellationToken token = default) - { - return Task.FromResult(status); - } - - /// - /// Gets the status of a single task - /// - /// The id of the task - /// The cancellation token, if any - /// The status of the single Task - public override Task GetTaskStatusAsync(Guid id, CancellationToken token = default) - { - TransformationProcessTaskStatus status; - if (!tasksStatuses.TryGetValue(id, out status)) - { - throw new ArgumentException($"Cannot find task with id {id}", nameof(id)); - } - - return Task.FromResult(status); - } - - /// - /// Gets the list of the tasks using the criteria specified into the query - /// - /// Query to use for filtering - /// The cancellation token, if any - /// -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public override async IAsyncEnumerable GetTasksStatusAsync(TasksStatusQuery query, [EnumeratorCancellation] CancellationToken token = default) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - { - if (query == null) throw new ArgumentNullException(nameof(query)); - - foreach (var pair in tasksStatuses) - { - // Filter task - if (!query.State.HasValue || pair.Value.State == query.State) - { - yield return pair.Value; - } - } - } - - /// - /// Overridable dispose pattern - /// - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - cancellationTokenSource?.Dispose(); - } - } - - private Task ChangeProcessStateAsync(TransformationExecutionState state) - { - status = new TransformationProcessStatus(Id, state); - return RaiseProgressAsync(status); - } - - private Task ChangeTaskStatusAsync(TransformationProcessTaskStatus status) - { - tasksStatuses[status.Id] = status; - - return RaiseTasksProgressAsync(status); - } - - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationExecutorBase.cs b/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationExecutorBase.cs deleted file mode 100644 index e62d27cbb7..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationExecutorBase.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Implementation of that persists state for a long running transformation - /// - public abstract class LongRunningTransformationExecutorBase : ITransformationExecutor - { - /// - /// The provider to use to resolve dependencies - /// - public IServiceProvider ServiceProvider { get; } - - /// - /// Constructor with Dependency Injection - /// - protected LongRunningTransformationExecutorBase(IServiceProvider serviceProvider) - { - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - /// - /// Creates a Page Transformation process - /// - /// The cancellation token, if any - /// The transformation process - public virtual async Task CreateTransformationProcessAsync( - CancellationToken token = default) - { - var process = CreateProcess(Guid.NewGuid()); - await process.InitAsync(token).ConfigureAwait(false); - - return process; - } - - /// - /// Loads a Page Transformation process - /// - /// The ID of the process to load - /// The cancellation token, if any - /// The transformation process - public virtual Task LoadTransformationProcessAsync(Guid processId, - CancellationToken token = default) - { - // When a long running process is restored, source provider and target context are not available - ITransformationProcess process = CreateProcess(processId); - - return Task.FromResult(process); - } - - /// - /// Creates a new instance of a inherited of type - /// - /// The process id - /// A new instance of a long running transformation process - protected abstract LongRunningTransformationProcessBase CreateProcess(Guid id); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationProcessBase.cs b/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationProcessBase.cs deleted file mode 100644 index c9a30a5e17..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/LongRunningTransformationProcessBase.cs +++ /dev/null @@ -1,280 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PnP.Core.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Base implementation of for long running processes - /// - public abstract class LongRunningTransformationProcessBase : TransformationProcessBase - { - /// - /// Creates an instance - /// - /// The process id - /// The service provider to use to resolve dependencies - protected LongRunningTransformationProcessBase( - Guid id, - IServiceProvider serviceProvider) - { - Id = id; - ServiceProvider = serviceProvider; - - TransformationStateManager = serviceProvider.GetRequiredService(); - } - - - /// - /// The unique ID of a Transformation Process - /// - public override Guid Id { get; } - - /// - /// The service provider to use to resolve dependencies - /// - protected IServiceProvider ServiceProvider { get; } - - /// - /// The state manager used - /// - protected ITransformationStateManager TransformationStateManager { get; } - - /// - /// Starts the Transformation Process - /// - public override async Task StartProcessAsync(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token = default) - { - // Check if process is already running - var status = await GetStatusAsync(token).ConfigureAwait(false); - if (status.State != TransformationExecutionState.Pending) - { - throw new InvalidOperationException("Process cannot be started twice!"); - } - - var transformationDistiller = ServiceProvider.GetRequiredService(); - - // Change state to running - await ChangeProcessStatusAsync(TransformationExecutionState.Running, token).ConfigureAwait(false); - - try - { - await foreach (var task in transformationDistiller.GetPageTransformationTasksAsync(sourceProvider, targetContext, token).WithCancellation(token)) - { - // Mark task as pending - await ChangeTaskStatusAsync(TransformationProcessTaskStatus.CreatePending(Id, task.Id, DateTimeOffset.Now), token) - .ConfigureAwait(false); - - // Queue item - await EnqueueTaskAsync(task, token).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - // Change state to aborted - await ChangeProcessStatusAsync(TransformationExecutionState.Aborted, token).ConfigureAwait(false); - throw; - } - catch (Exception) - { - // Change state to faulted - await ChangeProcessStatusAsync(TransformationExecutionState.Faulted, token).ConfigureAwait(false); - throw; - } - } - - /// - /// Adds the task to a queue in order to process it asynchronously - /// - /// The task item to enqueue - /// The cancellation token, if any - protected abstract Task EnqueueTaskAsync(PageTransformationTask task, CancellationToken token); - - /// - /// Stops the Transformation Process - /// - /// The cancellation token, if any - public override async Task StopProcessAsync(CancellationToken token = default) - { - // Change state to aborted - await ChangeProcessStatusAsync(TransformationExecutionState.Aborted, token).ConfigureAwait(false); - } - - /// - /// Allows to retrieve the status of the Transformation Process - /// - /// The cancellation token, if any - /// The status of the Transformation Process - public override async Task GetStatusAsync(CancellationToken token = default) - { - var status = await TransformationStateManager.ReadProcessStatusAsync(Id, token).ConfigureAwait(false); - status ??= new TransformationProcessStatus(Id, TransformationExecutionState.Pending); - - // If status is running we need to check if there is any item in pending state - if (status.State == TransformationExecutionState.Running) - { - bool hasPending = false; - await foreach (var task in GetTasksStatusAsync(new TasksStatusQuery(TransformationTaskExecutionState.Pending), token).ConfigureAwait(false)) - { - hasPending = true; - break; - } - - // No more tasks in pending state - if (!hasPending) - { - // Automatically set the state to completed - status = await ChangeProcessStatusAsync(TransformationExecutionState.Completed, token).ConfigureAwait(false); - } - } - - return status; - } - - /// - /// Initialize the process - /// - /// The cancellation token, if any - public virtual Task InitAsync(CancellationToken token = default) - { - // Save the initial state - return ChangeProcessStatusAsync(TransformationExecutionState.Pending, token); - } - - /// - /// Process a task and update the status - /// - /// The task item to process - /// The cancellation token, if any - /// - public virtual async Task ProcessTaskAsync(PageTransformationTask task, - CancellationToken token = default) - { - if (task == null) throw new ArgumentNullException(nameof(task)); - - var logger = ServiceProvider.GetRequiredService>(); - var pageTransformator = ServiceProvider.GetRequiredService(); - - // Retrieve the status for the task - var taskStatus = await GetTaskStatusAsync(task.Id, token).ConfigureAwait(false); - - // Check if task has been already processed - if (taskStatus.State != TransformationTaskExecutionState.Pending) - { - // Skip - return taskStatus; - } - - // Retrieve current process status - var status = await GetStatusAsync(token).ConfigureAwait(false); - - // Process is not running, skip the task - if (status.State != TransformationExecutionState.Running) - { - // Mark task as aborted - taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, TransformationTaskExecutionState.Aborted); - await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false); - return taskStatus; - } - - try - { - // Mark task as running - taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, DateTimeOffset.Now, null, TransformationTaskExecutionState.Running); - await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false); - - // Run the actual transformation task - await pageTransformator.TransformAsync(task, token).ConfigureAwait(false); - - // Mark task as completed - taskStatus = TransformationProcessTaskStatus.CreateNormal(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, TransformationTaskExecutionState.Completed); - await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false); - - return taskStatus; - } - catch (Exception ex) - { - logger.LogError(ex, "Error while transforming task {id}", task.Id); - - // Mark task as faulted - taskStatus = TransformationProcessTaskStatus.CreateFaulted(Id, task.Id, taskStatus.CreationDate, taskStatus.StartDate, DateTimeOffset.Now, ex); - await ChangeTaskStatusAsync(taskStatus, token).ConfigureAwait(false); - - return taskStatus; - } - } - - /// - /// Gets the status of a single task - /// - /// The id of the task - /// Cancellation token to use, if any - /// The process task status - public override async Task GetTaskStatusAsync(Guid id, CancellationToken token = default) - { - var taskStatus = await TransformationStateManager.ReadTaskStatusAsync(Id, id, token).ConfigureAwait(false); - if (taskStatus != null) - { - return taskStatus; - } - - throw new ArgumentException($"Cannot find task with id {id}", nameof(id)); - } - - /// - /// Gets the list of the tasks using the criteria specified into the query - /// - /// Query to use for filtering - /// Cancellation token to use, if any - /// The process task status - public override IAsyncEnumerable GetTasksStatusAsync(TasksStatusQuery query, CancellationToken token = default) - { - if (query == null) throw new ArgumentNullException(nameof(query)); - - return TransformationStateManager.GetProcessTasksStatus(Id, query, token); - } - - /// - /// Sets the state of the process - /// - /// The process status - /// Cancellation token to use, if any - /// The updated process status - protected virtual async Task ChangeProcessStatusAsync(TransformationExecutionState status, CancellationToken token) - { - // NOTE: This one returns the TransformationProcessStatus, while the next one doesn't (for the task) - // Evaluate a different and more consistent approach - - var newStatus = new TransformationProcessStatus(Id, status); - await TransformationStateManager - .WriteProcessStatusAsync(newStatus, token) - .ConfigureAwait(false); - - await RaiseProgressAsync(newStatus).ConfigureAwait(false); - - return newStatus; - } - - /// - /// Sets the status of a task - /// - /// The process task status - /// Cancellation token to use, if any - protected virtual async Task ChangeTaskStatusAsync(TransformationProcessTaskStatus status, CancellationToken token) - { - if (status == null) throw new ArgumentNullException(nameof(status)); - - // Save the new state - await TransformationStateManager - .WriteTaskStatusAsync( status, token) - .ConfigureAwait(false); - - await RaiseTasksProgressAsync(status).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PageGeneratorOutput.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PageGeneratorOutput.cs deleted file mode 100644 index f965bd2801..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PageGeneratorOutput.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines the output of a page generation - /// - public class PageGeneratorOutput - { - /// - /// The URL of the generated page - /// - public Uri GeneratedPageUrl { get; set; } - - /// - /// Defines a dictionary of custom properties retrieved during page generation and useful for telemetry - /// - public Dictionary TelemetryProperties { get; set; } = new Dictionary(); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PagePostTransformationContext.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PagePostTransformationContext.cs deleted file mode 100644 index 7fb6c3d051..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PagePostTransformationContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Object containing information about a page that has been transformed - /// - public class PagePostTransformationContext : PageTransformationContext - { - /// - /// Creates an instance of PagePostTransformationContext - /// - /// The page transformation task - /// The source item of the transformation - /// The target URI of the transformed page - public PagePostTransformationContext(PageTransformationTask task, ISourceItem sourceItem, Uri targetPageUri) : base(task, sourceItem, targetPageUri) - { - } - - // NOTE: Still under construction - // TODO: add transformation result properties - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PagePreTransformationContext.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PagePreTransformationContext.cs deleted file mode 100644 index 1ea98e1207..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PagePreTransformationContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Object containing information about a page to be transformed - /// - public class PagePreTransformationContext : PageTransformationContext - { - /// - /// Creates an instance of PagePreTransformationContext - /// - /// The page transformation task - /// The source item of the transformation - /// The target URI of the transformed page - public PagePreTransformationContext(PageTransformationTask task, ISourceItem sourceItem, Uri targetPageUri) : base(task, sourceItem, targetPageUri) - { - } - - // NOTE: Still under construction - // TODO: add source properties - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationContext.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationContext.cs deleted file mode 100644 index c16949aed4..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Object containing information about transformation of a page - /// - public class PageTransformationContext - { - /// - /// Creates an instance of PageTransformationContext - /// - /// The page transformation task - /// The source item of the transformation - /// The target URI of the transformed page - public PageTransformationContext(PageTransformationTask task, ISourceItem sourceItem, Uri targetPageUri) - { - Task = task ?? throw new ArgumentNullException(nameof(task)); - SourceItem = sourceItem ?? throw new ArgumentNullException(nameof(sourceItem)); - TargetPageUri = targetPageUri ?? throw new ArgumentNullException(nameof(targetPageUri)); - } - - /// - /// Gets the task of the current transformation - /// - public PageTransformationTask Task { get; } - - /// - /// Gets the source item for the current transformation - /// - public ISourceItem SourceItem { get; } - - /// - /// Gets the target page uri for current transformation - /// - public Uri TargetPageUri { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationOptions.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationOptions.cs deleted file mode 100644 index 1b626df474..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationOptions.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines the options to transform a page into a SharePoint Online modern page - /// - public class PageTransformationOptions - { - #region Generic transformation properties - - /// - /// Defines whether the target content should be overwritten. Defaults to true. - /// - public bool Overwrite { get; set; } = true; - - /// - /// Defines whether to keep item level page permissions on target or not. Defaults to true. - /// - public bool KeepPageSpecificPermissions { get; set; } = true; - - /// - /// Set this property to true in case you want to retain the page's Author/Editor/Created/Modified fields. Note that a page publish will always set Editor/Modified. Defaults to false. - /// - public bool KeepPageCreationModificationInformation { get; set; } - - /// - /// Defines whether the target page will be automatically published. Defaults to true. - /// - public bool PublishPage { get; set; } = true; - - /// - /// Defines whether the target page will have page comments enabled or disabled. Defaults to false (i.e. comments enabled). - /// - public bool DisablePageComments { get; set; } - - /// - /// Defines whether to post the created page as news. Defaults to false. - /// - public bool PostAsNews { get; set; } - - /// - /// If the page to be transformed is the web's home page, then replace with stock modern home page instead of transforming it. Defaults to false. - /// - public bool ReplaceHomePageWithDefaultHomePage { get; set; } - - #endregion - - #region Other page transformation properties - - /// - /// Defines if the target page will get the name of the source page. Defaults to false. - /// - public bool TargetPageTakesSourcePageName { get; set; } - - /// - /// Name of the transformed page. Leave it blank to reuse the source page name. - /// - public string TargetPageName { get; set; } - - /// - /// Target page folder. - /// - public string TargetPageFolder { get; set; } - - /// - /// Sets the page author in the page header similar to the original page author. Default to false. - /// - public bool SetAuthorInPageHeader { get; set; } - - /// - /// Prefix used to name the target page. - /// - public string TargetPagePrefix { get; set; } = "Migrated_"; - - /// - /// Copy the page metadata (if any) to the created modern client side page. Defaults to false. - /// - public bool CopyPageMetadata { get; set; } - - #endregion - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationTask.cs b/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationTask.cs deleted file mode 100644 index 8281f9c611..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/PageTransformationTask.cs +++ /dev/null @@ -1,49 +0,0 @@ -using PnP.Core.Services; -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines a page transformation task - /// - public class PageTransformationTask - { - /// - /// Creates an instance of the task with a new id - /// - public PageTransformationTask(ISourceProvider sourceProvider, ISourceItemId sourceItemId, PnPContext targetContext) : this(Guid.NewGuid(), sourceProvider, sourceItemId, targetContext) - { - } - - /// - /// Creates an instance of the task - /// - public PageTransformationTask(Guid id, ISourceProvider sourceProvider, ISourceItemId sourceItemId, PnPContext targetContext) - { - Id = id; - SourceProvider = sourceProvider ?? throw new ArgumentNullException(nameof(sourceProvider)); - SourceItemId = sourceItemId ?? throw new ArgumentNullException(nameof(sourceItemId)); - TargetContext = targetContext ?? throw new ArgumentNullException(nameof(targetContext)); - } - - /// - /// The source provider to use for the task - /// - public ISourceProvider SourceProvider { get; } - - /// - /// The source item id to process - /// - public ISourceItemId SourceItemId { get; } - - /// - /// The target PnP context - /// - public PnPContext TargetContext { get; } - - /// - /// The unique ID of the transformation task - /// - public Guid Id { get; } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TasksStatusQuery.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TasksStatusQuery.cs deleted file mode 100644 index ac84041495..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TasksStatusQuery.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Object defining filters and sorting criteria for transformation tasks - /// - public class TasksStatusQuery - { - /// - /// Creates an instance to identify a query which returns all items - /// - public TasksStatusQuery() - { } - - /// - /// Creates an instance to query only items with a specific state - /// - /// - public TasksStatusQuery(TransformationTaskExecutionState state) - { - State = state; - } - - /// - /// The optional state to filter by - /// - public TransformationTaskExecutionState? State { get; } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TelemetryService.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TelemetryService.cs deleted file mode 100644 index 99dafdff1d..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TelemetryService.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Extensibility; -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Service that handles telemetry for pages transformation and/or errors - /// - public class TelemetryService - { - // Intentionally hard-coded - private const string TELEMETRY_INSTRUMENTATION_KEY = "373400f5-a9cc-48f3-8298-3fd7f4c063d6"; - private const string TELEMETRY_ROLE_INSTANCE = "SharePointPnPPageTransformation"; - - private readonly TelemetryClient telemetryClient; - private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault(); - - private string version; - - internal const string CorrelationId = "PnPCorrelationId"; - internal const string AADTenantId = "AADTenantId"; - private const string PageTransformed = "PageTransformed"; - private const string EngineVersion = "Version"; - private const string Duration = "Duration"; - - #region Construction - /// - /// Instantiates the telemetry client - /// - public TelemetryService() - { - try - { - this.version = this.GetType().Assembly.FullName; - -#pragma warning disable CS0618 // Type or member is obsolete - this.telemetryConfiguration.InstrumentationKey = TELEMETRY_INSTRUMENTATION_KEY; -#pragma warning restore CS0618 // Type or member is obsolete - - this.telemetryClient = new TelemetryClient(this.telemetryConfiguration); - - this.telemetryClient.Context.Session.Id = Guid.NewGuid().ToString(); - this.telemetryClient.Context.Cloud.RoleInstance = TELEMETRY_ROLE_INSTANCE; - this.telemetryClient.Context.Device.OperatingSystem = Environment.OSVersion.ToString(); - } - catch - { - this.telemetryClient = null; - } - } - #endregion - - /// - /// Sends a transformation completed event - /// - /// Duration of the page transformation - /// Additional properties to store with telemetry - public void LogTransformationCompleted(TimeSpan duration, Dictionary telemetryProperties) - { - if (this.telemetryClient == null) - { - return; - } - - try - { - try - { - // Prepare event data - Dictionary properties = new Dictionary(telemetryProperties); - Dictionary metrics = new Dictionary(5); - - // Page transformation engine version - properties.Add(EngineVersion, TelemetryService.GetVersion()); - - // Populate metrics - if (duration != TimeSpan.Zero) - { - // How long did it take to transform this page - metrics.Add(Duration, duration.TotalSeconds); - } - - // Send the event - this.telemetryClient.TrackEvent(PageTransformed, properties, metrics); - } - finally - { - // before exit, flush the remaining data - this.telemetryClient.Flush(); - } - } - catch - { - // Eat all exceptions - } - } - - /// - /// Logs a page transformation error - /// - /// Exception object - /// Additional properties to store with telemetry - /// Location that generated this error - public void LogError(Exception ex, Dictionary telemetryProperties, string location = null) - { - if (this.telemetryClient == null || ex == null) - { - return; - } - - try - { - try - { - // Prepare event data - Dictionary properties = new Dictionary(telemetryProperties); - Dictionary metrics = new Dictionary(); - - // Page transformation engine version - properties.Add(EngineVersion, TelemetryService.GetVersion()); - - if (!string.IsNullOrEmpty(location)) - { - properties.Add("Location", location); - } - - this.telemetryClient.TrackException(ex, properties, metrics); - } - finally - { - // before exit, flush the remaining data - this.telemetryClient.Flush(); - } - } - catch - { - // Eat all exceptions - } - } - - /// - /// Gets the version of the assembly - /// - /// - internal static string GetVersion() - { - try - { - var coreAssembly = Assembly.GetExecutingAssembly(); - return ((AssemblyFileVersionAttribute)coreAssembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version.ToString(); - } - catch - { - // Catch all and skip - } - - return "undefined"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TokenParser.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TokenParser.cs deleted file mode 100644 index 70aaed0be8..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TokenParser.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using PnP.Core.Services; -using PnP.Core.Transformation.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Resolves tokens by their actual representation - /// - public class TokenParser - { - private readonly ILogger logger; - private readonly IMemoryCache memoryCache; - private readonly IServiceProvider serviceProvider; - - /// - /// Public constructor with dependency injection support - /// - /// - /// - public TokenParser( - ILogger logger, - IServiceProvider serviceProvider) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.serviceProvider = serviceProvider; - this.memoryCache = this.serviceProvider.GetService(); - } - - /// - /// Replaces the tokens in the provided input string with their values working on the data source side of the transformation flow - /// - /// String with tokens - /// Web part properties - /// Global context properties - /// A string with tokens replaced by actual values - public string ReplaceSourceTokens(string input, Dictionary webPartProperties, Dictionary globalProperties = null) - { - if (string.IsNullOrWhiteSpace(input)) - { - return input; - } - - if (webPartProperties == null) - { - throw new ArgumentNullException(nameof(webPartProperties)); - } - - // Merge the web part properties and the global tokens, if any - var properties = MergePropertiesAndGlobalTokens(webPartProperties, globalProperties); - - return ProcessStandardTokens(ref input, properties); - } - - /// - /// Replaces the tokens in the provided input string with their values - /// - /// Defines the target PnP Context - /// String with tokens - /// Web part properties - /// Global context properties - /// A string with tokens replaced by actual values - public string ReplaceTargetTokens(PnPContext context, string input, Dictionary webPartProperties, Dictionary globalProperties = null) - { - if (string.IsNullOrWhiteSpace(input)) - { - return input; - } - - if (webPartProperties == null) - { - throw new ArgumentNullException(nameof(webPartProperties)); - } - - // Merge the web part properties and the global tokens, if any - var properties = MergePropertiesAndGlobalTokens(webPartProperties, globalProperties); - - input = ProcessStandardTokens(ref input, properties); - - // Retrieve the custom token processors - var tokenDefinitions = this.serviceProvider.GetServices(); - - // Manage the custom token definitions - foreach (var property in properties) - { - if (!string.IsNullOrEmpty(property.Value) && property.Value.Contains("{")) - { - foreach (var tokenDefinition in tokenDefinitions) - { - var regex = new Regex($"{{{tokenDefinition.Name}\\:(?[\\w\\|\\d\\-\\.\\:\\\\\\/]*)}}", RegexOptions.IgnoreCase); - if (regex.IsMatch(property.Value)) - { - var match = regex.Match(property.Value); - - // Get the matching "argument" - var argument = match.Groups["argument"]?.Value; - - // Assign the token parser result to the input - var tokenValue = tokenDefinition.GetValue(context, argument); - if (tokenValue != null) - { - input = regex.Replace(input, tokenValue); - } - } - } - } - } - - return input; - } - - private static Dictionary MergePropertiesAndGlobalTokens(Dictionary webPartProperties, Dictionary globalProperties = null) - { - // Merge the web part properties and the global properties, if any - var properties = webPartProperties.Merge(globalProperties); - if (globalProperties != null && globalProperties.Count > 0) - { - foreach (var webPartPropertyKey in properties.Keys.ToList()) - { - if (globalProperties.ContainsKey(webPartPropertyKey)) - { - properties[webPartPropertyKey] = globalProperties[webPartPropertyKey]; - } - } - } - - return properties; - } - - private static string ProcessStandardTokens(ref string input, Dictionary properties) - { - var tokenChars = new[] { '{' }; - if (string.IsNullOrEmpty(input) || input.IndexOfAny(tokenChars) == -1) - { - return input; - } - - // Manage any of the native tokens - string origInput; - do - { - origInput = input; - foreach (var property in properties) - { - if (property.Value != null) - { - var regex = new Regex($"{{{property.Key}}}", RegexOptions.IgnoreCase); - if (regex.IsMatch(input)) - { - // $0 in the replacement value is replaced by the input...need to escape it first to avoid getting into a recursive loop - // See https://stackoverflow.com/questions/41227351/trouble-with-0-in-regex-replace-c-sharp/41229868#41229868 - input = regex.Replace(input, property.Value.Replace("$0", "$$0")); - } - } - } - } while (origInput != input && input.IndexOfAny(tokenChars) >= 0); - - return input; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/AssetPersistenceProviderToken.cs b/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/AssetPersistenceProviderToken.cs deleted file mode 100644 index ea67f5000b..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/AssetPersistenceProviderToken.cs +++ /dev/null @@ -1,58 +0,0 @@ -using PnP.Core.Model.SharePoint; -using PnP.Core.Services; -using System; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core.Tokens -{ - /// - /// Resolves an asset URL and eventually uploads the asset from the persistence provider to the target site - /// - internal class AssetPersistenceProviderToken : ITokenDefinition - { - private readonly IAssetPersistenceProvider assetPersistenceProvider; - - public AssetPersistenceProviderToken(IAssetPersistenceProvider assetPersistenceProvider) - { - // Retrieve the currently configured instance of the assets persistence provider - this.assetPersistenceProvider = assetPersistenceProvider ?? throw new ArgumentNullException(nameof(assetPersistenceProvider)); - } - - public string Name { get => "AssetPersistenceProvider"; } - - public string GetValue(PnPContext context, string argument) - { - var arguments = argument.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - - if (arguments.Length != 2) - { - throw new ArgumentException(TransformationResources.Error_InvalidArgumentForAssetPersistenceProviderToken); - } - - var persistedFilePath = arguments[0]; - var assetSiteRelativeUrl = arguments[1]; - var targetFilePath = assetSiteRelativeUrl.Substring(0, assetSiteRelativeUrl.LastIndexOf('/')); - - // In case the file comes from a publishing portal - // we need to replace PublishingImages with SiteAssets - if (targetFilePath.Equals("/PublishingImages", StringComparison.InvariantCultureIgnoreCase)) - { - targetFilePath = "/SiteAssets"; - } - var targetFileName = assetSiteRelativeUrl.Substring(assetSiteRelativeUrl.LastIndexOf('/') + 1); - - Task.Run(async () => { - - // Get the file stream from the persistence provider - var stream = await this.assetPersistenceProvider.ReadAssetAsync(persistedFilePath).ConfigureAwait(false); - - // Handle SPO upload - IFolder targetFolder = await context.Web.GetFolderByServerRelativeUrlAsync($"{context.Web.ServerRelativeUrl}{targetFilePath}").ConfigureAwait(false); - IFile addedFile = await targetFolder.Files.AddAsync(targetFileName, stream, true).ConfigureAwait(false); - - }).GetAwaiter().GetResult(); - - return $"{context.Web.ServerRelativeUrl}{targetFilePath}/{targetFileName}"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/SiteRelativeLink.cs b/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/SiteRelativeLink.cs deleted file mode 100644 index e76120c5e9..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/SiteRelativeLink.cs +++ /dev/null @@ -1,17 +0,0 @@ -using PnP.Core.Services; - -namespace PnP.Core.Transformation.Services.Core.Tokens -{ - /// - /// Resolves the server relative URL of a link targeting the current target site collection - /// - internal class SiteRelativeLinkToken : ITokenDefinition - { - public string Name { get => "SiteRelativeLink"; } - - public string GetValue(PnPContext context, string argument) - { - return $"{context.Web.ServerRelativeUrl}/{argument}"; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/TargetListIdByTitleToken.cs b/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/TargetListIdByTitleToken.cs deleted file mode 100644 index dc7bd5f178..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/Tokens/TargetListIdByTitleToken.cs +++ /dev/null @@ -1,23 +0,0 @@ -using PnP.Core.Services; - -namespace PnP.Core.Transformation.Services.Core.Tokens -{ - /// - /// Resolves the ID of a list in the target context based on the list title - /// - internal class TargetListIdByTitleToken : ITokenDefinition - { - public string Name { get => "TargetListIdByTitle"; } - - public string GetValue(PnPContext context, string argument) - { - var targetList = context.Web.Lists.GetByTitle(argument, l => l.Id); - if (targetList != null) - { - return targetList.Id.ToString(); - } - - return null; - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationExecutorExtensions.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TransformationExecutorExtensions.cs deleted file mode 100644 index 9dd7efa73e..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationExecutorExtensions.cs +++ /dev/null @@ -1,100 +0,0 @@ -using PnP.Core.Services; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Extensions for - /// - public static class TransformationExecutorExtensions - { - /// - /// Starts a process and waits for its completion - /// - /// The process to start - /// The source provider to use - /// The target PnP context - /// The cancellation token, if any - /// - public static async Task StartAndWaitProcessAsync( - this ITransformationProcess process, - ISourceProvider sourceProvider, - PnPContext targetContext, - CancellationToken token = default) - { - if (process == null) throw new ArgumentNullException(nameof(process)); - if (sourceProvider == null) throw new ArgumentNullException(nameof(sourceProvider)); - if (targetContext == null) throw new ArgumentNullException(nameof(targetContext)); - - // Start the process - await process.StartProcessAsync(sourceProvider, targetContext, token).ConfigureAwait(false); - - return await process.WaitProcessAsync(token).ConfigureAwait(false); - } - - /// - /// Wait for the completion of a process - /// - /// The process to start - /// The cancellation token, if any - /// The status of the process - public static async Task WaitProcessAsync( - this ITransformationProcess process, - CancellationToken token = default) - { - if (process == null) throw new ArgumentNullException(nameof(process)); - - // Intercept progress - var completionTask = new TaskCompletionSource(); - process.Progress = LocalProgress; - - // When token is cancelled, stop the process - token.Register(() => _ = process.StopProcessAsync(token)); - - // Since process could be already completed, we check immediately the status - await LocalProgress(await process.GetStatusAsync(token).ConfigureAwait(false)).ConfigureAwait(false); - - // Wait for completion of the task - return await completionTask.Task.ConfigureAwait(false); - - Task LocalProgress(TransformationProcessStatus status) - { - switch (status.State) - { - case TransformationExecutionState.Completed: - completionTask.TrySetResult(status); - break; - case TransformationExecutionState.Aborted: - completionTask.TrySetCanceled(token); - break; - } - return Task.CompletedTask; - } - } - - /// - /// Creates a new transformation process and waits for its completion - /// - /// The executor to use - /// The source provider - /// The target context - /// The cancellation token, if any - /// The status of the process - public static async Task TransformAsync( - this ITransformationExecutor transformationExecutor, - ISourceProvider sourceProvider, - PnPContext targetContext, - CancellationToken token = default) - { - if (transformationExecutor == null) throw new ArgumentNullException(nameof(transformationExecutor)); - - var process = await transformationExecutor.CreateTransformationProcessAsync(token).ConfigureAwait(false); - using (process as IDisposable) - { - return await process.StartAndWaitProcessAsync(sourceProvider, targetContext, token).ConfigureAwait(false); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessBase.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessBase.cs deleted file mode 100644 index 1c528070c3..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessBase.cs +++ /dev/null @@ -1,94 +0,0 @@ -using PnP.Core.Services; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Base abstract implementation of - /// - public abstract class TransformationProcessBase : ITransformationProcess - { - /// - /// The unique ID of a Transformation Process - /// - public abstract Guid Id { get; } - - /// - /// Starts the Transformation Process - /// - /// The source provider to use - /// The target PnP context - /// The cancellation token, if any - public abstract Task StartProcessAsync(ISourceProvider sourceProvider, PnPContext targetContext, CancellationToken token = default); - - /// - /// Stops the Transformation Process - /// - /// The cancellation token, if any - public abstract Task StopProcessAsync(CancellationToken token = default); - - /// - /// Allows monitoring the progress of the Transformation Process - /// - public virtual Func Progress { get; set; } - - /// - /// Allows monitoring the progress of each tasks - /// - public Func TasksProgress { get; set; } - - /// - /// Allows retrieving the status of the Transformation Process - /// - /// The cancellation token, if any - /// The status of the Transformation Process - public abstract Task GetStatusAsync(CancellationToken token = default); - - /// - /// Gets the status of a single task - /// - /// The id of the task - /// The cancellation token, if any - /// - public abstract Task GetTaskStatusAsync(Guid id, CancellationToken token = default); - - /// - /// Gets the list of the tasks using the criteria specified into the query - /// - /// Query to use for filtering - /// The cancellation token, if any - /// - public abstract IAsyncEnumerable GetTasksStatusAsync(TasksStatusQuery query, CancellationToken token = default); - - /// - /// Raises the process progress, if a Progress function is defined - /// - /// The status to notify - protected virtual Task RaiseProgressAsync(TransformationProcessStatus status) - { - if (Progress != null) - { - return Progress(status); - } - - return Task.CompletedTask; - } - - /// - /// Raises the task progress, if a TaskProgress function is defined - /// - /// The status to notify - protected virtual Task RaiseTasksProgressAsync(TransformationProcessTaskStatus status) - { - if (TasksProgress != null) - { - return TasksProgress(status); - } - - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessStatus.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessStatus.cs deleted file mode 100644 index d3d354fc14..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessStatus.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines the status of a transformation execution process - /// - public class TransformationProcessStatus - { - /// - /// Creates a new instance - /// - /// The process id - /// The status of the process - public TransformationProcessStatus(Guid processId, TransformationExecutionState state) - { - ProcessId = processId; - State = state; - } - - /// - /// The ID of the process - /// - public Guid ProcessId { get; } - - /// - /// Gets the status of the process - /// - public TransformationExecutionState State { get; } - } - - /// - /// List of process status - /// - public enum TransformationExecutionState - { - /// - /// Process is in pending state - /// - Pending, - - /// - /// Process is running - /// - Running, - - /// - /// Process has been aborted - /// - Aborted, - - /// - /// Process is completed - /// - Completed, - - /// - /// Process could not process any tasks - /// - Faulted - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessTaskStatus.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessTaskStatus.cs deleted file mode 100644 index b7d755ae8a..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationProcessTaskStatus.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// Defines the status of a single task of a process - /// - public class TransformationProcessTaskStatus - { - /// - /// Default constructor - /// - /// The process id - /// The task id - /// The creation date time for the task - /// The start date time for the task - /// The end date time for the task - /// The state of the task - /// The exception message, if any - /// The exception stack trace, if any - public TransformationProcessTaskStatus(Guid processId, Guid id, DateTimeOffset creationDate, DateTimeOffset? startDate, DateTimeOffset? endDate, TransformationTaskExecutionState state, string exceptionMessage, string exceptionStackTrace) - { - ProcessId = processId; - Id = id; - CreationDate = creationDate; - StartDate = startDate; - EndDate = endDate; - State = state; - ExceptionMessage = exceptionMessage; - ExceptionStackTrace = exceptionStackTrace; - } - - private TransformationProcessTaskStatus() - { - - } - - /// - /// Creates an instance for pending state - /// - /// The process id - /// The task id - /// The creation date time for the task - /// A new instance of - public static TransformationProcessTaskStatus CreatePending(Guid processId, Guid id, DateTimeOffset creationDate) - { - return new TransformationProcessTaskStatus - { - ProcessId = processId, - Id = id, - CreationDate = creationDate, - State = TransformationTaskExecutionState.Pending - }; - } - - /// - /// Creates an instance specifying the state - /// - /// The process id - /// The task id - /// The creation date time for the task - /// The start date time for the task - /// The end date time for the task - /// The state of the task - /// A new instance of - public static TransformationProcessTaskStatus CreateNormal(Guid processId, Guid id, DateTimeOffset creationDate, DateTimeOffset? startDate, DateTimeOffset? endDate, TransformationTaskExecutionState state) - { - return new TransformationProcessTaskStatus - { - ProcessId = processId, - Id = id, - CreationDate = creationDate, - StartDate = startDate, - EndDate = endDate, - State = state, - }; - } - - /// - /// Creates an instance for faulted state - /// - /// The process id - /// The task id - /// The creation date time for the task - /// The start date time for the task - /// The end date time for the task - /// The exception message, if any - /// The exception stack trace, if any - /// A new instance of - public static TransformationProcessTaskStatus CreateFaulted(Guid processId, Guid id, DateTimeOffset creationDate, DateTimeOffset? startDate, DateTimeOffset? endDate, string exceptionMessage, string exceptionStackTrace) - { - return new TransformationProcessTaskStatus - { - ProcessId = processId, - Id = id, - CreationDate = creationDate, - StartDate = startDate, - EndDate = endDate, - ExceptionMessage = exceptionMessage, - ExceptionStackTrace = exceptionStackTrace, - State = TransformationTaskExecutionState.Faulted - }; - } - - /// - /// Creates an instance for faulted state - /// - /// The process id - /// The task id - /// The creation date time for the task - /// The start date time for the task - /// The end date time for the task - /// The exception - /// A new instance of - public static TransformationProcessTaskStatus CreateFaulted(Guid processId, Guid id, DateTimeOffset creationDate, DateTimeOffset? startDate, DateTimeOffset? endDate, Exception exception) - { - if (exception == null) throw new ArgumentNullException(nameof(exception)); - - return new TransformationProcessTaskStatus - { - ProcessId = processId, - Id = id, - CreationDate = creationDate, - StartDate = startDate, - EndDate = endDate, - ExceptionMessage = exception.Message, - ExceptionStackTrace = exception.StackTrace, - State = TransformationTaskExecutionState.Faulted - }; - } - - /// - /// Gets the id of the process - /// - public Guid ProcessId { get; private set; } - - /// - /// Gets the id of the task - /// - public Guid Id { get; private set; } - - /// - /// Gets the creation date of the task - /// - public DateTimeOffset CreationDate { get; private set; } - - /// - /// Gets the date when task state became running - /// - public DateTimeOffset? StartDate { get; private set; } - - /// - /// Gets the date when task state became completed or faulted - /// - public DateTimeOffset? EndDate { get; private set; } - - /// - /// Gets the exception stack trace in case the state is Faulted - /// - public string ExceptionMessage { get; private set; } - - /// - /// Gets the exception stack trace in case the state is Faulted - /// - public string ExceptionStackTrace { get; private set; } - - /// - /// Gets the state of the task - /// - public TransformationTaskExecutionState State { get; private set; } - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationTaskExecutionState.cs b/src/sdk/PnP.Core.Transformation/Services/Core/TransformationTaskExecutionState.cs deleted file mode 100644 index d64a868457..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/Core/TransformationTaskExecutionState.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace PnP.Core.Transformation.Services.Core -{ - /// - /// List of process status - /// - public enum TransformationTaskExecutionState - { - /// - /// Process is in pending state - /// - Pending, - - /// - /// Process is running - /// - Running, - - /// - /// Process has been aborted - /// - Aborted, - - /// - /// Process is completed - /// - Completed, - - /// - /// Process could not process any tasks - /// - Faulted - } -} \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderInput.cs deleted file mode 100644 index cfc069722c..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderInput.cs +++ /dev/null @@ -1,25 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the basic input for a mapping provider - /// - public abstract class BaseMappingProviderInput - { - /// - /// Creates an instance with the context specified - /// - /// - public BaseMappingProviderInput(PageTransformationContext context) - { - Context = context ?? throw new ArgumentNullException(nameof(context)); - } - - /// - /// Provides information about the current transformation - /// - public PageTransformationContext Context { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderOutput.cs deleted file mode 100644 index 30ef232337..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/BaseMappingProviderOutput.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the basic output for a mapping provider - /// - public abstract class BaseMappingProviderOutput - { - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderInput.cs deleted file mode 100644 index 16e76e17a5..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderInput.cs +++ /dev/null @@ -1,32 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a HTML mapping provider - /// - public class HtmlMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instance for the specified context - /// - /// - /// - /// - public HtmlMappingProviderInput(PageTransformationContext context, string htmlContent) : base(context) - { - HtmlContent = htmlContent ?? throw new ArgumentNullException(nameof(htmlContent)); - } - - /// - /// The source HTML content to map - /// - public string HtmlContent { get; } - - /// - /// Gets or sets if placeholders should be used for img and iframe - /// - public bool UsePlaceHolders { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderOutput.cs deleted file mode 100644 index bf5f763047..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/HtmlMappingProviderOutput.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a HTML mapping provider - /// - public class HtmlMappingProviderOutput : BaseMappingProviderOutput - { - /// - /// Creates an instance for the content - /// - /// - public HtmlMappingProviderOutput(string htmlContent) - { - HtmlContent = htmlContent ?? throw new ArgumentNullException(nameof(htmlContent)); - } - - /// - /// Defines the HTML content resulting from the mapping - /// - public string HtmlContent { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IHtmlMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IHtmlMappingProvider.cs deleted file mode 100644 index 67e77832bb..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IHtmlMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a HTML mapping provider - /// - public interface IHtmlMappingProvider - { - /// - /// Maps HTML content from classic to modern - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapHtmlAsync(HtmlMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMappingProvider.cs deleted file mode 100644 index 4c9b76ac9d..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a generic mapping provider - /// - public interface IMappingProvider - { - /// - /// Maps an item from the source platform to the target platform - /// - /// The input for the mapping - /// The cancellation token, if any - /// The output of the mapping - Task MapAsync(MappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMetadataMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMetadataMappingProvider.cs deleted file mode 100644 index e74984dc00..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IMetadataMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a Metadata mapping provider - /// - public interface IMetadataMappingProvider - { - /// - /// Maps a Metadata Field Value from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapMetadataFieldAsync(MetadataMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IPageLayoutMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IPageLayoutMappingProvider.cs deleted file mode 100644 index b1326cab6d..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IPageLayoutMappingProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a Page Layout mapping provider - /// - public interface IPageLayoutMappingProvider - { - /// - /// Maps a classic Page Layout into a modern Page Layout - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapPageLayoutAsync(PageLayoutMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/ITaxonomyMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/ITaxonomyMappingProvider.cs deleted file mode 100644 index 3444817dfb..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/ITaxonomyMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a Taxonomy mapping provider - /// - public interface ITaxonomyMappingProvider - { - /// - /// Maps a Taxonomy Term from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapTermAsync(TaxonomyMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUrlMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUrlMappingProvider.cs deleted file mode 100644 index 7e44bbe03e..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUrlMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a URL mapping provider - /// - public interface IUrlMappingProvider - { - /// - /// Maps a URL from classic to modern - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapUrlAsync(UrlMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUserMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUserMappingProvider.cs deleted file mode 100644 index 7edd9447fd..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IUserMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a User mapping provider - /// - public interface IUserMappingProvider - { - /// - /// Maps a user UPN from the source platform to the target platform - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapUserAsync(UserMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IWebPartMappingProvider.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IWebPartMappingProvider.cs deleted file mode 100644 index 3803ae8c69..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/IWebPartMappingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Provides the basic interface for a Web Part mapping provider - /// - public interface IWebPartMappingProvider - { - /// - /// Maps a classic Web Part into a modern Web Part - /// - /// The input for the mapping activity - /// The cancellation token, if any - /// The output of the mapping activity - Task MapWebPartAsync(WebPartMappingProviderInput input, CancellationToken token = default); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderInput.cs deleted file mode 100644 index a5a7faab3a..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderInput.cs +++ /dev/null @@ -1,19 +0,0 @@ -using PnP.Core.Transformation.Services.Core; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a mapping provider - /// - public class MappingProviderInput : BaseMappingProviderInput - { - /// - /// Public constructor - /// - /// - public MappingProviderInput(PageTransformationContext context): base(context) - { - - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderOutput.cs deleted file mode 100644 index 51c8aefee5..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MappingProviderOutput.cs +++ /dev/null @@ -1,31 +0,0 @@ -using PnP.Core.Transformation.Model; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a mapping provider - /// - public class MappingProviderOutput: BaseMappingProviderOutput - { - /// - /// The modern page to create as the result of the transformation - /// - public Page TargetPage { get; } = new Page(); - - /// - /// Defines the metadata fields of the target page - /// - public Dictionary Metadata { get; } = new Dictionary(); - - /// - /// Defines the list item permissions for the target page - /// - public ListItemPermission Permissions { get; set; } - - /// - /// Defines a dictionary of custom properties retrieved during source content processing and useful for telemetry - /// - public Dictionary TelemetryProperties { get; set; } = new Dictionary(); - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderInput.cs deleted file mode 100644 index 2e42b86543..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderInput.cs +++ /dev/null @@ -1,34 +0,0 @@ -using PnP.Core.Transformation.Services.Core; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a metadata mapping provider - /// - public class MetadataMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instances for the specified context - /// - /// - public MetadataMappingProviderInput(PageTransformationContext context) : base(context) - { - } - - /// - /// Defines the internal name of the source field - /// - public string SourceFieldName { get; set; } - - /// - /// Defines the internal name of the target field - /// - public string TargetFieldName { get; set; } - - /// - /// Provides the value of the source field - /// - public object SourceValue { get; set; } - - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderOutput.cs deleted file mode 100644 index 5aa3f6f676..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/MetadataMappingProviderOutput.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a metadata mapping provider - /// - public class MetadataMappingProviderOutput: BaseMappingProviderOutput - { - /// - /// Provides the value resulting from the mapping - /// - public object Value { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderInput.cs deleted file mode 100644 index 64f127d5a1..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderInput.cs +++ /dev/null @@ -1,23 +0,0 @@ -using PnP.Core.Transformation.Services.Core; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a page layout mapping provider - /// - public class PageLayoutMappingProviderInput : MappingProviderInput - { - /// - /// Creates an instances for the specified context - /// - /// - public PageLayoutMappingProviderInput(PageTransformationContext context) : base(context) - { - } - - /// - /// Defines the source Page Layout to map - /// - public string PageLayout { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderOutput.cs deleted file mode 100644 index 4f2c5a9b4f..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/PageLayoutMappingProviderOutput.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a page layout mapping provider - /// - public class PageLayoutMappingProviderOutput: BaseMappingProviderOutput - { - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderInput.cs deleted file mode 100644 index b991f1f89a..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderInput.cs +++ /dev/null @@ -1,26 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a taxonomy mapping provider - /// - public class TaxonomyMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instance for the context - /// - /// - /// - public TaxonomyMappingProviderInput(PageTransformationContext context, string termId) : base(context) - { - TermId = termId ?? throw new ArgumentNullException(nameof(termId)); - } - - /// - /// Defines the source Term ID to map - /// - public string TermId { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderOutput.cs deleted file mode 100644 index 91645912c8..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/TaxonomyMappingProviderOutput.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a taxonomy mapping provider - /// - public class TaxonomyMappingProviderOutput : BaseMappingProviderOutput - { - /// - /// Defines the target Term ID from the mapping - /// - public string TermId { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderInput.cs deleted file mode 100644 index d6ff322f4e..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderInput.cs +++ /dev/null @@ -1,26 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a URL mapping provider - /// - public class UrlMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instance for the specified context - /// - /// - /// - public UrlMappingProviderInput(PageTransformationContext context, string text) : base(context) - { - Text = text ?? throw new ArgumentNullException(nameof(text)); - } - - /// - /// Defines the text to use for URL mapping - /// - public string Text { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderOutput.cs deleted file mode 100644 index c7cb3ee29a..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UrlMappingProviderOutput.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a URL mapping provider - /// - public class UrlMappingProviderOutput : BaseMappingProviderOutput - { - /// - /// Creates an instance with the specified text - /// - /// The text with mapped URLs - public UrlMappingProviderOutput(string text) - { - Text = text ?? throw new ArgumentNullException(nameof(text)); - } - - /// - /// Defines the text after the URL mapping - /// - public string Text { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderInput.cs deleted file mode 100644 index f3471368b8..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderInput.cs +++ /dev/null @@ -1,26 +0,0 @@ -using PnP.Core.Transformation.Services.Core; -using System; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a URL mapping provider - /// - public class UserMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instance for user - /// - /// - /// - public UserMappingProviderInput(PageTransformationContext context, string userPrincipalName) : base(context) - { - UserPrincipalName = userPrincipalName ?? throw new ArgumentNullException(nameof(userPrincipalName)); - } - - /// - /// Defines the source UPN to map - /// - public string UserPrincipalName { get; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderOutput.cs deleted file mode 100644 index 1f5574eccc..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/UserMappingProviderOutput.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a user mapping provider - /// - public class UserMappingProviderOutput : BaseMappingProviderOutput - { - /// - /// Defines the target UPN from the mapping - /// - public string UserPrincipalName { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderInput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderInput.cs deleted file mode 100644 index 3b39131c28..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderInput.cs +++ /dev/null @@ -1,45 +0,0 @@ -using PnP.Core.Transformation.Model; -using PnP.Core.Transformation.Services.Core; -using System.Collections.Generic; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the input for a Web Part mapping provider - /// - public class WebPartMappingProviderInput : BaseMappingProviderInput - { - /// - /// Creates an instance for the specified context and web part - /// - /// The transformation context - public WebPartMappingProviderInput(PageTransformationContext context) : base(context) - { - } - - /// - /// Defines the type of the source component - /// - public string SourceComponentType { get; set; } - - /// - /// Property bag of the source component - /// - public Dictionary SourceProperties { get; set; } - - /// - /// The actual TXT/XML/JSON content of the source component - /// - public string SourceComponentRawContent { get; set; } - - /// - /// The web part to transform - /// - public WebPartEntity WebPart { get; set; } - - /// - /// Defines whether the transformation is cross-site - /// - public bool IsCrossSiteTransformation { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderOutput.cs b/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderOutput.cs deleted file mode 100644 index 456884df1a..0000000000 --- a/src/sdk/PnP.Core.Transformation/Services/MappingProviders/WebPartMappingProviderOutput.cs +++ /dev/null @@ -1,15 +0,0 @@ -using PnP.Core.Transformation.Model; - -namespace PnP.Core.Transformation.Services.MappingProviders -{ - /// - /// Defines the output for a Web Part mapping provider - /// - public class WebPartMappingProviderOutput : BaseMappingProviderOutput - { - /// - /// The Web Part Entity resulting after the mapping - /// - public WebPartEntity WebPart { get; set; } - } -} diff --git a/src/sdk/PnP.Core.Transformation/TransformationResources.Designer.cs b/src/sdk/PnP.Core.Transformation/TransformationResources.Designer.cs deleted file mode 100644 index ce63325f4d..0000000000 --- a/src/sdk/PnP.Core.Transformation/TransformationResources.Designer.cs +++ /dev/null @@ -1,324 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PnP.Core.Transformation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class TransformationResources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal TransformationResources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PnP.Core.Transformation.TransformationResources", typeof(TransformationResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Check if the transformed page is the web's home page. - /// - internal static string Debug_TransformCheckIfPageIsHomePage { - get { - return ResourceManager.GetString("Debug_TransformCheckIfPageIsHomePage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page transformation for targeting non-modern sites is currently not supported. - /// - internal static string Error_CrossSiteTransferTargetsNonModernSite { - get { - return ResourceManager.GetString("Error_CrossSiteTransferTargetsNonModernSite", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid content for parameter 'argument' of AssetPersistenceProviderToken. - /// - internal static string Error_InvalidArgumentForAssetPersistenceProviderToken { - get { - return ResourceManager.GetString("Error_InvalidArgumentForAssetPersistenceProviderToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The provided asset stream is not valid for persistence. - /// - internal static string Error_InvalidAssetStreamToPersist { - get { - return ResourceManager.GetString("Error_InvalidAssetStreamToPersist", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The generated modern page does not have the expected URI.. - /// - internal static string Error_InvalidTargetPageUri { - get { - return ResourceManager.GetString("Error_InvalidTargetPageUri", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Missing UserMappingProvider in current services configuration. - /// - internal static string Error_MissingUserMappingProvider { - get { - return ResourceManager.GetString("Error_MissingUserMappingProvider", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The target page '{0}' already exists and overwrite is not configured. - /// - internal static string Error_PageNotOverwriteIfExists { - get { - return ResourceManager.GetString("Error_PageNotOverwriteIfExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Added '{0}' Client Side Web Part to target page. - /// - internal static string Info_AddedClientSideWebPartToPage { - get { - return ResourceManager.GetString("Info_AddedClientSideWebPartToPage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using add-in web part '{0}'. - /// - internal static string Info_ContentUsingAddinWebPart { - get { - return ResourceManager.GetString("Info_ContentUsingAddinWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using '{0}' modern web part. - /// - internal static string Info_ContentUsingModernWebPart { - get { - return ResourceManager.GetString("Info_ContentUsingModernWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Created new Text control. - /// - internal static string Info_CreatedTextControl { - get { - return ResourceManager.GetString("Info_CreatedTextControl", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mapping field '{0}'. - /// - internal static string Info_MappingRegularField { - get { - return ResourceManager.GetString("Info_MappingRegularField", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mapping taxonomy field '{0}'. - /// - internal static string Info_MappingTaxonomyField { - get { - return ResourceManager.GetString("Info_MappingTaxonomyField", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The page '{0}' already exists in target location. - /// - internal static string Info_PageAlreadyExistsInTargetLocation { - get { - return ResourceManager.GetString("Info_PageAlreadyExistsInTargetLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Published by the PnP Transformation Framework. - /// - internal static string Info_PublishMessage { - get { - return ResourceManager.GetString("Info_PublishMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Running transformation task {0} on data source item {1}. - /// - internal static string Info_RunningTransformationTask { - get { - return ResourceManager.GetString("Info_RunningTransformationTask", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Image Url:. - /// - internal static string Info_TransformArticleHeaderImageUrl { - get { - return ResourceManager.GetString("Info_TransformArticleHeaderImageUrl", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page Header Set to Custom. Using page header settings:. - /// - internal static string Info_TransformArticleSetHeaderToCustom { - get { - return ResourceManager.GetString("Info_TransformArticleSetHeaderToCustom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page Header Set to Default. Using page header default settings. - /// - internal static string Info_TransformArticleSetHeaderToDefault { - get { - return ResourceManager.GetString("Info_TransformArticleSetHeaderToDefault", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Page Header Set to None. Removing the page header. - /// - internal static string Info_TransformArticleSetHeaderToNone { - get { - return ResourceManager.GetString("Info_TransformArticleSetHeaderToNone", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Setting page author in page header. - /// - internal static string Info_TransformArticleSetHeaderToNoneWithAuthor { - get { - return ResourceManager.GetString("Info_TransformArticleSetHeaderToNoneWithAuthor", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Transforming source page as Article page. - /// - internal static string Info_TransformSourcePageAsArticlePage { - get { - return ResourceManager.GetString("Info_TransformSourcePageAsArticlePage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using a stock homepage layout as the new homepage - not transforming page. - /// - internal static string Info_TransformSourcePageHomePageUsingStock { - get { - return ResourceManager.GetString("Info_TransformSourcePageHomePageUsingStock", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using 'custom' modern web part. - /// - internal static string Info_UsingCustomModernWebPart { - get { - return ResourceManager.GetString("Info_UsingCustomModernWebPart", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Modern web part not found. - /// - internal static string Warning_ContentWarnModernNotFound { - get { - return ResourceManager.GetString("Warning_ContentWarnModernNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There was a non critical issue during page update/publish. Returned error message: {0}. - /// - internal static string Warning_NonCriticalErrorDuringPublish { - get { - return ResourceManager.GetString("Warning_NonCriticalErrorDuringPublish", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The page author header could not be set. Author '{0}' could not be resolved. - /// - internal static string Warning_PageHeaderAuthorNotSet { - get { - return ResourceManager.GetString("Warning_PageHeaderAuthorNotSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The page author header could not be set. Error: '{0}'. - /// - internal static string Warning_PageHeaderAuthorNotSetGenericError { - get { - return ResourceManager.GetString("Warning_PageHeaderAuthorNotSetGenericError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Posting a page as news requires the page to be published. Turning on the PublishCreatedPage flag. - /// - internal static string Warning_PostingAPageAsNewsRequiresPagePublishing { - get { - return ResourceManager.GetString("Warning_PostingAPageAsNewsRequiresPagePublishing", resourceCulture); - } - } - } -} diff --git a/src/sdk/PnP.Core.Transformation/TransformationResources.resx b/src/sdk/PnP.Core.Transformation/TransformationResources.resx deleted file mode 100644 index ad826cd160..0000000000 --- a/src/sdk/PnP.Core.Transformation/TransformationResources.resx +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Check if the transformed page is the web's home page - - - Page transformation for targeting non-modern sites is currently not supported - - - Invalid content for parameter 'argument' of AssetPersistenceProviderToken - - - The provided asset stream is not valid for persistence - - - The generated modern page does not have the expected URI. - - - Missing UserMappingProvider in current services configuration - - - The target page '{0}' already exists and overwrite is not configured - - - Added '{0}' Client Side Web Part to target page - - - Using add-in web part '{0}' - - - Using '{0}' modern web part - - - Created new Text control - - - Mapping field '{0}' - - - Mapping taxonomy field '{0}' - - - The page '{0}' already exists in target location - - - Published by the PnP Transformation Framework - - - Running transformation task {0} on data source item {1} - - - Image Url: - - - Page Header Set to Custom. Using page header settings: - - - Page Header Set to Default. Using page header default settings - - - Page Header Set to None. Removing the page header - - - Setting page author in page header - - - Transforming source page as Article page - - - Using a stock homepage layout as the new homepage - not transforming page - - - Using 'custom' modern web part - - - Modern web part not found - - - There was a non critical issue during page update/publish. Returned error message: {0} - - - The page author header could not be set. Author '{0}' could not be resolved - - - The page author header could not be set. Error: '{0}' - - - Posting a page as news requires the page to be published. Turning on the PublishCreatedPage flag - - \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/extensibility.md b/src/sdk/PnP.Core.Transformation/extensibility.md deleted file mode 100644 index 336cd55bb0..0000000000 --- a/src/sdk/PnP.Core.Transformation/extensibility.md +++ /dev/null @@ -1,20 +0,0 @@ -# Extensibility of PnP.Core.Transformation -This document provides information about how to extend and customize the default behavior of the PnP Transformation Framework. - -... - -## Transformation customization -The configuration - -## Custom batch processing -Since a site can - -## Extensibility -The extension method **AddPnPSharePointTransformation** configures the base engine using SharePoint as the data source. You can customize each part of the engine, providing a custom implementation of the following interfaces: -- **ITargetPageUriResolver**: interface used to resolve the SharePoint target uri. Call *WithTargetPageUriResolver* on *IPnPTransformationBuilder* instance in order to customize; - -- **ITransformationStateManager**: interface to handle the state of a transformation process managed by an implementation of *ITransformationExecutor*. Call *WithTransformationStateManager* on *IPnPTransformationBuilder* instance in order to customize; - -- **ITransformationDistiller**: interface for a service that defines a list of pages to transform comparing source and target. Call *WithTransformationDistiller* on *IPnPTransformationBuilder* instance in order to customize; - -- **IPageTransformator**: . Call *WithPageTransformator* on *IPnPTransformationBuilder* instance in order to customize; \ No newline at end of file diff --git a/src/sdk/PnP.Core.Transformation/getting-started.md b/src/sdk/PnP.Core.Transformation/getting-started.md deleted file mode 100644 index f7b3eff1ab..0000000000 --- a/src/sdk/PnP.Core.Transformation/getting-started.md +++ /dev/null @@ -1,54 +0,0 @@ -# Getting Started with PnP.Core.Transformation -This document provides the basic information about how to get started developing solutions that rely on the PnP Transformation Framework, to transform SharePoint Online classic pages into SharePoint Online modern pages. - -## Transforming a single page -In order to setup the PnP Transformation Framework you have to configure it using .NET dependency injection. Since the library is built on top of the **PnP Core SDK** you have also to configure it. - -> You can find further details about the PnP Core SDK and about how to setup it up in your solution by reading the official documentation available here: [Getting started with the PnP Core SDK](https://pnp.github.io/pnpcore/using-the-sdk/readme.html). - -Here you can see a code excerpt about how to configure dependency injection to use the PnP Core SDK and the PnP Transformation Framework. The source code refers to a couple of sites named "source" and "target" that should be configured in the settings of your application, accordingly to the configuration syntax of the PnP Core SDK. - -```c# -services.AddPnPCoreAuthentication(); -services.AddPnPCore(); - -services.AddPnPSharePointTransformation(); -``` -The PnP Transformation Framework supports the transformation of a single page through an implementation of type **IPageTransformator**. You can easily transform a SharePoint Online classic page using the following syntax: - -```c# -// Get required dependencies -var pnpContextFactory = provider.GetRequiredService(); -var pageTransformator = provider.GetRequiredService(); - -// Create SharePoint contexts for source and target -var sourceContext = await pnpContextFactory.CreateAsync("source"); -var targetContext = await pnpContextFactory.CreateAsync("target"); -var sourceUri = new Uri("https://tenant.sharepoint.com/sites/SourceSite/item.aspx"); - -// Transform the source page uri into the target context -Uri result = await pageTransformator.TransformSharePointAsync(sourceContext, targetContext, sourceUri); -``` - -When the transformation is completed, the function returns the URL of the transformed page. - -## Batch transformation -If you want to transform more than one page, you can use the IPageTransformator implementation multiple times. -However, the Transformation Framework provides the capability to enumerate all the source pages of a site and transform each of them into a modern page. -Using an implementation of type **ITransformationExecutor** and the method **TransformSharePointAsync** you can easily transform a whole SharePoint site from classic to modern. - -```c# -// Get required dependency -var transformationExecutor = provider.GetRequiredService(); - -// Transform the entire site -TransformationProcessStatus result = await transformationExecutor.TransformSharePointAsync(sourceContext, targetContext); -``` - -Out of the box and by default, with just two lines of code, you can transform a whole site. -Of course, you can specify additional settings to customize this default behavior. - -The executor uses a default implementation, which works in memory and returns a final status as the result of the transformation process. Optionally you can provide a **CancellationToken** in order to cancel the long running operation. - -## Transformation Framework Extensibility -If you like, you can heavily extend and customize the out of the box behavior of the Transformation Framework. You can read the document [Extensibility of PnP.Core.Transformation](./extensibility.md) to understand how to do that. diff --git a/src/sdk/PnP.Core.Transformation/nugeticon.png b/src/sdk/PnP.Core.Transformation/nugeticon.png deleted file mode 100644 index 14962c3be3..0000000000 Binary files a/src/sdk/PnP.Core.Transformation/nugeticon.png and /dev/null differ diff --git a/src/sdk/PnP.Core.Transformation/readme.md b/src/sdk/PnP.Core.Transformation/readme.md deleted file mode 100644 index 96b12a5239..0000000000 --- a/src/sdk/PnP.Core.Transformation/readme.md +++ /dev/null @@ -1,36 +0,0 @@ -# PnP.Core.Transformation -The PnP.Core.Transformation library (aka Transformation Framework) is an add-on, built on top of the PnP.Core library, to transform content-based solutions into Microsoft SharePoint Online "modern" solutions. -It is the evolution of the PnP Modernization Framework that we've built and maintained in the last few years as a PnP Community project. -The basic idea of the Transformation Framework is to support customers and developers transforming content from whatever technology (including Microsoft SharePoint on-premises and Online) to modern pages in SharePoint Online. The architecture of the framework is really open and allows to plug into it any data source capable of providing "content" that can be transformed into the content of a modern page. - -## General overview -At the very basis of the Transformation Framework architecture there is the idea of **PageTransformator**, which is an object capable of transforming a data source into a SharePoint Online modern page. -You can use a PageTransformator either in code or via PowerShell (PnP PowerShell), to trasform a content page into a modern page. - -The data source can be any other kind of content, as long as it is somehow transformable into a SharePoint Online modern page. -Out of the box, the Transformation Framework provides the basic infrastructure to transform classic pages of SharePoint on-premises (2013, 2016, 2019) and SharePoint Online into modern pages of SharePoint Online. -However, anyone can implement a custom data source, which is nothing more than a set of .NET types implementing some interfaces and providing some custom logic, in order to support transforming content from external platforms to modern pages in SharePoint Online. -For example, there could be a data source for Word Press, or a data source for other portal solutions. - -With a PageTransformator you can transform a single content into a modern page. -However, we've seen that most of the customers are using this technology to transform a whole site or portal into a modern site in SharePoint Online. -Moreover, there are customers and partners building solutions for transforming content and feeding modern sites in SharePoint Online. - -That's why we introduced the concept of **TransformationExecutor**, which is a more complex object capable of tranforming a set of content items from a data source into a set of SharePoint Online modern pages. Internally a TransformationExecutor relies on a **TransformationDistiller**, which creates the list of content items to transform. The goal of a TransformationDistiller is to determine the list of content items to process and transform, and it can be used for example to create delta-transformations to synchornize a target site with a data source. A TransformationExecutor also relies on **TransformationStateManager** object, which is able to keep track of the state of a complex transformation process. At the very end, a TransformationExecutor will use a set of PageTransformator instances to transform the set of pages in target. - -This architecture allows to create highly scalable and decoupled solutions that can either process in-memory the transformation of content items, or can rely on a back-end asynchronous infrastructure (for example hosted on Microsoft Azure) to process high volume of transformation tasks. - -## Transformation process -Internally, the transformation process relies on a a set of mapping providers. For every data source there is a basic mapping provider object, which implements the **IMappingProvider** interface, and there could be specialized mapping providers for the main contents that need to be transformed. Natively, you can find the following mapping providers: -- Html Mapping Provider: to map HTML content from the data source to SharePoint Online. -- Metadata Mapping Provider: to map metadata fields from the data source to SharePoint Online. -- Page Layout Mapping Provider: to map page layouts from the data source to SharePoint Online. -- Taxonomy Mapping Provider: to map taxonomy terms from the data source to SharePoint Online. -- Url Mapping Provider: to map URLs from the data source to SharePoint Online. -- User Mapping Provider: to map user accounts from the data source to SharePoint Online. -- Web Part Mapping Provider: to map content controls/components from the data source into web parts of SharePoint Online. - -The content transformation process goes through the source content and applies all the mappings to generate the transformed modern page. - -## Getting started -In order to start using the Transformation Framework, you can read the ["Getting started"](./getting-started.md) guide. \ No newline at end of file diff --git a/src/sdk/PnP.Core.sln b/src/sdk/PnP.Core.sln index b8c8368c8e..0a115830d2 100644 --- a/src/sdk/PnP.Core.sln +++ b/src/sdk/PnP.Core.sln @@ -18,14 +18,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Auth", "PnP.Core.A EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Auth.Test", "PnP.Core.Auth.Test\PnP.Core.Auth.Test.csproj", "{D11B7FF5-DE02-4DAA-A634-62B9D6C684F8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Transformation", "PnP.Core.Transformation\PnP.Core.Transformation.csproj", "{F9C46372-AD8A-4D71-BA10-839DCF5B94C9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Transformation.Test", "PnP.Core.Transformation.Test\PnP.Core.Transformation.Test.csproj", "{5F617F14-4968-4E02-8481-4A97452108C9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Transformation.SharePoint", "PnP.Core.Transformation.SharePoint\PnP.Core.Transformation.SharePoint.csproj", "{51098E4D-FDBE-47F5-BA81-7B39982D1E1D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Transformation.SharePoint.Test", "PnP.Core.Transformation.SharePoint.Test\PnP.Core.Transformation.SharePoint.Test.csproj", "{3B14330E-F306-421F-A971-E166EFC4A292}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Admin", "PnP.Core.Admin\PnP.Core.Admin.csproj", "{EB0CA4A2-BB67-44B0-BB1E-8D7DAFCB5BC2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnP.Core.Admin.Test", "PnP.Core.Admin.Test\PnP.Core.Admin.Test.csproj", "{8806BABA-C6CC-495C-AA33-B9A56E213572}" @@ -56,22 +48,6 @@ Global {D11B7FF5-DE02-4DAA-A634-62B9D6C684F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D11B7FF5-DE02-4DAA-A634-62B9D6C684F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D11B7FF5-DE02-4DAA-A634-62B9D6C684F8}.Release|Any CPU.Build.0 = Release|Any CPU - {F9C46372-AD8A-4D71-BA10-839DCF5B94C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9C46372-AD8A-4D71-BA10-839DCF5B94C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9C46372-AD8A-4D71-BA10-839DCF5B94C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9C46372-AD8A-4D71-BA10-839DCF5B94C9}.Release|Any CPU.Build.0 = Release|Any CPU - {5F617F14-4968-4E02-8481-4A97452108C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F617F14-4968-4E02-8481-4A97452108C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F617F14-4968-4E02-8481-4A97452108C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F617F14-4968-4E02-8481-4A97452108C9}.Release|Any CPU.Build.0 = Release|Any CPU - {51098E4D-FDBE-47F5-BA81-7B39982D1E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51098E4D-FDBE-47F5-BA81-7B39982D1E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51098E4D-FDBE-47F5-BA81-7B39982D1E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51098E4D-FDBE-47F5-BA81-7B39982D1E1D}.Release|Any CPU.Build.0 = Release|Any CPU - {3B14330E-F306-421F-A971-E166EFC4A292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B14330E-F306-421F-A971-E166EFC4A292}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B14330E-F306-421F-A971-E166EFC4A292}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B14330E-F306-421F-A971-E166EFC4A292}.Release|Any CPU.Build.0 = Release|Any CPU {EB0CA4A2-BB67-44B0-BB1E-8D7DAFCB5BC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EB0CA4A2-BB67-44B0-BB1E-8D7DAFCB5BC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EB0CA4A2-BB67-44B0-BB1E-8D7DAFCB5BC2}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/sdk/PnP.Core/AssemblyInfo.cs b/src/sdk/PnP.Core/AssemblyInfo.cs index 8d95962892..407b29950d 100644 --- a/src/sdk/PnP.Core/AssemblyInfo.cs +++ b/src/sdk/PnP.Core/AssemblyInfo.cs @@ -8,7 +8,5 @@ [assembly: InternalsVisibleTo("PnP.Core.Auth.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] [assembly: InternalsVisibleTo("PnP.Core.Admin.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] [assembly: InternalsVisibleTo("PnP.Core.Perf, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] -[assembly: InternalsVisibleTo("PnP.Core.Transformation, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] -[assembly: InternalsVisibleTo("PnP.Core.Transformation.SharePoint, PublicKey=00240000048000009400000006020000002400005253413100040000010001000dbc41bd44b97df5ebb933aeecf7a91a7bf9d2d10ce54c5daa3adc211ab557d179355b18ee4bc50e4e82151ee15cc4f4220e3bb6048b22bb93bd6193ed82a0b71c0cd56a527e1d28614988b1fdbdf6f40f850acc009fc26db1b37878e6d4d6da2463d6867ef7317c1cef37b5099c9138dcf21571bb6201b48d8814abdc33b8ad")] [assembly: NeutralResourcesLanguage("en")] \ No newline at end of file