Merge branch 'v14/dev' into contrib
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
|
||||
<!-- Package Validation -->
|
||||
<PropertyGroup>
|
||||
<GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile>
|
||||
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
|
||||
<EnablePackageValidation>true</EnablePackageValidation>
|
||||
<PackageValidationBaselineVersion>14.0.0</PackageValidationBaselineVersion>
|
||||
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
|
||||
|
||||
@@ -12,24 +12,24 @@
|
||||
</ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.7" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
@@ -47,7 +47,7 @@
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.3.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.3.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.62" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.65" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
<PackageVersion Include="MailKit" Version="4.7.1.1" />
|
||||
@@ -75,7 +75,7 @@
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.7.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.7.3" />
|
||||
</ItemGroup>
|
||||
<!-- Transitive pinned versions (only required because our direct dependencies have vulnerable versions of transitive dependencies) -->
|
||||
<ItemGroup>
|
||||
@@ -87,5 +87,7 @@
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="8.0.1" />
|
||||
<!-- Both Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc bring in a vulnerable version of System.Text.RegularExpressions -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.7.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,10 +5,6 @@ parameters:
|
||||
displayName: Run SQL Server Integration Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: sqlServerAcceptanceTests
|
||||
displayName: Run SQL Server Acceptance Tests
|
||||
type: boolean
|
||||
default: false
|
||||
- name: myGetDeploy
|
||||
displayName: Deploy to MyGet
|
||||
type: boolean
|
||||
@@ -517,10 +513,7 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- ${{ if eq(parameters.isNightly, true) }}:
|
||||
pwsh: npm run test --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
- pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
@@ -556,8 +549,6 @@ stages:
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
# condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerAcceptanceTests}}) # Outcommented due to timeouts
|
||||
condition: eq(${{parameters.sqlServerAcceptanceTests}}, True)
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
@@ -593,7 +584,8 @@ stages:
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)" | Out-File .env
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
@@ -660,10 +652,7 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- ${{ if eq(parameters.isNightly, true) }}:
|
||||
pwsh: npm run test --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
- pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
@@ -714,7 +703,7 @@ stages:
|
||||
dependsOn:
|
||||
- Unit
|
||||
- Integration
|
||||
# - E2E
|
||||
# - E2E
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
|
||||
400
build/nightly-E2E-test-pipelines.yml
Normal file
400
build/nightly-E2E-test-pipelines.yml
Normal file
@@ -0,0 +1,400 @@
|
||||
name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
solution: umbraco.sln
|
||||
buildConfiguration: Release
|
||||
UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_GENERATE_ASPNET_CERTIFICATE: false
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_client
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
|
||||
parameters:
|
||||
- name: runSmokeTests
|
||||
displayName: Run the smoke tests
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
stages:
|
||||
###############################################
|
||||
## Build
|
||||
###############################################
|
||||
- stage: Build
|
||||
jobs:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 0
|
||||
submodules: true
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
- template: templates/backoffice-install.yml
|
||||
- script: npm run build:for:cms
|
||||
displayName: Run build (Bellissima)
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
displayName: Run npm ci (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- script: npm run build
|
||||
displayName: Run npm build (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet restore
|
||||
inputs:
|
||||
command: restore
|
||||
projects: $(solution)
|
||||
- task: DotNetCoreCLI@2
|
||||
name: build
|
||||
displayName: Run dotnet build and generate NuGet packages
|
||||
inputs:
|
||||
command: build
|
||||
projects: $(solution)
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish nupkg
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
artifactName: nupkg
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish build artifacts
|
||||
inputs:
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
artifactName: build_output
|
||||
|
||||
- stage: E2E
|
||||
displayName: E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
# Enable console logging in Release mode
|
||||
SERILOG__WRITETO__0__NAME: Async
|
||||
SERILOG__WRITETO__0__ARGS__CONFIGURE__0__NAME: Console
|
||||
# Set unattended install settings
|
||||
UMBRACO__CMS__UNATTENDED__INSTALLUNATTENDED: true
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERNAME: Playwright Test
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD: UmbracoAcceptance123!
|
||||
UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL: playwright@umbraco.com
|
||||
# Custom Umbraco settings
|
||||
UMBRACO__CMS__CONTENT__CONTENTVERSIONCLEANUPPOLICY__ENABLECLEANUP: false
|
||||
UMBRACO__CMS__GLOBAL__DISABLEELECTIONFORSINGLESERVER: true
|
||||
UMBRACO__CMS__GLOBAL__INSTALLMISSINGDATABASE: true
|
||||
UMBRACO__CMS__GLOBAL__ID: 00000000-0000-0000-0000-000000000042
|
||||
UMBRACO__CMS__GLOBAL__VERSIONCHECKPERIOD: 0
|
||||
UMBRACO__CMS__GLOBAL__USEHTTPS: true
|
||||
UMBRACO__CMS__HEALTHCHECKS__NOTIFICATION__ENABLED: false
|
||||
UMBRACO__CMS__KEEPALIVE__DISABLEKEEPALIVETASK: true
|
||||
UMBRACO__CMS__WEBROUTING__UMBRACOAPPLICATIONURL: https://localhost:44331/
|
||||
ASPNETCORE_URLS: https://localhost:44331
|
||||
jobs:
|
||||
# E2E Tests
|
||||
- job:
|
||||
displayName: E2E Tests (SQLite)
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- ${{ if eq(parameters.runSmokeTests, true) }}:
|
||||
pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run test --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: 'Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True'
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- ${{ if eq(parameters.runSmokeTests, true) }}:
|
||||
pwsh: npm run smokeTest --ignore-certificate-errors
|
||||
${{ else }}:
|
||||
pwsh: npm run test --ignore-certificate-errors
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: 'Acceptance Tests - $(Agent.JobName) - Attempt #$(System.JobAttempt)'
|
||||
@@ -26,7 +26,7 @@ steps:
|
||||
useSameBranch: true
|
||||
waitForQueuedBuildsToFinish: false
|
||||
storeInEnvironmentVariable: false
|
||||
templateParameters: 'sqlServerIntegrationTests: true, sqlServerAcceptanceTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
|
||||
templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
|
||||
authenticationMethod: 'OAuth Token'
|
||||
enableBuildInQueueCondition: false
|
||||
dependentOnSuccessfulBuildCondition: false
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="OpenIddict.Abstractions" />
|
||||
<PackageReference Include="OpenIddict.AspNetCore" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing.Validation;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
@@ -9,6 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content;
|
||||
|
||||
public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
|
||||
protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status)
|
||||
=> OperationStatusResult(status, problemDetailsBuilder => status switch
|
||||
{
|
||||
@@ -96,7 +99,8 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
}
|
||||
|
||||
var errors = new SortedDictionary<string, string[]>();
|
||||
var missingPropertyAliases = new List<string>();
|
||||
|
||||
var missingPropertyModels = new List<PropertyValidationResponseModel>();
|
||||
foreach (PropertyValidationError validationError in validationResult.ValidationErrors)
|
||||
{
|
||||
TValueModel? requestValue = requestModel.Values.FirstOrDefault(value =>
|
||||
@@ -105,7 +109,7 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
&& value.Segment == validationError.Segment);
|
||||
if (requestValue is null)
|
||||
{
|
||||
missingPropertyAliases.Add(validationError.Alias);
|
||||
missingPropertyModels.Add(MapMissingProperty(validationError));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -119,7 +123,16 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
|
||||
.WithTitle("Validation failed")
|
||||
.WithDetail("One or more properties did not pass validation")
|
||||
.WithRequestModelErrors(errors)
|
||||
.WithExtension("missingProperties", missingPropertyAliases.ToArray())
|
||||
.WithExtension("missingValues", missingPropertyModels.ToArray())
|
||||
.Build()));
|
||||
}
|
||||
|
||||
private PropertyValidationResponseModel MapMissingProperty(PropertyValidationError source) =>
|
||||
new()
|
||||
{
|
||||
Alias = source.Alias,
|
||||
Segment = source.Segment,
|
||||
Culture = source.Culture,
|
||||
Messages = source.ErrorMessages,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,18 +18,21 @@ public abstract class CreateDocumentControllerBase : DocumentControllerBase
|
||||
|
||||
protected async Task<IActionResult> HandleRequest(CreateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
|
||||
{
|
||||
IEnumerable<string> cultures = requestModel.Variants
|
||||
.Where(v => v.Culture is not null)
|
||||
.Select(v => v.Culture!);
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
// TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages.
|
||||
// The values are ignored in the ContentEditingService
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
// IEnumerable<string> cultures = requestModel.Variants
|
||||
// .Where(v => v.Culture is not null)
|
||||
// .Select(v => v.Culture!);
|
||||
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
// User,
|
||||
// ContentPermissionResource.WithKeys(ActionNew.ActionLetter, requestModel.Parent?.Id, cultures),
|
||||
// AuthorizationPolicies.ContentPermissionByResource);
|
||||
//
|
||||
// if (!authorizationResult.Succeeded)
|
||||
// {
|
||||
// return Forbidden();
|
||||
// }
|
||||
|
||||
return await authorizedHandler();
|
||||
}
|
||||
|
||||
@@ -21,16 +21,21 @@ public class SearchDocumentItemController : DocumentItemControllerBase
|
||||
_documentPresentationFactory = documentPresentationFactory;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
=> await SearchFromParent(cancellationToken, query, skip, take);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, skip, take);
|
||||
var result = new PagedModel<DocumentItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
|
||||
Total = searchResult.Total
|
||||
Total = searchResult.Total,
|
||||
};
|
||||
|
||||
return await Task.FromResult(Ok(result));
|
||||
|
||||
@@ -17,18 +17,21 @@ public abstract class UpdateDocumentControllerBase : DocumentControllerBase
|
||||
|
||||
protected async Task<IActionResult> HandleRequest(Guid id, UpdateDocumentRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
|
||||
{
|
||||
IEnumerable<string> cultures = requestModel.Variants
|
||||
.Where(v => v.Culture is not null)
|
||||
.Select(v => v.Culture!);
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
// TODO This have temporarily been uncommented, to support the client sends values from all cultures, even when the user do not have access to the languages.
|
||||
// The values are ignored in the ContentEditingService
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
// IEnumerable<string> cultures = requestModel.Variants
|
||||
// .Where(v => v.Culture is not null)
|
||||
// .Select(v => v.Culture!);
|
||||
// AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
// User,
|
||||
// ContentPermissionResource.WithKeys(ActionUpdate.ActionLetter, id, cultures),
|
||||
// AuthorizationPolicies.ContentPermissionByResource);
|
||||
//
|
||||
// if (!authorizationResult.Succeeded)
|
||||
// {
|
||||
// return Forbidden();
|
||||
// }
|
||||
|
||||
return await authorizedHandler();
|
||||
}
|
||||
|
||||
@@ -21,16 +21,21 @@ public class SearchMediaItemController : MediaItemControllerBase
|
||||
_mediaPresentationFactory = mediaPresentationFactory;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
=> await SearchFromParent(cancellationToken, query, skip, take, null);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, skip, take);
|
||||
var result = new PagedModel<MediaItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),
|
||||
Total = searchResult.Total
|
||||
Total = searchResult.Total,
|
||||
};
|
||||
|
||||
return await Task.FromResult(Ok(result));
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Security;
|
||||
|
||||
public class BackOfficeDefaultController : Controller
|
||||
{
|
||||
private readonly IRuntime _umbracoRuntime;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public BackOfficeDefaultController(IRuntime umbracoRuntime)
|
||||
=> _umbracoRuntime = umbracoRuntime;
|
||||
|
||||
[Obsolete("Use the non obsoleted constructor instead. Scheduled to be removed in v17")]
|
||||
public BackOfficeDefaultController()
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<IRuntime>())
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Index(CancellationToken cancellationToken)
|
||||
{
|
||||
// force authentication to occur since this is not an authorized endpoint
|
||||
AuthenticateResult result = await this.AuthenticateBackOfficeAsync();
|
||||
// a user can not be authenticated if no users have been created yet, or the user repository is unavailable
|
||||
AuthenticateResult result = _umbracoRuntime.State.Level < RuntimeLevel.Upgrade
|
||||
? AuthenticateResult.Fail("RuntimeLevel " + _umbracoRuntime.State.Level + " does not support authentication")
|
||||
: await this.AuthenticateBackOfficeAsync();
|
||||
|
||||
// if we are not authenticated then we need to redirect to the login page
|
||||
if (!result.Succeeded)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Server;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Server;
|
||||
|
||||
@@ -11,8 +13,20 @@ namespace Umbraco.Cms.Api.Management.Controllers.Server;
|
||||
public class ConfigurationServerController : ServerControllerBase
|
||||
{
|
||||
private readonly SecuritySettings _securitySettings;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
public ConfigurationServerController(IOptions<SecuritySettings> securitySettings) => _securitySettings = securitySettings.Value;
|
||||
[Obsolete("Use the constructor that accepts all arguments. Will be removed in V16.")]
|
||||
public ConfigurationServerController(IOptions<SecuritySettings> securitySettings)
|
||||
: this(securitySettings, StaticServiceProvider.Instance.GetRequiredService<IOptions<GlobalSettings>>())
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ConfigurationServerController(IOptions<SecuritySettings> securitySettings, IOptions<GlobalSettings> globalSettings)
|
||||
{
|
||||
_securitySettings = securitySettings.Value;
|
||||
_globalSettings = globalSettings.Value;
|
||||
}
|
||||
|
||||
[HttpGet("configuration")]
|
||||
[MapToApiVersion("1.0")]
|
||||
@@ -22,6 +36,7 @@ public class ConfigurationServerController : ServerControllerBase
|
||||
var responseModel = new ServerConfigurationResponseModel
|
||||
{
|
||||
AllowPasswordReset = _securitySettings.AllowPasswordReset,
|
||||
VersionCheckPeriod = _globalSettings.VersionCheckPeriod
|
||||
};
|
||||
|
||||
return Task.FromResult<IActionResult>(Ok(responseModel));
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Server;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Server;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.RequireAdminAccess)]
|
||||
public class UpgradeCheckServerController : ServerControllerBase
|
||||
{
|
||||
private readonly IUpgradeService _upgradeService;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
|
||||
public UpgradeCheckServerController(IUpgradeService upgradeService, IUmbracoVersion umbracoVersion)
|
||||
{
|
||||
_upgradeService = upgradeService;
|
||||
_umbracoVersion = umbracoVersion;
|
||||
}
|
||||
|
||||
[HttpGet("upgrade-check")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(UpgradeCheckResponseModel), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> UpgradeCheck(CancellationToken cancellationToken)
|
||||
{
|
||||
UpgradeResult upgradeResult = await _upgradeService.CheckUpgrade(_umbracoVersion.SemanticVersion);
|
||||
|
||||
var responseModel = new UpgradeCheckResponseModel
|
||||
{
|
||||
Type = upgradeResult.UpgradeType,
|
||||
Comment = upgradeResult.Comment,
|
||||
Url = upgradeResult.UpgradeUrl.IsNullOrWhiteSpace()
|
||||
? string.Empty
|
||||
: $"{upgradeResult.UpgradeUrl}?version={_umbracoVersion.Version.ToString(3)}"
|
||||
};
|
||||
|
||||
return Ok(responseModel);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ internal static class ApplicationBuilderExtensions
|
||||
{
|
||||
innerBuilder.UseExceptionHandler(exceptionBuilder => exceptionBuilder.Run(async context =>
|
||||
{
|
||||
var isDebug = context.RequestServices.GetRequiredService<IHostingEnvironment>().IsDebugMode;
|
||||
Exception? exception = context.Features.Get<IExceptionHandlerPathFeature>()?.Error;
|
||||
if (exception is null)
|
||||
{
|
||||
@@ -42,16 +43,16 @@ internal static class ApplicationBuilderExtensions
|
||||
var response = new ProblemDetails
|
||||
{
|
||||
Title = exception.Message,
|
||||
Detail = exception.StackTrace,
|
||||
Detail = isDebug ? exception.StackTrace : null,
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Instance = exception.GetType().Name,
|
||||
Instance = isDebug ? exception.GetType().Name : null,
|
||||
Type = "Error"
|
||||
};
|
||||
await context.Response.WriteAsJsonAsync(response);
|
||||
}));
|
||||
});
|
||||
|
||||
internal static IApplicationBuilder UseEndpoints(this IApplicationBuilder applicationBuilder)
|
||||
internal static IApplicationBuilder UseEndpoints(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
IServiceProvider provider = applicationBuilder.ApplicationServices;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, UserGroupPermissionHandler>();
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, UserPermissionHandler>();
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, AllowedApplicationHandler>();
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, BackOfficeHandler>();
|
||||
|
||||
builder.Services.AddAuthorization(CreatePolicies);
|
||||
return builder;
|
||||
@@ -46,7 +47,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions
|
||||
options.AddPolicy(AuthorizationPolicies.BackOfficeAccess, policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
|
||||
policy.RequireAuthenticatedUser();
|
||||
policy.Requirements.Add(new BackOfficeRequirement());
|
||||
});
|
||||
|
||||
options.AddPolicy(AuthorizationPolicies.RequireAdminAccess, policy =>
|
||||
|
||||
@@ -27,13 +27,14 @@ internal sealed class DocumentNotificationPresentationFactory : IDocumentNotific
|
||||
.ToArray()
|
||||
?? Array.Empty<string>();
|
||||
|
||||
var availableActionIds = _actionCollection.Where(a => a.ShowInNotifier).Select(a => a.Letter.ToString()).ToArray();
|
||||
|
||||
return await Task.FromResult(
|
||||
availableActionIds.Select(actionId => new DocumentNotificationResponseModel
|
||||
return await Task.FromResult(_actionCollection
|
||||
.Where(action => action.ShowInNotifier)
|
||||
.Select(action => new DocumentNotificationResponseModel
|
||||
{
|
||||
ActionId = actionId,
|
||||
Subscribed = subscribedActionIds.Contains(actionId)
|
||||
}).ToArray());
|
||||
ActionId = action.Letter,
|
||||
Alias = action.Alias,
|
||||
Subscribed = subscribedActionIds.Contains(action.Letter)
|
||||
})
|
||||
.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9875,6 +9875,14 @@
|
||||
"format": "int32",
|
||||
"default": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parentId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -15545,6 +15553,14 @@
|
||||
"format": "int32",
|
||||
"default": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "parentId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -25176,6 +25192,41 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/server/upgrade-check": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Server"
|
||||
],
|
||||
"operationId": "GetServerUpgradeCheck",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UpgradeCheckResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/item/static-file": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -36244,6 +36295,7 @@
|
||||
"DocumentNotificationResponseModel": {
|
||||
"required": [
|
||||
"actionId",
|
||||
"alias",
|
||||
"subscribed"
|
||||
],
|
||||
"type": "object",
|
||||
@@ -36251,6 +36303,9 @@
|
||||
"actionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"alias": {
|
||||
"type": "string"
|
||||
},
|
||||
"subscribed": {
|
||||
"type": "boolean"
|
||||
}
|
||||
@@ -37907,7 +37962,9 @@
|
||||
},
|
||||
"providerProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": { },
|
||||
"additionalProperties": {
|
||||
"nullable": true
|
||||
},
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
@@ -42446,12 +42503,17 @@
|
||||
},
|
||||
"ServerConfigurationResponseModel": {
|
||||
"required": [
|
||||
"allowPasswordReset"
|
||||
"allowPasswordReset",
|
||||
"versionCheckPeriod"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowPasswordReset": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"versionCheckPeriod": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -44514,6 +44576,26 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"UpgradeCheckResponseModel": {
|
||||
"required": [
|
||||
"comment",
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"UpgradeSettingsResponseModel": {
|
||||
"required": [
|
||||
"currentState",
|
||||
@@ -45275,4 +45357,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
using Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security.Authorization.DenyLocalLogin;
|
||||
|
||||
@@ -24,12 +25,12 @@ public class DenyLocalLoginHandler : MustSatisfyRequirementAuthorizationHandler<
|
||||
|
||||
if (isDenied is false)
|
||||
{
|
||||
// AuthorizationPolicies.BackOfficeAccess policy adds this requirement by policy.RequireAuthenticatedUser()
|
||||
// AuthorizationPolicies.BackOfficeAccess policy adds this requirement by policy.Requirements.Add(new BackOfficeRequirement());
|
||||
// Since we want to "allow anonymous" for some endpoints (i.e. BackOfficeController.Login()), it is necessary to succeed this requirement
|
||||
IEnumerable<DenyAnonymousAuthorizationRequirement> denyAnonymousUserRequirements = context.PendingRequirements.OfType<DenyAnonymousAuthorizationRequirement>();
|
||||
foreach (DenyAnonymousAuthorizationRequirement denyAnonymousUserRequirement in denyAnonymousUserRequirements)
|
||||
IEnumerable<BackOfficeRequirement> backOfficeRequirements = context.PendingRequirements.OfType<BackOfficeRequirement>();
|
||||
foreach (BackOfficeRequirement backOfficeRequirement in backOfficeRequirements)
|
||||
{
|
||||
context.Succeed(denyAnonymousUserRequirement);
|
||||
context.Succeed(backOfficeRequirement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures authorization is successful for a back office user.
|
||||
/// </summary>
|
||||
public class BackOfficeHandler : MustSatisfyRequirementAuthorizationHandler<BackOfficeRequirement>
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurity;
|
||||
|
||||
public BackOfficeHandler(IBackOfficeSecurityAccessor backOfficeSecurity)
|
||||
{
|
||||
_backOfficeSecurity = backOfficeSecurity;
|
||||
}
|
||||
|
||||
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, BackOfficeRequirement requirement)
|
||||
{
|
||||
|
||||
if (context.HasFailed is false && context.HasSucceeded is true)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
if (!_backOfficeSecurity.BackOfficeSecurity?.IsAuthenticated() ?? false)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var userApprovalSucceeded = !requirement.RequireApproval ||
|
||||
(_backOfficeSecurity.BackOfficeSecurity?.CurrentUser?.IsApproved ?? false);
|
||||
return Task.FromResult(userApprovalSucceeded);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security.Authorization.User;
|
||||
|
||||
/// <summary>
|
||||
/// Authorization requirement for the <see cref="BackOfficeHandler" />.
|
||||
/// </summary>
|
||||
public class BackOfficeRequirement : IAuthorizationRequirement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeRequirement" /> class.
|
||||
/// </summary>
|
||||
/// <param name="requireApproval">Flag for whether back-office user approval is required.</param>
|
||||
public BackOfficeRequirement(bool requireApproval = true) => RequireApproval = requireApproval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether back-office user approval is required.
|
||||
/// </summary>
|
||||
public bool RequireApproval { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Content;
|
||||
|
||||
public class PropertyValidationResponseModel
|
||||
{
|
||||
public string[] Messages { get; set; } = Array.Empty<string>();
|
||||
|
||||
public string Alias { get; set; } = string.Empty;
|
||||
|
||||
public string? Culture { get; set; }
|
||||
|
||||
public string? Segment { get; set; }
|
||||
}
|
||||
@@ -4,5 +4,7 @@ public class DocumentNotificationResponseModel
|
||||
{
|
||||
public required string ActionId { get; set; }
|
||||
|
||||
public required string Alias { get; set; }
|
||||
|
||||
public required bool Subscribed { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
public class ServerConfigurationResponseModel
|
||||
{
|
||||
public bool AllowPasswordReset { get; set; }
|
||||
|
||||
public int VersionCheckPeriod { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Server;
|
||||
|
||||
public class UpgradeCheckResponseModel
|
||||
{
|
||||
public required string Type { get; init; }
|
||||
|
||||
public required string Comment { get; init; }
|
||||
|
||||
public required string Url { get; init; }
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Server;
|
||||
|
||||
[Obsolete("Not used. Will be removed in V15.")]
|
||||
public class VersionResponseModel
|
||||
{
|
||||
[Required]
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
<!-- Take top-level depedendency on Azure.Identity, because Microsoft.EntityFrameworkCore.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
<!-- Take top-level depedendency on Azure.Identity, because NPoco.SqlServer depends on a vulnerable version -->
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="NPoco.SqlServer" />
|
||||
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -40,9 +40,14 @@
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm run build:for:cms" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildLogin" DependsOnTargets="BuildBellissima">
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm ci --no-fund --no-audit --prefer-offline" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm run build" />
|
||||
<Target Name="BuildBelle">
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm ci --no-fund --no-audit --prefer-offline" Timeout="600000" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Client\" Command="npm run build:skip-tests" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildLogin">
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm ci --no-fund --no-audit --prefer-offline" Timeout="600000" />
|
||||
<Exec WorkingDirectory="$(ProjectDir)..\Umbraco.Web.UI.Login\" Command="npm run build" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<!-- Generate JSON schema on build (and before copying to project) -->
|
||||
<Target Name="GenerateAppsettingsSchema" BeforeTargets="Build;CopyUmbracoJsonSchemaFiles" Condition="!Exists('$(_UmbracoCmsJsonSchemaReference)')">
|
||||
<Message Text="Generating $(_UmbracoCmsJsonSchemaReference) because it doesn't exist" Importance="high" />
|
||||
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)..\..\tools\Umbraco.JsonSchema" Command="dotnet run --configuration $(Configuration) -- --outputFile "$(MSBuildThisFileDirectory)$(_UmbracoCmsJsonSchemaReference)"" />
|
||||
<Exec WorkingDirectory="$(MSBuildThisFileDirectory)..\..\tools\Umbraco.JsonSchema" Command="dotnet run --configuration $(Configuration) -- --outputFile "$(MSBuildThisFileDirectory)$(_UmbracoCmsJsonSchemaReference)"" Timeout="600000" />
|
||||
</Target>
|
||||
|
||||
<!-- Remove generated JSON schema on clean -->
|
||||
|
||||
@@ -13,7 +13,7 @@ public static partial class Constants
|
||||
public static readonly string[] UmbracoCoreAssemblyNames =
|
||||
{
|
||||
"Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.Examine.Lucene",
|
||||
"Umbraco.Web.Common", "Umbraco.Web.BackOffice", "Umbraco.Web.Website",
|
||||
"Umbraco.Web.Common", "Umbraco.Cms.Api.Common","Umbraco.Cms.Api.Delivery","Umbraco.Cms.Api.Management", "Umbraco.Web.Website",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using Umbraco.Cms.Core.Models.Entities;
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods that return udis for Umbraco entities.
|
||||
/// Provides extension methods that return UDIs for Umbraco entities.
|
||||
/// </summary>
|
||||
public static class UdiGetterExtensions
|
||||
{
|
||||
@@ -19,11 +19,177 @@ public static class UdiGetterExtensions
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this ITemplate entity)
|
||||
public static Udi GetUdi(this IEntity entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed();
|
||||
return entity switch
|
||||
{
|
||||
// Concrete types
|
||||
EntityContainer container => container.GetUdi(),
|
||||
Script script => script.GetUdi(),
|
||||
Stylesheet stylesheet => stylesheet.GetUdi(),
|
||||
// Interfaces
|
||||
IContentBase contentBase => contentBase.GetUdi(),
|
||||
IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(),
|
||||
IDataType dataType => dataType.GetUdi(),
|
||||
IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(),
|
||||
ILanguage language => language.GetUdi(),
|
||||
IMemberGroup memberGroup => memberGroup.GetUdi(),
|
||||
IPartialView partialView => partialView.GetUdi(),
|
||||
IRelationType relationType => relationType.GetUdi(),
|
||||
ITemplate template => template.GetUdi(),
|
||||
IWebhook webhook => webhook.GetUdi(),
|
||||
_ => throw new NotSupportedException($"Entity type {entity.GetType().FullName} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this EntityContainer entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string entityType;
|
||||
if (entity.ContainedObjectType == Constants.ObjectTypes.DataType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DataTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DocumentTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.MediaTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DocumentBlueprintContainer;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Contained object type {entity.ContainedObjectType} is not supported.");
|
||||
}
|
||||
|
||||
return new GuidUdi(entityType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this Script entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this Stylesheet entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContentBase entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return entity switch
|
||||
{
|
||||
IContent content => content.GetUdi(),
|
||||
IMedia media => media.GetUdi(),
|
||||
IMember member => member.GetUdi(),
|
||||
_ => throw new NotSupportedException($"Content base type {entity.GetType().FullName} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContent entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document;
|
||||
|
||||
return new GuidUdi(entityType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IMedia entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IMember entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContentTypeComposition entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return entity switch
|
||||
{
|
||||
IContentType contentType => contentType.GetUdi(),
|
||||
IMediaType mediaType => mediaType.GetUdi(),
|
||||
IMemberType memberType => memberType.GetUdi(),
|
||||
_ => throw new NotSupportedException($"Composition type {entity.GetType().FullName} is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,42 +234,6 @@ public static class UdiGetterExtensions
|
||||
return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IMemberGroup entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContentTypeComposition entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string entityType = entity switch
|
||||
{
|
||||
IContentType => Constants.UdiEntityType.DocumentType,
|
||||
IMediaType => Constants.UdiEntityType.MediaType,
|
||||
IMemberType => Constants.UdiEntityType.MemberType,
|
||||
_ => throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)),
|
||||
};
|
||||
|
||||
return new GuidUdi(entityType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
@@ -118,129 +248,6 @@ public static class UdiGetterExtensions
|
||||
return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this EntityContainer entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string entityType;
|
||||
if (entity.ContainedObjectType == Constants.ObjectTypes.DataType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DataTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DocumentTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.MediaTypeContainer;
|
||||
}
|
||||
else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentBlueprint)
|
||||
{
|
||||
entityType = Constants.UdiEntityType.DocumentBlueprintContainer;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType));
|
||||
}
|
||||
|
||||
return new GuidUdi(entityType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IMedia entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContent entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string entityType = entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document;
|
||||
|
||||
return new GuidUdi(entityType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IMember entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this Stylesheet entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.Stylesheet, entity.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this Script entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.Script, entity.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UDI from a path.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The type of the entity.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
private static StringUdi GetUdiFromPath(string entityType, string path)
|
||||
{
|
||||
string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/");
|
||||
|
||||
return new StringUdi(entityType, id).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
@@ -262,11 +269,11 @@ public static class UdiGetterExtensions
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this IPartialView entity)
|
||||
public static StringUdi GetUdi(this ILanguage entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path);
|
||||
return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -276,19 +283,25 @@ public static class UdiGetterExtensions
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this IContentBase entity)
|
||||
public static GuidUdi GetUdi(this IMemberGroup entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
string type = entity switch
|
||||
{
|
||||
IContent => Constants.UdiEntityType.Document,
|
||||
IMedia => Constants.UdiEntityType.Media,
|
||||
IMember => Constants.UdiEntityType.Member,
|
||||
_ => throw new NotSupportedException(string.Format("Content base type {0} is not supported.", entity.GetType().FullName)),
|
||||
};
|
||||
return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
return new GuidUdi(type, entity.Key).EnsureClosed();
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this IPartialView entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return GetUdiFromPath(Constants.UdiEntityType.PartialView, entity.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,6 +318,20 @@ public static class UdiGetterExtensions
|
||||
return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static GuidUdi GetUdi(this ITemplate entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
@@ -320,56 +347,17 @@ public static class UdiGetterExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// Gets the UDI from a path.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="entityType">The type of the entity.</param>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static StringUdi GetUdi(this ILanguage entity)
|
||||
private static StringUdi GetUdiFromPath(string entityType, string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
string id = path.TrimStart(Constants.CharArrays.ForwardSlash).Replace("\\", "/");
|
||||
|
||||
return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity identifier of the entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>
|
||||
/// The entity identifier of the entity.
|
||||
/// </returns>
|
||||
public static Udi GetUdi(this IEntity entity)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entity);
|
||||
|
||||
return entity switch
|
||||
{
|
||||
// Concrete types
|
||||
EntityContainer container => container.GetUdi(),
|
||||
Stylesheet stylesheet => stylesheet.GetUdi(),
|
||||
Script script => script.GetUdi(),
|
||||
// Content types
|
||||
IContentType contentType => contentType.GetUdi(),
|
||||
IMediaType mediaType => mediaType.GetUdi(),
|
||||
IMemberType memberType => memberType.GetUdi(),
|
||||
IContentTypeComposition contentTypeComposition => contentTypeComposition.GetUdi(),
|
||||
// Content
|
||||
IContent content => content.GetUdi(),
|
||||
IMedia media => media.GetUdi(),
|
||||
IMember member => member.GetUdi(),
|
||||
IContentBase contentBase => contentBase.GetUdi(),
|
||||
// Other
|
||||
IDataType dataTypeComposition => dataTypeComposition.GetUdi(),
|
||||
IDictionaryItem dictionaryItem => dictionaryItem.GetUdi(),
|
||||
ILanguage language => language.GetUdi(),
|
||||
IMemberGroup memberGroup => memberGroup.GetUdi(),
|
||||
IPartialView partialView => partialView.GetUdi(),
|
||||
IRelationType relationType => relationType.GetUdi(),
|
||||
ITemplate template => template.GetUdi(),
|
||||
IWebhook webhook => webhook.GetUdi(),
|
||||
_ => throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)),
|
||||
};
|
||||
return new StringUdi(entityType, id).EnsureClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,16 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
/// Wrapper class for OEmbed response.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class OEmbedResponse : OEmbedResponseBase<double>;
|
||||
public class OEmbedResponse : OEmbedResponseBase<double>
|
||||
{
|
||||
|
||||
// these is only here to avoid breaking changes. In theory it should still be source code compatible to remove them.
|
||||
public new double? ThumbnailHeight => base.ThumbnailHeight;
|
||||
|
||||
public new double? ThumbnailWidth => base.ThumbnailWidth;
|
||||
|
||||
public new double? Height => base.Height;
|
||||
|
||||
public new double? Width => base.Width;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class OEmbedResponseBase<T>
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "thumbnail_height")]
|
||||
public T? ThumbnailHeight { get; set; }
|
||||
public virtual T? ThumbnailHeight { get; set; }
|
||||
|
||||
[DataMember(Name = "thumbnail_width")]
|
||||
public T? ThumbnailWidth { get; set; }
|
||||
|
||||
@@ -16,14 +16,10 @@ public class UpgradeCheckRepository : IUpgradeCheckRepository
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_httpClient == null)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
_httpClient ??= new HttpClient { Timeout = TimeSpan.FromSeconds(1) };
|
||||
|
||||
using var content = new StringContent(_jsonSerializer.Serialize(new CheckUpgradeDto(version)), Encoding.UTF8, "application/json");
|
||||
|
||||
_httpClient.Timeout = TimeSpan.FromSeconds(1);
|
||||
using HttpResponseMessage task = await _httpClient.PostAsync(RestApiUpgradeChecklUrl, content);
|
||||
var json = await task.Content.ReadAsStringAsync();
|
||||
UpgradeResult? result = _jsonSerializer.Deserialize<UpgradeResult>(json);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
/// <summary>
|
||||
/// A validator that validates that the value is not null or empty (if it is a string)
|
||||
/// </summary>
|
||||
public sealed class RequiredValidator : IValueRequiredValidator, IValueValidator
|
||||
public class RequiredValidator : IValueRequiredValidator, IValueValidator
|
||||
{
|
||||
[Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")]
|
||||
public RequiredValidator(ILocalizedTextService textService)
|
||||
@@ -24,7 +24,7 @@ public sealed class RequiredValidator : IValueRequiredValidator, IValueValidator
|
||||
ValidateRequired(value, valueType);
|
||||
|
||||
/// <inheritdoc cref="IValueRequiredValidator.ValidateRequired" />
|
||||
public IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
public virtual IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
@@ -12,7 +16,11 @@ internal sealed class ContentEditingService
|
||||
{
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly ILogger<ContentEditingService> _logger;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILanguageService _languageService;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16.")]
|
||||
public ContentEditingService(
|
||||
IContentService contentService,
|
||||
IContentTypeService contentTypeService,
|
||||
@@ -24,10 +32,46 @@ internal sealed class ContentEditingService
|
||||
IUserIdKeyResolver userIdKeyResolver,
|
||||
ITreeEntitySortingService treeEntitySortingService,
|
||||
IContentValidationService contentValidationService)
|
||||
: this(
|
||||
contentService,
|
||||
contentTypeService,
|
||||
propertyEditorCollection,
|
||||
dataTypeService,
|
||||
templateService,
|
||||
logger,
|
||||
scopeProvider,
|
||||
userIdKeyResolver,
|
||||
treeEntitySortingService,
|
||||
contentValidationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IUserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>()
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ContentEditingService(
|
||||
IContentService contentService,
|
||||
IContentTypeService contentTypeService,
|
||||
PropertyEditorCollection propertyEditorCollection,
|
||||
IDataTypeService dataTypeService,
|
||||
ITemplateService templateService,
|
||||
ILogger<ContentEditingService> logger,
|
||||
ICoreScopeProvider scopeProvider,
|
||||
IUserIdKeyResolver userIdKeyResolver,
|
||||
ITreeEntitySortingService treeEntitySortingService,
|
||||
IContentValidationService contentValidationService,
|
||||
IUserService userService,
|
||||
ILocalizationService localizationService,
|
||||
ILanguageService languageService)
|
||||
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, contentValidationService, treeEntitySortingService)
|
||||
{
|
||||
_templateService = templateService;
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
_localizationService = localizationService;
|
||||
_languageService = languageService;
|
||||
}
|
||||
|
||||
public async Task<IContent?> GetAsync(Guid key)
|
||||
@@ -65,7 +109,7 @@ internal sealed class ContentEditingService
|
||||
ContentEditingOperationStatus validationStatus = result.Status;
|
||||
ContentValidationResult validationResult = result.Result.ValidationResult;
|
||||
|
||||
IContent content = result.Result.Content!;
|
||||
IContent content = await EnsureOnlyAllowedFieldsAreUpdated(result.Result.Content!, userKey);
|
||||
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, createModel.TemplateKey);
|
||||
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
|
||||
{
|
||||
@@ -78,6 +122,53 @@ internal sealed class ContentEditingService
|
||||
: Attempt.FailWithStatus(saveStatus, new ContentCreateResult { Content = content });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A temporary method that ensures the data is sent in is overridden by the original data, in cases where the user do not have permissions to change the data.
|
||||
/// </summary>
|
||||
private async Task<IContent> EnsureOnlyAllowedFieldsAreUpdated(IContent contentWithPotentialUnallowedChanges, Guid userKey)
|
||||
{
|
||||
if (contentWithPotentialUnallowedChanges.ContentType.VariesByCulture() is false)
|
||||
{
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
IContent? existingContent = await GetAsync(contentWithPotentialUnallowedChanges.Key);
|
||||
|
||||
IUser? user = await _userService.GetAsync(userKey);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
var allowedLanguageIds = user.CalculateAllowedLanguageIds(_localizationService)!;
|
||||
|
||||
var allowedCultures = (await _languageService.GetIsoCodesByIdsAsync(allowedLanguageIds)).ToHashSet();
|
||||
|
||||
foreach (var culture in contentWithPotentialUnallowedChanges.EditedCultures ?? contentWithPotentialUnallowedChanges.PublishedCultures)
|
||||
{
|
||||
if (allowedCultures.Contains(culture))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// else override the updates values with the original values.
|
||||
foreach (IProperty property in contentWithPotentialUnallowedChanges.Properties)
|
||||
{
|
||||
if (property.PropertyType.VariesByCulture() is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = existingContent?.Properties.First(x=>x.Alias == property.Alias).GetValue(culture, null, false);
|
||||
property.SetValue(value, culture, null);
|
||||
}
|
||||
}
|
||||
|
||||
return contentWithPotentialUnallowedChanges;
|
||||
}
|
||||
|
||||
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentUpdateModel updateModel, Guid userKey)
|
||||
{
|
||||
IContent? content = ContentService.GetById(key);
|
||||
@@ -102,6 +193,8 @@ internal sealed class ContentEditingService
|
||||
ContentEditingOperationStatus validationStatus = result.Status;
|
||||
ContentValidationResult validationResult = result.Result.ValidationResult;
|
||||
|
||||
content = await EnsureOnlyAllowedFieldsAreUpdated(content, userKey);
|
||||
|
||||
ContentEditingOperationStatus updateTemplateStatus = await UpdateTemplateAsync(content, updateModel.TemplateKey);
|
||||
if (updateTemplateStatus != ContentEditingOperationStatus.Success)
|
||||
{
|
||||
|
||||
@@ -523,8 +523,6 @@ public class FileService : RepositoryService, IFileService
|
||||
/// </summary>
|
||||
/// <param name="templates">List of <see cref="Template" /> to save</param>
|
||||
/// <param name="userId">Optional id of the user</param>
|
||||
// FIXME: we need to re-implement PackageDataInstallation.ImportTemplates so it imports templates in the correct order
|
||||
// instead of relying on being able to save invalid templates (child templates whose master has yet to be created)
|
||||
[Obsolete("Please use ITemplateService for template operations - will be removed in Umbraco 15")]
|
||||
public void SaveTemplate(IEnumerable<ITemplate> templates, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
|
||||
@@ -196,7 +196,7 @@ public interface IDataTypeService : IService
|
||||
/// </summary>
|
||||
/// <param name="propertyEditorAlias">Alias of the property editor</param>
|
||||
/// <returns>Collection of <see cref="IDataType" /> configured for the property editor</returns>
|
||||
Task<IEnumerable<IDataType>> GetByEditorAliasAsync(string propertyEditorAlias);
|
||||
Task<IEnumerable<IDataType>> GetByEditorAliasAsync(string propertyEditorAlias) => Task.FromResult(GetByEditorAlias(propertyEditorAlias));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="IDataType" /> for a given editor UI alias
|
||||
@@ -246,5 +246,5 @@ public interface IDataTypeService : IService
|
||||
/// </summary>
|
||||
/// <param name="propertyEditorAlias">Aliases of the property editors</param>
|
||||
/// <returns>Collection of <see cref="IDataType" /> configured for the property editors</returns>
|
||||
Task<IEnumerable<IDataType>> GetByEditorAliasAsync(string[] propertyEditorAlias);
|
||||
Task<IEnumerable<IDataType>> GetByEditorAliasAsync(string[] propertyEditorAlias) => Task.FromResult(propertyEditorAlias.SelectMany(x=>GetByEditorAlias(x)));
|
||||
}
|
||||
|
||||
@@ -13,4 +13,8 @@ namespace Umbraco.Cms.Core.Services;
|
||||
public interface IIndexedEntitySearchService
|
||||
{
|
||||
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false);
|
||||
|
||||
// default implementation to avoid breaking changes falls back to old behaviour
|
||||
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
|
||||
}
|
||||
|
||||
@@ -65,16 +65,28 @@ public interface IPackageDataInstallation
|
||||
/// <returns>An enumerable list of generated languages</returns>
|
||||
IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId);
|
||||
|
||||
[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
|
||||
IEnumerable<ITemplate> ImportTemplate(XElement templateElement, int userId);
|
||||
|
||||
Task<IEnumerable<ITemplate>> ImportTemplateAsync(XElement templateElement, int userId) => Task.FromResult(ImportTemplate(templateElement, userId));
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="ITemplate"/>
|
||||
/// </summary>
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
|
||||
IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="ITemplate"/>
|
||||
/// </summary>
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
Task<IReadOnlyList<ITemplate>> ImportTemplatesAsync(IReadOnlyCollection<XElement> templateElements, int userId) => Task.FromResult(ImportTemplates(templateElements, userId));
|
||||
|
||||
Guid GetContentTypeKey(XElement contentType);
|
||||
|
||||
string? GetEntityTypeAlias(XElement entityType);
|
||||
|
||||
@@ -5,10 +5,7 @@ public enum ContentTypeOperationStatus
|
||||
Success,
|
||||
DuplicateAlias,
|
||||
InvalidAlias,
|
||||
NameCannotBeEmpty,
|
||||
NameTooLong,
|
||||
InvalidPropertyTypeAlias,
|
||||
PropertyTypeAliasCannotEqualContentTypeAlias,
|
||||
DuplicatePropertyTypeAlias,
|
||||
DataTypeNotFound,
|
||||
InvalidInheritance,
|
||||
@@ -21,6 +18,9 @@ public enum ContentTypeOperationStatus
|
||||
NotFound,
|
||||
NotAllowed,
|
||||
CancelledByNotification,
|
||||
PropertyTypeAliasCannotEqualContentTypeAlias,
|
||||
NameCannotBeEmpty,
|
||||
NameTooLong,
|
||||
InvalidElementFlagDocumentHasContent,
|
||||
InvalidElementFlagElementIsUsedInPropertyEditorConfiguration,
|
||||
InvalidElementFlagComparedToParent,
|
||||
|
||||
@@ -356,8 +356,16 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
throw new ArgumentNullException(nameof(entityService));
|
||||
}
|
||||
|
||||
UdiParser.TryParse(searchFrom, true, out Udi? udi);
|
||||
searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString();
|
||||
if (Guid.TryParse(searchFrom, out Guid guid))
|
||||
{
|
||||
searchFrom = entityService.GetId(guid, objectType).Result.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// fallback to Udi for legacy reasons as the calling methods take string?
|
||||
UdiParser.TryParse(searchFrom, true, out Udi? udi);
|
||||
searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString();
|
||||
}
|
||||
|
||||
TreeEntityPath? entityPath =
|
||||
int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) &&
|
||||
|
||||
@@ -26,6 +26,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
@@ -237,6 +238,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
|
||||
builder.Services.AddSingleton<IBlockEditorElementTypeCache, BlockEditorElementTypeCache>();
|
||||
|
||||
builder.Services.AddSingleton<IRichTextRequiredValidator, RichTextRequiredValidator>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ public static partial class UmbracoBuilderExtensions
|
||||
factory.GetRequiredService<IShortStringHelper>(),
|
||||
factory.GetRequiredService<IConfigurationEditorJsonSerializer>(),
|
||||
factory.GetRequiredService<IMediaService>(),
|
||||
factory.GetRequiredService<IMediaTypeService>());
|
||||
factory.GetRequiredService<IMediaTypeService>(),
|
||||
factory.GetRequiredService<ITemplateContentParserService>(),
|
||||
factory.GetRequiredService<ITemplateService>());
|
||||
|
||||
private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSourcesFactory(
|
||||
IServiceProvider container)
|
||||
|
||||
@@ -53,7 +53,6 @@ public class FilePermissionHelper : IFilePermissionHelper
|
||||
{
|
||||
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Bin),
|
||||
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Umbraco),
|
||||
hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoPath),
|
||||
hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,18 +63,19 @@ public class UmbracoPlan : MigrationPlan
|
||||
To<V_13_0_0.ChangeWebhookUrlColumnsToNvarcharMax>("{21C42760-5109-4C03-AB4F-7EA53577D1F5}");
|
||||
To<V_13_0_0.AddExceptionOccured>("{6158F3A3-4902-4201-835E-1ED7F810B2D8}");
|
||||
To<V_13_3_0.AlignUpgradedDatabase>("{985AF2BA-69D3-4DBA-95E0-AD3FA7459FA7}");
|
||||
To<V_13_5_0.ChangeRedirectUrlToNvarcharMax>("{CC47C751-A81B-489A-A2BC-0240245DB687}");
|
||||
|
||||
// To 14.0.0
|
||||
To<V_14_0_0.AddPropertyEditorUiAliasColumn>("{419827A0-4FCE-464B-A8F3-247C6092AF55}");
|
||||
To<V_14_0_0.AddGuidsToUserGroups>("{69E12556-D9B3-493A-8E8A-65EC89FB658D}");
|
||||
To<V_14_0_0.AddUserGroup2PermisionTable>("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}");
|
||||
To<V_14_0_0.AddGuidsToUsers>("{A8E01644-9F2E-4988-8341-587EF5B7EA69}");
|
||||
To<NoopMigration>("{69E12556-D9B3-493A-8E8A-65EC89FB658D}");
|
||||
To<NoopMigration>("{F2B16CD4-F181-4BEE-81C9-11CF384E6025}");
|
||||
To<NoopMigration>("{A8E01644-9F2E-4988-8341-587EF5B7EA69}");
|
||||
To<V_14_0_0.UpdateDefaultGuidsOfCreatedPackages>("{E073DBC0-9E8E-4C92-8210-9CB18364F46E}");
|
||||
To<V_14_0_0.RenameTechnologyLeakingPropertyEditorAliases>("{80D282A4-5497-47FF-991F-BC0BCE603121}");
|
||||
To<V_14_0_0.MigrateSchduledPublishesToUtc>("{96525697-E9DC-4198-B136-25AD033442B8}");
|
||||
To<V_14_0_0.AddListViewKeysToDocumentTypes>("{7FC5AC9B-6F56-415B-913E-4A900629B853}");
|
||||
To<V_14_0_0.MigrateDataTypeConfigurations>("{1539A010-2EB5-4163-8518-4AE2AA98AFC6}");
|
||||
To<V_14_0_0.MigrateCharPermissionsToStrings>("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}");
|
||||
To<NoopMigration>("{C567DE81-DF92-4B99-BEA8-CD34EF99DA5D}");
|
||||
To<V_14_0_0.DeleteMacroTables>("{0D82C836-96DD-480D-A924-7964E458BD34}");
|
||||
To<V_14_0_0.MoveDocumentBlueprintsToFolders>("{1A0FBC8A-6FC6-456C-805C-B94816B2E570}");
|
||||
To<V_14_0_0.MigrateTours>("{302DE171-6D83-4B6B-B3C0-AC8808A16CA1}");
|
||||
@@ -91,5 +92,8 @@ public class UmbracoPlan : MigrationPlan
|
||||
|
||||
// To 14.2.0
|
||||
To<V_14_2_0.AddMissingDateTimeConfiguration>("{20ED404C-6FF9-4F91-8AC9-2B298E0002EB}");
|
||||
|
||||
// To 14.3.0
|
||||
To<V_13_5_0.ChangeRedirectUrlToNvarcharMax>("{EEF792FC-318C-4921-9859-51EBF07A53A3}"); // Execute again, to ensure all that migrated to 14.0.0 without 13.5 will have this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,9 @@ public class UmbracoPremigrationPlan : MigrationPlan
|
||||
|
||||
// To 14.0.0
|
||||
To<V_14_0_0.UpdateToOpenIddictV5>("{76FBF80E-37E6-462E-ADC1-25668F56151D}");
|
||||
To<V_14_0_0.AddGuidsToUserGroups>("{37CF4AC3-8489-44BC-A7E8-64908FEEC656}");
|
||||
To<V_14_0_0.AddUserGroup2PermisionTable>("{7BCB5352-B2ED-4D4B-B27D-ECDED930B50A}");
|
||||
To<V_14_0_0.AddGuidsToUsers>("{3E69BF9B-BEAB-41B1-BB11-15383CCA1C7F}");
|
||||
To<V_14_0_0.MigrateCharPermissionsToStrings>("{F12C609B-86B9-4386-AFA4-78E02857247C}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,23 +120,48 @@ public class AlignUpgradedDatabase : MigrationBase
|
||||
// We need to do this to ensure we don't try to rename the constraint if it doesn't exist.
|
||||
const string tableName = "umbracoContentVersion";
|
||||
const string columnName = "VersionDate";
|
||||
const string newColumnName = "versionDate";
|
||||
const string expectedConstraintName = "DF_umbracoContentVersion_versionDate";
|
||||
|
||||
ColumnInfo? versionDateColumn = columns
|
||||
.FirstOrDefault(x => x is { TableName: tableName, ColumnName: columnName });
|
||||
|
||||
if (versionDateColumn is null)
|
||||
// we only want to rename the column if necessary
|
||||
if (versionDateColumn is not null)
|
||||
{
|
||||
// The column was not found I.E. the column is correctly named
|
||||
return;
|
||||
RenameColumn(tableName, columnName, newColumnName, columns);
|
||||
}
|
||||
|
||||
RenameColumn(tableName, columnName, "versionDate", columns);
|
||||
|
||||
// Renames the default constraint for the column,
|
||||
// apparently the content version table used to be prefixed with cms and not umbraco
|
||||
// We don't have a fluid way to rename the default constraint so we have to use raw SQL
|
||||
// This should be okay though since we are only running this migration on SQL Server
|
||||
Sql<ISqlContext> constraintNameQuery = Database.SqlContext.Sql(@$"
|
||||
SELECT obj_Constraint.NAME AS 'constraintName'
|
||||
FROM sys.objects obj_table
|
||||
JOIN sys.objects obj_Constraint
|
||||
ON obj_table.object_id = obj_Constraint.parent_object_id
|
||||
JOIN sys.sysconstraints constraints
|
||||
ON constraints.constid = obj_Constraint.object_id
|
||||
JOIN sys.columns columns
|
||||
ON columns.object_id = obj_table.object_id
|
||||
AND columns.column_id = constraints.colid
|
||||
WHERE obj_table.NAME = '{tableName}'
|
||||
AND columns.NAME = '{newColumnName}'
|
||||
AND obj_Constraint.type = 'D'
|
||||
");
|
||||
var currentConstraintName = Database.ExecuteScalar<string>(constraintNameQuery);
|
||||
|
||||
|
||||
// only rename the constraint if necessary
|
||||
if (currentConstraintName == expectedConstraintName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sql<ISqlContext> renameConstraintQuery = Database.SqlContext.Sql(
|
||||
"EXEC sp_rename N'DF_cmsContentVersion_VersionDate', N'DF_umbracoContentVersion_versionDate', N'OBJECT'");
|
||||
$"EXEC sp_rename N'{currentConstraintName}', N'{expectedConstraintName}', N'OBJECT'");
|
||||
Database.Execute(renameConstraintQuery);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Column;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_5_0;
|
||||
|
||||
public class ChangeRedirectUrlToNvarcharMax : MigrationBase
|
||||
{
|
||||
public ChangeRedirectUrlToNvarcharMax(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Migrate()
|
||||
{
|
||||
// We don't need to run this migration for SQLite, since ntext is not a thing there, text is just text.
|
||||
if (DatabaseType == DatabaseType.SQLite)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string tableName = RedirectUrlDto.TableName;
|
||||
string colName = "url";
|
||||
|
||||
// Determine the current datatype of the column within the database
|
||||
string colDataType = Database.ExecuteScalar<string>($"SELECT TOP(1) CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS" +
|
||||
$" WHERE TABLE_NAME = '{tableName}' AND COLUMN_NAME = '{colName}'");
|
||||
|
||||
// 255 is the old length, -1 indicate MAX length
|
||||
if (colDataType == "255")
|
||||
{
|
||||
// Upgrade to MAX length
|
||||
Database.Execute($"Drop Index IX_umbracoRedirectUrl_culture_hash on {Constants.DatabaseSchema.Tables.RedirectUrl}");
|
||||
Database.Execute($"ALTER TABLE {tableName} ALTER COLUMN {colName} nvarchar(MAX) NOT NULL");
|
||||
Database.Execute($"CREATE INDEX IX_umbracoRedirectUrl_culture_hash ON {Constants.DatabaseSchema.Tables.RedirectUrl} (urlHash, contentKey, culture, createDateUtc)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,15 +20,24 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase
|
||||
|
||||
protected override void Migrate()
|
||||
{
|
||||
// If the new column already exists we'll do nothing.
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName))
|
||||
{
|
||||
Context.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// SQL server can simply add the column, but for SQLite this won't work,
|
||||
// so we'll have to create a new table and copy over data.
|
||||
if (DatabaseType != DatabaseType.SQLite)
|
||||
{
|
||||
MigrateSqlServer();
|
||||
Context.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
MigrateSqlite();
|
||||
Context.Complete();
|
||||
}
|
||||
|
||||
private void MigrateSqlServer()
|
||||
@@ -37,11 +46,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase
|
||||
using IDisposable notificationSuppression = scope.Notifications.Suppress();
|
||||
ScopeDatabase(scope);
|
||||
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
|
||||
AddColumnIfNotExists<UserGroupDto>(columns, NewColumnName);
|
||||
|
||||
@@ -68,12 +72,6 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase
|
||||
using IDisposable notificationSuppression = scope.Notifications.Suppress();
|
||||
ScopeDatabase(scope);
|
||||
|
||||
// If the new column already exists we'll do nothing.
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This isn't pretty,
|
||||
// But since you cannot alter columns, we have to copy the data over and delete the old table.
|
||||
// However we cannot do this due to foreign keys, so temporarily disable these keys while migrating.
|
||||
|
||||
@@ -26,6 +26,12 @@ internal class AddGuidsToUsers : UnscopedMigrationBase
|
||||
|
||||
protected override void Migrate()
|
||||
{
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName))
|
||||
{
|
||||
Context.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
InvalidateBackofficeUserAccess = true;
|
||||
using IScope scope = _scopeProvider.CreateScope();
|
||||
using IDisposable notificationSuppression = scope.Notifications.Suppress();
|
||||
@@ -75,11 +81,6 @@ internal class AddGuidsToUsers : UnscopedMigrationBase
|
||||
|
||||
private void MigrateSqlite()
|
||||
{
|
||||
if (ColumnExists(Constants.DatabaseSchema.Tables.User, NewColumnName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We commit the initial transaction started by the scope. This is required in order to disable the foreign keys.
|
||||
* We then begin a new transaction, this transaction will be committed or rolled back by the scope, like normal.
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
@@ -34,6 +36,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
private readonly IConfigurationEditorJsonSerializer _serializer;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly ITemplateContentParserService _templateContentParserService;
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IContentService _contentService;
|
||||
@@ -52,7 +56,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
IShortStringHelper shortStringHelper,
|
||||
IConfigurationEditorJsonSerializer serializer,
|
||||
IMediaService mediaService,
|
||||
IMediaTypeService mediaTypeService)
|
||||
IMediaTypeService mediaTypeService,
|
||||
ITemplateContentParserService templateContentParserService,
|
||||
ITemplateService templateService)
|
||||
{
|
||||
_dataValueEditorFactory = dataValueEditorFactory;
|
||||
_logger = logger;
|
||||
@@ -68,6 +74,44 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
_serializer = serializer;
|
||||
_mediaService = mediaService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_templateContentParserService = templateContentParserService;
|
||||
_templateService = templateService;
|
||||
}
|
||||
|
||||
[Obsolete("Please use new constructor, scheduled for removal in v15")]
|
||||
public PackageDataInstallation(
|
||||
IDataValueEditorFactory dataValueEditorFactory,
|
||||
ILogger<PackageDataInstallation> logger,
|
||||
IFileService fileService,
|
||||
ILocalizationService localizationService,
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IContentTypeService contentTypeService,
|
||||
IContentService contentService,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
IScopeProvider scopeProvider,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IConfigurationEditorJsonSerializer serializer,
|
||||
IMediaService mediaService,
|
||||
IMediaTypeService mediaTypeService)
|
||||
: this(
|
||||
dataValueEditorFactory,
|
||||
logger,
|
||||
fileService,
|
||||
localizationService,
|
||||
dataTypeService,
|
||||
entityService,
|
||||
contentTypeService,
|
||||
contentService,
|
||||
propertyEditors,
|
||||
scopeProvider,
|
||||
shortStringHelper,
|
||||
serializer,
|
||||
mediaService,
|
||||
mediaTypeService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ITemplateContentParserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ITemplateService>())
|
||||
{
|
||||
}
|
||||
|
||||
// Also remove factory service registration when this constructor is removed
|
||||
@@ -103,7 +147,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
shortStringHelper,
|
||||
serializer,
|
||||
mediaService,
|
||||
mediaTypeService)
|
||||
mediaTypeService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ITemplateContentParserService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ITemplateService>())
|
||||
{ }
|
||||
|
||||
#region Install/Uninstall
|
||||
@@ -1663,16 +1709,25 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
|
||||
#region Templates
|
||||
|
||||
[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
|
||||
public IEnumerable<ITemplate> ImportTemplate(XElement templateElement, int userId)
|
||||
=> ImportTemplates(new[] { templateElement }, userId);
|
||||
|
||||
public async Task<IEnumerable<ITemplate>> ImportTemplateAsync(XElement templateElement, int userId)
|
||||
=> ImportTemplatesAsync(new[] {templateElement}, userId).GetAwaiter().GetResult();
|
||||
|
||||
|
||||
[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
|
||||
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
=> ImportTemplatesAsync(templateElements, userId).GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="ITemplate"/>
|
||||
/// </summary>
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
public async Task<IReadOnlyList<ITemplate>> ImportTemplatesAsync(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
{
|
||||
var templates = new List<ITemplate>();
|
||||
|
||||
@@ -1682,20 +1737,19 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
{
|
||||
var dependencies = new List<string>();
|
||||
XElement elementCopy = tempElement;
|
||||
//Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.
|
||||
if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false &&
|
||||
templateElements.Any(x => (string?)x.Element("Alias") == (string?)elementCopy.Element("Master")))
|
||||
|
||||
//Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.'
|
||||
var masterTemplate = _templateContentParserService.MasterTemplateAlias(tempElement.Value);
|
||||
if (masterTemplate is not null && templateElements.Any(x => (string?)x.Element("Alias") == masterTemplate))
|
||||
{
|
||||
dependencies.Add((string)elementCopy.Element("Master")!);
|
||||
dependencies.Add(masterTemplate);
|
||||
}
|
||||
else if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false &&
|
||||
templateElements.Any(x =>
|
||||
(string?)x.Element("Alias") == (string?)elementCopy.Element("Master")) == false)
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored.",
|
||||
(string?)elementCopy.Element("Alias"),
|
||||
(string?)elementCopy.Element("Master"));
|
||||
masterTemplate);
|
||||
}
|
||||
|
||||
graph.AddItem(TopoGraph.CreateNode((string)elementCopy.Element("Alias")!, elementCopy, dependencies));
|
||||
@@ -1712,9 +1766,9 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
var design = templateElement.Element("Design")?.Value;
|
||||
XElement? masterElement = templateElement.Element("Master");
|
||||
|
||||
var existingTemplate = _fileService.GetTemplate(alias) as Template;
|
||||
var existingTemplate = await _templateService.GetAsync(alias) as Template;
|
||||
|
||||
Template? template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias);
|
||||
Template template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias);
|
||||
|
||||
// For new templates, use the serialized key if avaialble.
|
||||
if (existingTemplate == null && Guid.TryParse(templateElement.Element("Key")?.Value, out Guid key))
|
||||
@@ -1737,9 +1791,16 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
templates.Add(template);
|
||||
}
|
||||
|
||||
if (templates.Any())
|
||||
foreach (ITemplate template in templates)
|
||||
{
|
||||
_fileService.SaveTemplate(templates, userId);
|
||||
if (template.Id > 0)
|
||||
{
|
||||
await _templateService.UpdateAsync(template, Constants.Security.SuperUserKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _templateService.CreateAsync(template, Constants.Security.SuperUserKey);
|
||||
}
|
||||
}
|
||||
|
||||
return templates;
|
||||
|
||||
@@ -38,6 +38,7 @@ internal class RedirectUrlDto
|
||||
|
||||
[Column("url")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
|
||||
public string Url { get; set; } = null!;
|
||||
|
||||
[Column("culture")]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Cache.PropertyEditors;
|
||||
@@ -10,6 +11,7 @@ using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -66,14 +68,17 @@ public class RichTextPropertyEditor : DataEditor
|
||||
internal class RichTextPropertyValueEditor : BlockValuePropertyValueEditorBase<RichTextBlockValue, RichTextBlockLayoutItem>
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IHtmlSanitizer _htmlSanitizer;
|
||||
private readonly HtmlImageSourceParser _imageSourceParser;
|
||||
private readonly HtmlLocalLinkParser _localLinkParser;
|
||||
private readonly RichTextEditorPastedImages _pastedImages;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IBlockEditorElementTypeCache _elementTypeCache;
|
||||
private readonly IRichTextRequiredValidator _richTextRequiredValidator;
|
||||
private readonly ILogger<RichTextPropertyValueEditor> _logger;
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This is schedules for removal in v16.")]
|
||||
public RichTextPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
@@ -91,20 +96,64 @@ public class RichTextPropertyEditor : DataEditor
|
||||
IBlockEditorElementTypeCache elementTypeCache,
|
||||
IPropertyValidationService propertyValidationService,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection)
|
||||
: this(
|
||||
attribute,
|
||||
propertyEditors,
|
||||
dataTypeReadCache,
|
||||
logger,
|
||||
backOfficeSecurityAccessor,
|
||||
localizedTextService,
|
||||
shortStringHelper,
|
||||
imageSourceParser,
|
||||
localLinkParser,
|
||||
pastedImages,
|
||||
jsonSerializer,
|
||||
ioHelper,
|
||||
htmlSanitizer,
|
||||
elementTypeCache,
|
||||
propertyValidationService,
|
||||
dataValueReferenceFactoryCollection,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IRichTextRequiredValidator>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RichTextPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
IDataTypeConfigurationCache dataTypeReadCache,
|
||||
ILogger<RichTextPropertyValueEditor> logger,
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
HtmlImageSourceParser imageSourceParser,
|
||||
HtmlLocalLinkParser localLinkParser,
|
||||
RichTextEditorPastedImages pastedImages,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
IHtmlSanitizer htmlSanitizer,
|
||||
IBlockEditorElementTypeCache elementTypeCache,
|
||||
IPropertyValidationService propertyValidationService,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection,
|
||||
IRichTextRequiredValidator richTextRequiredValidator)
|
||||
: base(attribute, propertyEditors, dataTypeReadCache, localizedTextService, logger, shortStringHelper, jsonSerializer, ioHelper, dataValueReferenceFactoryCollection)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_localizedTextService = localizedTextService;
|
||||
_imageSourceParser = imageSourceParser;
|
||||
_localLinkParser = localLinkParser;
|
||||
_pastedImages = pastedImages;
|
||||
_htmlSanitizer = htmlSanitizer;
|
||||
_elementTypeCache = elementTypeCache;
|
||||
_richTextRequiredValidator = richTextRequiredValidator;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
|
||||
Validators.Add(new RichTextEditorBlockValidator(propertyValidationService, CreateBlockEditorValues(), elementTypeCache, jsonSerializer, logger));
|
||||
}
|
||||
|
||||
public override IValueRequiredValidator RequiredValidator => _richTextRequiredValidator;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConfigurationObject
|
||||
{
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
internal interface IRichTextRequiredValidator : IValueRequiredValidator
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
internal class RichTextRequiredValidator : RequiredValidator, IRichTextRequiredValidator
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ILogger<RichTextRequiredValidator> _logger;
|
||||
|
||||
public RichTextRequiredValidator(ILocalizedTextService textService, IJsonSerializer jsonSerializer, ILogger<RichTextRequiredValidator> logger) : base(textService)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType) => base.ValidateRequired(GetValue(value), valueType);
|
||||
|
||||
private object? GetValue(object? value)
|
||||
{
|
||||
if(RichTextPropertyEditorHelper.TryParseRichTextEditorValue(value, _jsonSerializer, _logger, out RichTextEditorValue? richTextEditorValue))
|
||||
{
|
||||
return richTextEditorValue?.Markup;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,15 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
|
||||
}
|
||||
|
||||
public PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType, query, null, skip, take, ignoreUserStartNodes);
|
||||
|
||||
public PagedModel<IEntitySlim> Search(
|
||||
UmbracoObjectTypes objectType,
|
||||
string query,
|
||||
Guid? parentId,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool ignoreUserStartNodes = false)
|
||||
{
|
||||
UmbracoEntityTypes entityType = objectType switch
|
||||
{
|
||||
@@ -37,7 +46,8 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
|
||||
pageSize,
|
||||
pageNumber,
|
||||
out var totalFound,
|
||||
ignoreUserStartNodes: ignoreUserStartNodes);
|
||||
ignoreUserStartNodes: ignoreUserStartNodes,
|
||||
searchFrom: parentId?.ToString());
|
||||
|
||||
Guid[] keys = searchResults.Select(
|
||||
result =>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
@@ -354,8 +357,16 @@ public class PackagingService : IPackagingService
|
||||
|
||||
if (!string.IsNullOrEmpty(packageManifest.Version))
|
||||
{
|
||||
// Always use package version from manifest
|
||||
installedPackage.Version = packageManifest.Version;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(installedPackage.Version) &&
|
||||
string.IsNullOrEmpty(installedPackage.PackageId) is false &&
|
||||
TryGetAssemblyInformationalVersion(installedPackage.PackageId, out string? version))
|
||||
{
|
||||
// Use version of the assembly with the same name as the package ID
|
||||
installedPackage.Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
// Return all packages with an ID or name in the package manifest or package migrations
|
||||
@@ -414,4 +425,20 @@ public class PackagingService : IPackagingService
|
||||
|
||||
return packageFile.CreateReadStream();
|
||||
}
|
||||
|
||||
private static bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version)
|
||||
{
|
||||
foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies)
|
||||
{
|
||||
AssemblyName assemblyName = assembly.GetName();
|
||||
if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) &&
|
||||
assembly.TryGetInformationalVersion(out version))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
version = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +351,12 @@ internal class Property : PublishedPropertyBase
|
||||
|
||||
public CacheValue For(string? culture, string? segment)
|
||||
{
|
||||
// As noted on IPropertyValue, null value means invariant
|
||||
// But as we need an actual string value to build a CompositeStringStringKey
|
||||
// We need to convert null to empty
|
||||
culture ??= string.Empty;
|
||||
segment ??= string.Empty;
|
||||
|
||||
if (culture == string.Empty && segment == string.Empty)
|
||||
{
|
||||
return this;
|
||||
|
||||
@@ -43,14 +43,14 @@ public class AspNetCoreHostingEnvironment : IHostingEnvironment
|
||||
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
|
||||
_urlProviderMode = _webRoutingSettings.CurrentValue.UrlProviderMode;
|
||||
|
||||
SetSiteName(hostingSettings.CurrentValue.SiteName);
|
||||
SetSiteNameAndDebugMode(hostingSettings.CurrentValue);
|
||||
|
||||
// We have to ensure that the OptionsMonitor is an actual options monitor since we have a hack
|
||||
// where we initially use an OptionsMonitorAdapter, which doesn't implement OnChange.
|
||||
// See summery of OptionsMonitorAdapter for more information.
|
||||
if (hostingSettings is OptionsMonitor<HostingSettings>)
|
||||
{
|
||||
hostingSettings.OnChange(settings => SetSiteName(settings.SiteName));
|
||||
hostingSettings.OnChange(settings => SetSiteNameAndDebugMode(settings));
|
||||
}
|
||||
|
||||
ApplicationPhysicalPath = webHostEnvironment.ContentRootPath;
|
||||
@@ -95,7 +95,7 @@ public class AspNetCoreHostingEnvironment : IHostingEnvironment
|
||||
_hostingSettings.CurrentValue.ApplicationVirtualPath?.EnsureStartsWith('/') ?? "/";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDebugMode => _hostingSettings.CurrentValue.Debug;
|
||||
public bool IsDebugMode { get; private set; }
|
||||
|
||||
public string LocalTempPath
|
||||
{
|
||||
@@ -188,8 +188,12 @@ public class AspNetCoreHostingEnvironment : IHostingEnvironment
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSiteName(string? siteName) =>
|
||||
SiteName = string.IsNullOrWhiteSpace(siteName)
|
||||
private void SetSiteNameAndDebugMode(HostingSettings hostingSettings)
|
||||
{
|
||||
SiteName = string.IsNullOrWhiteSpace(hostingSettings.SiteName)
|
||||
? _webHostEnvironment.ApplicationName
|
||||
: siteName;
|
||||
: hostingSettings.SiteName;
|
||||
|
||||
IsDebugMode = hostingSettings.Debug;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<!-- Take top-level depedendency on System.Text.RegularExpressions, because both Dazinator.Extensions.FileProviders and MiniProfiler.AspNetCore.Mvc depend on a vulnerable version -->
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
<!-- Both OpenIddict.AspNetCore, Npoco.SqlServer and Microsoft.EntityFrameworkCore.SqlServer bring in a vulnerable version of Microsoft.IdentityModel.JsonWebTokens -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Submodule src/Umbraco.Web.UI.Client updated: bb6abdc884...b2c598f6ef
@@ -2,7 +2,10 @@ import {
|
||||
UmbBundleExtensionInitializer,
|
||||
UmbServerExtensionRegistrator
|
||||
} from "@umbraco-cms/backoffice/extension-api";
|
||||
import { umbExtensionsRegistry } from "@umbraco-cms/backoffice/extension-registry";
|
||||
import {
|
||||
UmbAppEntryPointExtensionInitializer,
|
||||
umbExtensionsRegistry
|
||||
} from "@umbraco-cms/backoffice/extension-registry";
|
||||
import type { UmbElement } from "@umbraco-cms/backoffice/element-api";
|
||||
import { UmbControllerBase } from "@umbraco-cms/backoffice/class-api";
|
||||
import { UUIIconRegistryEssential } from "@umbraco-cms/backoffice/external/uui";
|
||||
@@ -21,6 +24,7 @@ export class UmbSlimBackofficeController extends UmbControllerBase {
|
||||
constructor(host: UmbElement) {
|
||||
super(host);
|
||||
new UmbBundleExtensionInitializer(host, umbExtensionsRegistry);
|
||||
new UmbAppEntryPointExtensionInitializer(host, umbExtensionsRegistry);
|
||||
new UmbServerExtensionRegistrator(host, umbExtensionsRegistry).registerPublicExtensions();
|
||||
|
||||
this.#uuiIconRegistry.attach(host);
|
||||
|
||||
@@ -3,7 +3,9 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.CreateUmbracoBuilder()
|
||||
.AddBackOffice()
|
||||
.AddWebsite()
|
||||
#if UseDeliveryApi
|
||||
.AddDeliveryApi()
|
||||
#endif
|
||||
.AddComposers()
|
||||
.Build();
|
||||
|
||||
@@ -23,6 +25,9 @@ app.UseUmbraco()
|
||||
})
|
||||
.WithEndpoints(u =>
|
||||
{
|
||||
/*#if (UmbracoRelease = 'LTS')
|
||||
u.UseInstallerEndpoints();
|
||||
#endif */
|
||||
u.UseBackOfficeEndpoints();
|
||||
u.UseWebsiteEndpoints();
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<Content Include="UmbracoPackage\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoPackageRcl\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoProject\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoDockerCompose\**" Exclude="bin;obj"/>
|
||||
<Content Include="..\src\Umbraco.Web.UI\Views\Partials\blocklist\**">
|
||||
<Link>UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<PackagePath>UmbracoProject\Views\Partials\blocklist</PackagePath>
|
||||
@@ -47,7 +48,7 @@
|
||||
</_TemplateJsonFiles>
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(_TemplateJsonFiles)" DestinationFiles="%(DestinationFile)" />
|
||||
<JsonPathUpdateValue JsonFile="%(_TemplateJsonFiles.DestinationFile)" Path="$.symbols.UmbracoVersion.defaultValue" Value=""$(PackageVersion)"" />
|
||||
<JsonPathUpdateValue JsonFile="%(_TemplateJsonFiles.DestinationFile)" Path="$.symbols.FinalVersion.parameters.cases.[0].value" Value=""$(PackageVersion)"" />
|
||||
<ItemGroup>
|
||||
<_PackageFiles Include="%(_TemplateJsonFiles.DestinationFile)">
|
||||
<PackagePath>%(_TemplateJsonFiles.RelativeDir)</PackagePath>
|
||||
|
||||
1
templates/UmbracoDockerCompose/.env
Normal file
1
templates/UmbracoDockerCompose/.env
Normal file
@@ -0,0 +1 @@
|
||||
DB_PASSWORD=Password1234
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/dotnetcli.host.json",
|
||||
"symbolInfo": {
|
||||
"ProjectName": {
|
||||
"longName": "ProjectName",
|
||||
"shortName": "P"
|
||||
},
|
||||
"DatabasePassword": {
|
||||
"longName": "DatabasePassword",
|
||||
"shortName": "dbpw"
|
||||
},
|
||||
"Port":
|
||||
{
|
||||
"longName": "Port",
|
||||
"shortName": "p"
|
||||
}
|
||||
},
|
||||
"usageExamples": [
|
||||
"dotnet new umbraco-compose -P MyProject",
|
||||
"dotnet new umbraco-compose --ProjectName MyProject",
|
||||
"dotnet new umbraco-compose -P -MyProject -dbpw MyStr0ngP@ssword",
|
||||
"dotnet new umbraco-compose -P -MyProject --DatabasePassword MyStr0ngP@ssword"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/ide.host.json",
|
||||
"order": 0,
|
||||
"icon": "../../icon.png",
|
||||
"description": {
|
||||
"id": "UmbracoDockerCompose",
|
||||
"text": "Umbraco Docker Compose - Docker compose for Umbraco CMS and associated database"
|
||||
},
|
||||
"symbolInfo": [
|
||||
{
|
||||
"id": "ProjectName",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "DatabasePassword",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "Port",
|
||||
"isVisible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/template.json",
|
||||
"author": "Umbraco HQ",
|
||||
"classifications": [
|
||||
"Web",
|
||||
"CMS",
|
||||
"Umbraco"
|
||||
],
|
||||
"name": "Umbraco Docker Compose",
|
||||
"description": "Creates the prerequisites for developing Umbraco in Docker containers",
|
||||
"groupIdentity": "Umbraco.Templates.UmbracoDockerCompose",
|
||||
"identity": "Umbraco.Templates.UmbracoDockerCompose",
|
||||
"shortName": "umbraco-compose",
|
||||
"tags": {
|
||||
"type": "item"
|
||||
},
|
||||
"symbols": {
|
||||
"ProjectName": {
|
||||
"type": "parameter",
|
||||
"description": "The name of the project the Docker Compose file will be created for",
|
||||
"datatype": "string",
|
||||
"replaces": "UmbracoProject",
|
||||
"isRequired": true
|
||||
},
|
||||
"DatabasePassword": {
|
||||
"type": "parameter",
|
||||
"description": "The password to the database, will be stored in .env file",
|
||||
"datatype": "string",
|
||||
"replaces": "Password1234",
|
||||
"defaultValue": "Password1234"
|
||||
},
|
||||
"Port": {
|
||||
"type": "parameter",
|
||||
"description": "The port forward on the docker container, this is the port you use to access the site",
|
||||
"datatype": "string",
|
||||
"replaces": "TEMPLATE_PORT",
|
||||
"defaultValue": "44372"
|
||||
},
|
||||
"ImageName": {
|
||||
"type": "generated",
|
||||
"generator": "casing",
|
||||
"parameters": {
|
||||
"source": "ProjectName",
|
||||
"toLower": true
|
||||
},
|
||||
"replaces": "umbraco_image"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
templates/UmbracoDockerCompose/Database/Dockerfile
Normal file
21
templates/UmbracoDockerCompose/Database/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM mcr.microsoft.com/azure-sql-edge:latest
|
||||
|
||||
ENV ACCEPT_EULA=Y
|
||||
|
||||
USER root
|
||||
|
||||
RUN mkdir /var/opt/sqlserver
|
||||
|
||||
RUN chown mssql /var/opt/sqlserver
|
||||
|
||||
ENV MSSQL_BACKUP_DIR="/var/opt/mssql"
|
||||
ENV MSSQL_DATA_DIR="/var/opt/mssql/data"
|
||||
ENV MSSQL_LOG_DIR="/var/opt/mssql/log"
|
||||
|
||||
EXPOSE 1433/tcp
|
||||
COPY setup.sql /
|
||||
COPY startup.sh /
|
||||
COPY healthcheck.sh /
|
||||
|
||||
ENTRYPOINT [ "/bin/bash", "startup.sh" ]
|
||||
CMD [ "/opt/mssql/bin/sqlservr" ]
|
||||
15
templates/UmbracoDockerCompose/Database/healthcheck.sh
Normal file
15
templates/UmbracoDockerCompose/Database/healthcheck.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
value="$(/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -d master -Q "SELECT state_desc FROM sys.databases WHERE name = 'umbracoDb'" | awk 'NR==3')"
|
||||
|
||||
# This checks for any non-zero length string, and $value will be empty when the database does not exist.
|
||||
if [ -n "$value" ]
|
||||
then
|
||||
echo "ONLINE"
|
||||
return 0 # With docker 0 = success
|
||||
else
|
||||
echo "OFFLINE"
|
||||
return 1 # And 1 = unhealthy
|
||||
fi
|
||||
|
||||
# This is useful for debugging
|
||||
# echo "Value is:"
|
||||
# echo "$value"
|
||||
10
templates/UmbracoDockerCompose/Database/setup.sql
Normal file
10
templates/UmbracoDockerCompose/Database/setup.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
USE [master]
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = 'UmbracoDb')
|
||||
BEGIN
|
||||
CREATE DATABASE [umbracoDb]
|
||||
END;
|
||||
GO
|
||||
|
||||
USE UmbracoDb;
|
||||
23
templates/UmbracoDockerCompose/Database/startup.sh
Normal file
23
templates/UmbracoDockerCompose/Database/startup.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Taken from: https://github.com/CarlSargunar/Umbraco-Docker-Workshop
|
||||
if [ "$1" = '/opt/mssql/bin/sqlservr' ]; then
|
||||
# If this is the container's first run, initialize the application database
|
||||
if [ ! -f /tmp/app-initialized ]; then
|
||||
# Initialize the application database asynchronously in a background process. This allows a) the SQL Server process to be the main process in the container, which allows graceful shutdown and other goodies, and b) us to only start the SQL Server process once, as opposed to starting, stopping, then starting it again.
|
||||
function initialize_app_database() {
|
||||
# Wait a bit for SQL Server to start. SQL Server's process doesn't provide a clever way to check if it's up or not, and it needs to be up before we can import the application database
|
||||
sleep 15s
|
||||
|
||||
#run the setup script to create the DB and the schema in the DB
|
||||
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -d master -i setup.sql
|
||||
|
||||
# Note that the container has been initialized so future starts won't wipe changes to the data
|
||||
touch /tmp/app-initialized
|
||||
}
|
||||
initialize_app_database &
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
102
templates/UmbracoDockerCompose/docker-compose.yml
Normal file
102
templates/UmbracoDockerCompose/docker-compose.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
services:
|
||||
umb_database:
|
||||
container_name: umbraco_image_database
|
||||
build:
|
||||
context: ./Database
|
||||
environment:
|
||||
SA_PASSWORD: ${DB_PASSWORD}
|
||||
MSSQL_SA_PASSWORD: ${DB_PASSWORD}
|
||||
ports:
|
||||
- "1433:1433"
|
||||
- "1434:1434"
|
||||
volumes:
|
||||
- umb_database:/var/opt/mssql
|
||||
networks:
|
||||
- umbnet
|
||||
healthcheck:
|
||||
# This healthcheck is to make sure that the database is up and running before the umbraco container starts.
|
||||
# It works by querying the database for the state of the umbracoDb database, ensuring it exists.
|
||||
test: ./healthcheck.sh
|
||||
interval: 5m
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s # Bootstrap duration, for this duration failures does not count towards max retries.
|
||||
start_interval: 5s # How long after the health check has started to run the healthcheck again.
|
||||
|
||||
umbraco_image:
|
||||
image: umbraco_image
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ConnectionStrings__umbracoDbDSN=Server=umb_database;Database=umbracoDb;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=true;
|
||||
- ConnectionStrings__umbracoDbDSN_ProviderName=Microsoft.Data.SqlClient
|
||||
volumes:
|
||||
- umb_media:/app/wwwroot/media
|
||||
- umb_scripts:/app/wwwroot/scripts
|
||||
- umb_styles:/app/wwwroot/css
|
||||
- umb_logs:/app/umbraco/Logs
|
||||
- umb_views:/app/Views
|
||||
- umb_data:/app/umbraco
|
||||
- umb_models:/app/umbraco/models
|
||||
build:
|
||||
context: .
|
||||
dockerfile: UmbracoProject/Dockerfile
|
||||
args:
|
||||
- BUILD_CONFIGURATION=Debug
|
||||
|
||||
depends_on:
|
||||
umb_database:
|
||||
condition: service_healthy
|
||||
restart: always
|
||||
ports:
|
||||
- "TEMPLATE_PORT:8080"
|
||||
networks:
|
||||
- umbnet
|
||||
develop:
|
||||
# This allows you to run docker compose watch, after doing so the container will rebuild when the models are changed.
|
||||
# Once a restart only feature is implemented (https://github.com/docker/compose/issues/11446)
|
||||
# It would be really nice to add a restart only watch to \Views, since the file watchers for recompilation of Razor views does not work with docker.
|
||||
watch:
|
||||
- path: ./UmbracoProject/umbraco/models
|
||||
action: rebuild
|
||||
|
||||
# These volumes are all made as bind mounts, meaning that they are bound to the host machine's file system.
|
||||
# This is to better facilitate local development in the IDE, so the views, models, etc... are available in the IDE.
|
||||
# This can be changed by removing the driver and driver_opts from the volumes.
|
||||
volumes:
|
||||
umb_media:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
device: ./UmbracoProject/wwwroot/media
|
||||
o: bind
|
||||
umb_scripts:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
device: ./UmbracoProject/wwwroot/scripts
|
||||
o: bind
|
||||
umb_styles:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
device: ./UmbracoProject/wwwroot/css
|
||||
o: bind
|
||||
umb_logs:
|
||||
umb_views:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
device: ./UmbracoProject/Views
|
||||
o: bind
|
||||
umb_data:
|
||||
umb_models:
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
device: ./UmbracoProject/umbraco/models
|
||||
o: bind
|
||||
umb_database:
|
||||
|
||||
networks:
|
||||
umbnet:
|
||||
driver: bridge
|
||||
25
templates/UmbracoProject/.dockerignore
Normal file
25
templates/UmbracoProject/.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
@@ -8,12 +8,25 @@
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"longName": "version",
|
||||
"shortName": "v"
|
||||
"shortName": "v",
|
||||
"isHidden": true
|
||||
},
|
||||
"UmbracoRelease": {
|
||||
"longName": "release",
|
||||
"shortName": "r"
|
||||
},
|
||||
"UseHttpsRedirect": {
|
||||
"longName": "use-https-redirect",
|
||||
"shortName": ""
|
||||
},
|
||||
"UseDeliveryApi": {
|
||||
"longName": "use-delivery-api",
|
||||
"shortName": "da"
|
||||
},
|
||||
"Docker": {
|
||||
"longName": "add-docker",
|
||||
"shortName": ""
|
||||
},
|
||||
"SkipRestore": {
|
||||
"longName": "no-restore",
|
||||
"shortName": ""
|
||||
@@ -58,6 +71,18 @@
|
||||
"longName": "PackageTestSiteName",
|
||||
"shortName": "p",
|
||||
"isHidden": true
|
||||
},
|
||||
"ModelsBuilderMode": {
|
||||
"longName": "models-mode",
|
||||
"shortName": "mm"
|
||||
},
|
||||
"StarterKit": {
|
||||
"longName": "starter-kit",
|
||||
"shortName": "sk"
|
||||
},
|
||||
"DevelopmentMode": {
|
||||
"longName": "development-mode",
|
||||
"shortName": "dm"
|
||||
}
|
||||
},
|
||||
"usageExamples": [
|
||||
|
||||
@@ -9,11 +9,22 @@
|
||||
"symbolInfo": [
|
||||
{
|
||||
"id": "UmbracoVersion",
|
||||
"isVisible": true
|
||||
"isVisible": false
|
||||
},
|
||||
{
|
||||
"id": "UseHttpsRedirect",
|
||||
"isVisible": true,
|
||||
"persistenceScope": "templateGroup",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"id": "UseDeliveryApi",
|
||||
"isVisible": true,
|
||||
"persistenceScope": "templateGroup"
|
||||
},
|
||||
{
|
||||
"id": "ModelsBuilderMode",
|
||||
"isVisible": true,
|
||||
"persistenceScope": "templateGroup"
|
||||
},
|
||||
{
|
||||
@@ -54,6 +65,23 @@
|
||||
{
|
||||
"id": "NoNodesViewPath",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "Docker",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "StarterKit",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "UmbracoRelease",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "DevelopmentMode",
|
||||
"isVisible": true,
|
||||
"defaultValue": "IDEDevelopment"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/template.json",
|
||||
"symbols": {
|
||||
"StarterKit": {
|
||||
"displayName": "Starter kit",
|
||||
"type": "parameter",
|
||||
"datatype": "choice",
|
||||
"description": "Choose a starter kit to install.",
|
||||
"defaultValue": "None",
|
||||
"replaces": "STARTER_KIT_NAME",
|
||||
// The choice here should be the name of the starter kit package, since it will be used directly for package reference.
|
||||
"choices": [
|
||||
{
|
||||
"choice": "None",
|
||||
"description": "No starter kit."
|
||||
},
|
||||
{
|
||||
"choice": "Umbraco.TheStarterKit",
|
||||
"description": "The Umbraco starter kit.",
|
||||
"displayName": "The Starter Kit"
|
||||
}
|
||||
]
|
||||
},
|
||||
// Used to determine the version of the starter kit to install.
|
||||
// there should be cases for Latest, LTS and Custom for every starterkit added above.
|
||||
// This has the benefit that all maintenance of starter kits in template can be done from this file.
|
||||
"StarterKitVersion": {
|
||||
"type": "generated",
|
||||
"generator": "switch",
|
||||
"replaces": "STARTER_KIT_VERSION",
|
||||
"parameters": {
|
||||
"evaluator": "C++",
|
||||
"datatype": "string",
|
||||
"cases": [
|
||||
{
|
||||
"condition": "(StarterKit == 'Umbraco.TheStarterKit' && (UmbracoRelease == 'Latest' || UmbracoRelease == 'Custom'))",
|
||||
"value": "14.0.0"
|
||||
},
|
||||
{
|
||||
"condition": "(StarterKit == 'Umbraco.TheStarterKit' && UmbracoRelease == 'LTS')",
|
||||
"value": "13.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
"sourceName": "UmbracoProject",
|
||||
"defaultName": "UmbracoProject1",
|
||||
"preferNameDirectory": true,
|
||||
"additionalConfigFiles": [ "starterkits.template.json"],
|
||||
"sources": [
|
||||
{
|
||||
"modifiers": [
|
||||
@@ -26,6 +27,13 @@
|
||||
"exclude": [
|
||||
".gitignore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"condition": "(!Docker)",
|
||||
"exclude": [
|
||||
"Dockerfile",
|
||||
".dockerignore"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -46,13 +54,72 @@
|
||||
"defaultValue": "net8.0",
|
||||
"replaces": "net8.0"
|
||||
},
|
||||
"UmbracoRelease": {
|
||||
"displayName": "Umbraco Version",
|
||||
"description": "The Umbraco release to use, either latest or latest long term supported",
|
||||
"type": "parameter",
|
||||
"datatype": "choice",
|
||||
"defaultValue": "Latest",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "Latest",
|
||||
"description": "The latest umbraco release"
|
||||
},
|
||||
{
|
||||
"choice": "LTS",
|
||||
"description": "The most recent long term supported version",
|
||||
"displayName": "Long Term Supported"
|
||||
}
|
||||
],
|
||||
"isRequired": false
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"displayName": "Umbraco version",
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference.",
|
||||
"displayName": "Custom Version",
|
||||
"description": "The selected custom version of Umbraco, this is obsoleted, and will be removed in a future version of the template.",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "*",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
"defaultValue": "null",
|
||||
"replaces": "CUSTOM_VERSION",
|
||||
"isRequired": false
|
||||
},
|
||||
"FinalVersion" : {
|
||||
"type": "generated",
|
||||
"generator": "switch",
|
||||
"datatype": "text",
|
||||
"description": "The calculated version of Umbraco to use",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE",
|
||||
"parameters": {
|
||||
"evaluator": "C++",
|
||||
"datatype": "text",
|
||||
"cases": [
|
||||
{
|
||||
"condition": "(UmbracoRelease == 'Latest')",
|
||||
"value": "*"
|
||||
},
|
||||
{
|
||||
"condition": "(UmbracoRelease == 'LTS')",
|
||||
"value": "13.5.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"DotnetVersion":
|
||||
{
|
||||
"type": "generated",
|
||||
"generator": "switch",
|
||||
"datatype": "text",
|
||||
"description": "Not relevant at the moment, but if we need to change the dotnet version based on the Umbraco version, we can do it here",
|
||||
"replaces": "DOTNET_VERSION_FROM_TEMPLATE",
|
||||
"parameters": {
|
||||
"evaluator": "C++",
|
||||
"datatype": "text",
|
||||
"cases": [
|
||||
{
|
||||
"condition": "(true)",
|
||||
"value": "net8.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"UseHttpsRedirect": {
|
||||
"displayName": "Use HTTPS redirect",
|
||||
@@ -61,6 +128,20 @@
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"UseDeliveryApi": {
|
||||
"displayName": "Use Delivery API",
|
||||
"description": "Enables the Delivery API",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"Docker": {
|
||||
"displayName": "Add Docker file",
|
||||
"description": "Adds a docker file to the project.",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"SkipRestore": {
|
||||
"displayName": "Skip restore",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
@@ -244,6 +325,58 @@
|
||||
"defaultValue": "",
|
||||
"replaces": "PACKAGE_PROJECT_NAME_FROM_TEMPLATE"
|
||||
},
|
||||
"DevelopmentMode": {
|
||||
"type": "parameter",
|
||||
"displayName": "Development mode",
|
||||
"datatype": "choice",
|
||||
"description": "Choose the development mode to use for the project.",
|
||||
"defaultValue": "BackofficeDevelopment",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "BackofficeDevelopment",
|
||||
"description": "Enables backoffice development, allowing you to develop from within the backoffice, this is the default behaviour.",
|
||||
"displayName": "Backoffice Development"
|
||||
},
|
||||
{
|
||||
"choice": "IDEDevelopment",
|
||||
"description": "Configures appsettings.Development.json to Development runtime mode and SourceCodeAuto models builder mode, and configures appsettings.json to Production runtime mode, Nothing models builder mode, and enables UseHttps",
|
||||
"displayName": "IDE Development"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ModelsBuilderMode": {
|
||||
"type": "parameter",
|
||||
"displayName": "Models builder mode",
|
||||
"datatype": "choice",
|
||||
"description": "Choose the models builder mode to use for the project. When development mode is set to IDEDevelopment this only changes the models builder mode appsetttings.development.json",
|
||||
"defaultValue": "Default",
|
||||
"replaces": "MODELS_MODE",
|
||||
"choices": [
|
||||
{
|
||||
"choice": "Default",
|
||||
"description": "Let DevelopmentMode determine the models builder mode."
|
||||
},
|
||||
{
|
||||
"choice": "InMemoryAuto",
|
||||
"description": "Generate models in memory, automatically updating when a content type change, this means no need for app rebuild, however models are only available in views.",
|
||||
"displayName": "In Memory Auto"
|
||||
},
|
||||
{
|
||||
"choice": "SourceCodeManual",
|
||||
"description": "Generate models as source code, only updating when requested manually, this means a interaction and rebuild is required when content type(s) change, however models are available in code.",
|
||||
"displayName": "Source Code Manual"
|
||||
},
|
||||
{
|
||||
"choice": "SourceCodeAuto",
|
||||
"description": "Generate models as source code, automatically updating when a content type change, this means a rebuild is required when content type(s) change, however models are available in code.",
|
||||
"displayName": "Source Code Auto"
|
||||
},
|
||||
{
|
||||
"choice": "Nothing",
|
||||
"description": "No models are generated, this is recommended for production assuming generated models are used for development."
|
||||
}
|
||||
]
|
||||
},
|
||||
"Namespace": {
|
||||
"type": "derived",
|
||||
"valueSource": "name",
|
||||
|
||||
33
templates/UmbracoProject/Dockerfile
Normal file
33
templates/UmbracoProject/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["UmbracoProject/UmbracoProject.csproj", "UmbracoProject/"]
|
||||
RUN dotnet restore "UmbracoProject/UmbracoProject.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/UmbracoProject"
|
||||
RUN dotnet build "UmbracoProject.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "UmbracoProject.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
# We need to make sure that the user running the app has write access to the umbraco folder, in order to write logs and other files.
|
||||
# Since these are volumes they are created as root by the docker daemon.
|
||||
USER root
|
||||
RUN mkdir umbraco
|
||||
RUN mkdir umbraco/Logs
|
||||
RUN chown $APP_UID umbraco --recursive
|
||||
#if (UmbracoRelease = 'LTS')
|
||||
RUN chown $APP_UID wwwroot/umbraco --recursive
|
||||
#endif
|
||||
USER $APP_UID
|
||||
ENTRYPOINT ["dotnet", "UmbracoProject.dll"]
|
||||
@@ -1,13 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>DOTNET_VERSION_FROM_TEMPLATE</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Umbraco.Cms.Web.UI</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!--#if (UmbracoVersion != "null")
|
||||
<PackageReference Include="Umbraco.Cms" Version="CUSTOM_VERSION" />
|
||||
#else
|
||||
<PackageReference Include="Umbraco.Cms" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
#endif-->
|
||||
<!--#if (StarterKit != "None") -->
|
||||
<PackageReference Include="STARTER_KIT_NAME" Version="STARTER_KIT_VERSION"/>
|
||||
<!--#endif -->
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -21,11 +28,13 @@
|
||||
<CopyRazorGenerateFilesToPublishDirectory>true</CopyRazorGenerateFilesToPublishDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--#if (ModelsBuilderMode == "InMemoryAuto" || (DevelopmentMode == "BackofficeDevelopment" && ModelsBuilderMode == "Default")) -->
|
||||
<PropertyGroup>
|
||||
<!-- Remove RazorCompileOnBuild and RazorCompileOnPublish when not using ModelsMode InMemoryAuto -->
|
||||
<RazorCompileOnBuild>false</RazorCompileOnBuild>
|
||||
<RazorCompileOnPublish>false</RazorCompileOnPublish>
|
||||
</PropertyGroup>
|
||||
<!--#endif -->
|
||||
|
||||
<Import Project="..\PACKAGE_PROJECT_NAME_FROM_TEMPLATE\buildTransitive\PACKAGE_PROJECT_NAME_FROM_TEMPLATE.targets" Condition="'$(PackageProjectName)' != ''" />
|
||||
<ItemGroup Condition="'$(PackageProjectName)' != ''">
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
//#endif
|
||||
"Umbraco": {
|
||||
"CMS": {
|
||||
//#if (UseHttpsRedirect || DevelopmentMode == "IDEDevelopment")
|
||||
"Global": {
|
||||
"UseHttps": false
|
||||
},
|
||||
//#endif
|
||||
//#if (UsingUnattenedInstall)
|
||||
"Unattended": {
|
||||
"InstallUnattended": true,
|
||||
@@ -36,12 +41,22 @@
|
||||
"Content": {
|
||||
"MacroErrors": "Throw"
|
||||
},
|
||||
//#if (DevelopmentMode == "IDEDevelopment")
|
||||
"Runtime": {
|
||||
"Mode": "Development"
|
||||
},
|
||||
//#if (ModelsBuilderMode == "Default")
|
||||
"ModelsBuilder": {
|
||||
"ModelsMode": "SourceCodeAuto"
|
||||
},
|
||||
////#else
|
||||
//"ModelsBuilder": {
|
||||
// "ModelsMode": "MODELS_MODE"
|
||||
//},
|
||||
//#endif
|
||||
//#endif
|
||||
"Hosting": {
|
||||
"Debug": true
|
||||
},
|
||||
"RuntimeMinification": {
|
||||
"UseInMemoryCache": true,
|
||||
"CacheBuster": "Timestamp"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"CMS": {
|
||||
"Global": {
|
||||
"Id": "TELEMETRYID_FROM_TEMPLATE",
|
||||
//#if (UseHttpsRedirect)
|
||||
//#if (UseHttpsRedirect || DevelopmentMode == "IDEDevelopment")
|
||||
"UseHttps": true,
|
||||
//#endif
|
||||
//#if (HasNoNodesViewPath)
|
||||
@@ -37,6 +37,24 @@
|
||||
"Unattended": {
|
||||
"UpgradeUnattended": true
|
||||
},
|
||||
//#if (UseDeliveryApi)
|
||||
"DeliveryApi": {
|
||||
"Enabled": true
|
||||
},
|
||||
//#endif
|
||||
//#if (ModelsBuilderMode != "Default" && DevelopmentMode == "BackOfficeDevelopment")
|
||||
"ModelsBuilder": {
|
||||
"ModelsMode": "MODELS_MODE"
|
||||
},
|
||||
//#endif
|
||||
//#if (DevelopmentMode == "IDEDevelopment")
|
||||
"Runtime": {
|
||||
"Mode": "Production"
|
||||
},
|
||||
"ModelsBuilder": {
|
||||
"ModelsMode": "Nothing"
|
||||
},
|
||||
//#endif
|
||||
"Security": {
|
||||
"AllowConcurrentLogins": false
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Packages.props, $(MSBuildThisFileDirectory)..))" />
|
||||
<ItemGroup>
|
||||
<!-- Microsoft packages -->
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageVersion Include="System.Data.DataSetExtensions" Version="4.5.0" />
|
||||
@@ -17,7 +17,7 @@
|
||||
<!-- Third-party packages -->
|
||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
||||
<PackageVersion Include="AutoFixture.NUnit3" Version="4.18.1" />
|
||||
<PackageVersion Include="Bogus" Version="35.6.0" />
|
||||
<PackageVersion Include="Bogus" Version="35.6.1" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="NUnit" Version="3.14.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" PrivateAssets="all" />
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Ogv.ogv
Normal file
BIN
tests/Umbraco.Tests.AcceptanceTest/fixtures/mediaLibrary/Ogv.ogv
Normal file
Binary file not shown.
Binary file not shown.
793
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
793
tests/Umbraco.Tests.AcceptanceTest/package-lock.json
generated
@@ -7,24 +7,18 @@
|
||||
"name": "acceptancetest",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.14",
|
||||
"@umbraco/playwright-testhelpers": "^2.0.0-beta.73",
|
||||
"@umbraco/json-models-builders": "^2.0.20",
|
||||
"@umbraco/playwright-testhelpers": "^2.0.0-beta.84",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"faker": "^4.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"xhr2": "^0.2.1"
|
||||
"node-fetch": "^2.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.43",
|
||||
"@types/node": "^20.9.0",
|
||||
"del": "^6.0.0",
|
||||
"ncp": "^2.0.0",
|
||||
"prompt": "^1.2.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.3",
|
||||
"wait-on": "^7.2.0"
|
||||
"typescript": "^4.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
@@ -36,190 +30,55 @@
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
"run-parallel": "^1.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.stat": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.walk": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
"fastq": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz",
|
||||
"integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==",
|
||||
"version": "1.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
|
||||
"integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.43.1"
|
||||
"playwright": "1.46.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/address": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
|
||||
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/formula": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.10.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz",
|
||||
"integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==",
|
||||
"version": "20.14.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz",
|
||||
"integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/json-models-builders": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.14.tgz",
|
||||
"integrity": "sha512-fP6hVSSph1iFQ1c65UH80AM6QK3r1CzuIiYOvZh+QOoVzpVFtH1VCHL3J2k8AwaHWLVAEopcvtvH5kkl7Luqww==",
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.20.tgz",
|
||||
"integrity": "sha512-LmTtklne1HlhMr1nALA+P5FrjIC9jL3A6Pcxj4dy+IPnTgnU2vMYaQIfE8wwz5Z5fZ5AAhWx/Zpdi8xCTbVSuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/playwright-testhelpers": {
|
||||
"version": "2.0.0-beta.73",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.73.tgz",
|
||||
"integrity": "sha512-CCURatZa7Ipui9ZTqdZmkpx89Sr5AJLoXogniq6mv84mSVGeCQFYzHvw1op2UE8nkKY5/wyqfrCihjrbW5v8lw==",
|
||||
"version": "2.0.0-beta.84",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.84.tgz",
|
||||
"integrity": "sha512-vH13Lg48knTkkLVTwhMXUKTOdjtmixFj0wF5Qhgb++13u4AVDb+oW+TbFwTjSYaLeNMraq5Uhwmto/XuJPs2Rw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "2.0.14",
|
||||
"@umbraco/json-models-builders": "2.0.20",
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/aggregate-error": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"clean-stack": "^2.0.0",
|
||||
"indent-string": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/array-union": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
|
||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
@@ -228,15 +87,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
@@ -246,23 +96,6 @@
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cycle": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
|
||||
@@ -272,57 +105,15 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/del": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz",
|
||||
"integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"globby": "^11.0.1",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"is-glob": "^4.0.1",
|
||||
"is-path-cwd": "^2.2.0",
|
||||
"is-path-inside": "^3.0.2",
|
||||
"p-map": "^4.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/eyes": {
|
||||
@@ -334,343 +125,24 @@
|
||||
"node": "> 0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/faker": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz",
|
||||
"integrity": "sha512-ILKg69P6y/D8/wSmDXw35Ly0re8QzQ8pMfBCflsGiZG2ZjMUNLYNexA6lz5pkmJlepVdsiDFUxYAzPQ9/+iGLA=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.2",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
|
||||
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/globby": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
|
||||
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-union": "^2.1.0",
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.2.9",
|
||||
"ignore": "^5.2.0",
|
||||
"merge2": "^1.4.1",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
||||
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/indent-string": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-path-cwd": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
|
||||
"integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/is-path-inside": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
|
||||
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.11.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz",
|
||||
"integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0",
|
||||
"@hapi/topo": "^5.0.0",
|
||||
"@sideway/address": "^4.1.3",
|
||||
"@sideway/formula": "^3.0.1",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mute-stream": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"ncp": "bin/ncp"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
@@ -690,88 +162,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-map": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"aggregate-error": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.43.1.tgz",
|
||||
"integrity": "sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==",
|
||||
"version": "1.46.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
|
||||
"integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.43.1"
|
||||
"playwright-core": "1.46.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.43.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.43.1.tgz",
|
||||
"integrity": "sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==",
|
||||
"version": "1.46.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
|
||||
"integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/prompt": {
|
||||
@@ -790,32 +208,6 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/read": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
|
||||
@@ -828,16 +220,6 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/revalidator": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz",
|
||||
@@ -847,62 +229,6 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
@@ -912,27 +238,15 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
@@ -954,25 +268,6 @@
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
|
||||
"integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axios": "^1.6.1",
|
||||
"joi": "^17.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.8",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"wait-on": "bin/wait-on"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@@ -1012,20 +307,6 @@
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/xhr2": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz",
|
||||
"integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,15 @@
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.43",
|
||||
"@types/node": "^20.9.0",
|
||||
"del": "^6.0.0",
|
||||
"ncp": "^2.0.0",
|
||||
"prompt": "^1.2.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.3",
|
||||
"wait-on": "^7.2.0"
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.14",
|
||||
"@umbraco/playwright-testhelpers": "^2.0.0-beta.73",
|
||||
"@umbraco/json-models-builders": "^2.0.20",
|
||||
"@umbraco/playwright-testhelpers": "^2.0.0-beta.84",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"faker": "^4.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"xhr2": "^0.2.1"
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ test.describe('DataType tests', () => {
|
||||
const dataTypeName = 'TestDataType';
|
||||
const folderName = 'TestDataTypeFolder';
|
||||
const editorAlias = 'Umbraco.DateTime';
|
||||
const editorUiAlias = 'Umb.PropertyEditorUi.DatePicker';
|
||||
const dataTypeData = [
|
||||
{
|
||||
"alias": "tester",
|
||||
@@ -29,7 +30,7 @@ test.describe('DataType tests', () => {
|
||||
|
||||
test('can create dataType', async ({umbracoApi}) => {
|
||||
// Act
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, dataTypeData);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
|
||||
// Assert
|
||||
expect(umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
|
||||
@@ -37,7 +38,7 @@ test.describe('DataType tests', () => {
|
||||
|
||||
test('can update dataType', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, []);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, []);
|
||||
const dataType = await umbracoApi.dataType.get(dataTypeId);
|
||||
dataType.values = dataTypeData;
|
||||
|
||||
@@ -52,7 +53,7 @@ test.describe('DataType tests', () => {
|
||||
|
||||
test('can delete dataType', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, dataTypeData);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
@@ -65,7 +66,7 @@ test.describe('DataType tests', () => {
|
||||
test('can move a dataType to a folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.dataType.ensureNameNotExists(folderName);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, dataTypeData);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy();
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
|
||||
expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
|
||||
@@ -82,7 +83,7 @@ test.describe('DataType tests', () => {
|
||||
test('can copy a dataType to a folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.dataType.ensureNameNotExists(folderName);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, dataTypeData);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
|
||||
|
||||
const dataType = await umbracoApi.dataType.get(dataTypeId);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import {expect} from '@playwright/test';
|
||||
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
|
||||
let documentTypeId = '';
|
||||
@@ -67,7 +67,7 @@ test('can open document type', async ({umbracoApi, umbracoUi}) => {
|
||||
|
||||
test('can open template', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const templateName = "TestTemplateForContent";
|
||||
const templateName = 'TestTemplateForContent';
|
||||
await umbracoApi.template.ensureNameNotExists(templateName);
|
||||
const templateId = await umbracoApi.template.createDefaultTemplate(templateName);
|
||||
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId, true);
|
||||
@@ -88,8 +88,8 @@ test('can open template', async ({umbracoApi, umbracoUi}) => {
|
||||
|
||||
test('can change template', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const firstTemplateName = "TestTemplateOneForContent";
|
||||
const secondTemplateName = "TestTemplateTwoForContent";
|
||||
const firstTemplateName = 'TestTemplateOneForContent';
|
||||
const secondTemplateName = 'TestTemplateTwoForContent';
|
||||
await umbracoApi.template.ensureNameNotExists(firstTemplateName);
|
||||
await umbracoApi.template.ensureNameNotExists(secondTemplateName);
|
||||
const firstTemplateId = await umbracoApi.template.createDefaultTemplate(firstTemplateName);
|
||||
@@ -115,8 +115,8 @@ test('can change template', async ({umbracoApi, umbracoUi}) => {
|
||||
|
||||
test('cannot change to a template that is not allowed in the document type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const firstTemplateName = "TestTemplateOneForContent";
|
||||
const secondTemplateName = "TestTemplateTwoForContent";
|
||||
const firstTemplateName = 'TestTemplateOneForContent';
|
||||
const secondTemplateName = 'TestTemplateTwoForContent';
|
||||
await umbracoApi.template.ensureNameNotExists(firstTemplateName);
|
||||
await umbracoApi.template.ensureNameNotExists(secondTemplateName);
|
||||
const firstTemplateId = await umbracoApi.template.createDefaultTemplate(firstTemplateName);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConstantHelper, test, AliasHelper } from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
@@ -18,6 +18,7 @@ test.afterEach(async ({umbracoApi}) => {
|
||||
|
||||
test('can create content with the checkbox list data type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
@@ -33,26 +34,27 @@ test('can create content with the checkbox list data type', async ({umbracoApi,
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test('can publish content with the checkbox list data type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -61,14 +63,12 @@ test('can create content with the custom checkbox list data type', async ({umbra
|
||||
const customDataTypeName = 'CustomCheckboxList';
|
||||
const optionValues = ['testOption1', 'testOption2'];
|
||||
const customDataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, optionValues);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.chooseCheckboxListOption(optionValues[0]);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
|
||||
@@ -8,11 +8,10 @@ const contentPickerDocumentTypeName = 'DocumentTypeForContentPicker';
|
||||
const contentPickerName = 'TestContentPicker';
|
||||
let contentPickerDocumentTypeId = '';
|
||||
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
contentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(contentPickerDocumentTypeName);
|
||||
await umbracoUi.goToBackOffice();
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
@@ -24,9 +23,11 @@ test.afterEach(async ({umbracoApi}) => {
|
||||
|
||||
test('can create content with the content picker datatype', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
const contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
@@ -38,24 +39,25 @@ test('can create content with the content picker datatype', {tag: '@smoke'}, asy
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(1);
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values[0].value).toEqual(contentPickerId);
|
||||
});
|
||||
|
||||
test('can publish content with the content picker data type', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
const contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.addContentPicker(contentPickerName);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
@@ -63,6 +65,7 @@ test('can publish content with the content picker data type', async ({umbracoApi
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values[0].value).toEqual(contentPickerId);
|
||||
});
|
||||
|
||||
@@ -70,15 +73,15 @@ test('can open content picker in the content', async ({umbracoApi, umbracoUi}) =
|
||||
// Arrange
|
||||
const customDataTypeName = 'CustomContentPicker';
|
||||
const customDataTypeId = await umbracoApi.dataType.createContentPickerDataTypeWithShowOpenButton(customDataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
// Create content to pick
|
||||
await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.addContentPicker(contentPickerName);
|
||||
|
||||
// Assert
|
||||
@@ -97,19 +100,18 @@ test('can choose start node for the content picker in the content', async ({umbr
|
||||
const childContentPickerName = 'TestChildContentPicker';
|
||||
await umbracoApi.documentType.ensureNameNotExists(childContentPickerDocumentTypeName);
|
||||
const childContentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childContentPickerDocumentTypeName);
|
||||
const contentPickerDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(contentPickerName, childContentPickerDocumentTypeId);
|
||||
contentPickerDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(contentPickerName, childContentPickerDocumentTypeId);
|
||||
const contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoApi.document.createDefaultDocumentWithParent(childContentPickerName, childContentPickerDocumentTypeId, contentPickerId);
|
||||
// Create a custom content picker with start node
|
||||
const customDataTypeId = await umbracoApi.dataType.createContentPickerDataTypeWithStartNode(customDataTypeName, contentPickerId);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
|
||||
// Assert
|
||||
@@ -128,19 +130,18 @@ test.skip('can ignore user start node for the content picker in the content', as
|
||||
const childContentPickerName = 'TestChildContentPicker';
|
||||
await umbracoApi.documentType.ensureNameNotExists(childContentPickerDocumentTypeName);
|
||||
const childContentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childContentPickerDocumentTypeName);
|
||||
const contentPickerDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(contentPickerName, childContentPickerDocumentTypeId);
|
||||
contentPickerDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(contentPickerName, childContentPickerDocumentTypeId);
|
||||
const contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoApi.document.createDefaultDocumentWithParent(childContentPickerName, childContentPickerDocumentTypeId, contentPickerId);
|
||||
// Create a custom content picker with the setting "ignore user start node" is enable
|
||||
const customDataTypeId = await umbracoApi.dataType.createContentPickerDataTypeWithIgnoreUserStartNodes(customDataTypeName, contentPickerId);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
|
||||
// Assert
|
||||
@@ -158,6 +159,7 @@ test('can remove content picker in the content', async ({umbracoApi, umbracoUi})
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
const contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId);
|
||||
await umbracoApi.document.createDocumentWithContentPicker(contentName, documentTypeId, contentPickerId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConstantHelper, test, AliasHelper } from '@umbraco/playwright-testhelpers';
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
@@ -20,6 +20,7 @@ for (const dataTypeName of dataTypeNames) {
|
||||
|
||||
test(`can create content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
@@ -35,26 +36,27 @@ for (const dataTypeName of dataTypeNames) {
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
test(`can publish content with the ${dataTypeName} data type`, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -65,14 +67,12 @@ for (const dataTypeName of dataTypeNames) {
|
||||
const selectedOptions = dataTypeName === 'Dropdown' ? [optionValues[0]] : optionValues;
|
||||
const isMultiple = dataTypeName === 'Dropdown' ? false : true;
|
||||
const customDataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, isMultiple, optionValues);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.chooseDropdownOption(selectedOptions);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
const documentTypeName = 'TestDocumentTypeForContent';
|
||||
const dataTypeName = 'Image Cropper';
|
||||
const imageFileName = 'Umbraco.png';
|
||||
const imageFilePath = './fixtures/mediaLibrary/' + imageFileName;
|
||||
const defaultFocalPoint = {
|
||||
left: 0.5,
|
||||
top: 0.5,
|
||||
};
|
||||
|
||||
test.beforeEach(async ({umbracoApi, umbracoUi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoUi.goToBackOffice();
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('can create content with the image cropper data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Draft';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateButton();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.uploadFile(imageFilePath);
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessNotificationVisible();
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(dataTypeName));
|
||||
expect(contentData.values[0].value.src).toContain(AliasHelper.toAlias(imageFileName));
|
||||
// TODO: is no longer null, we need to set an expected crops value
|
||||
// expect(contentData.values[0].value.crops).toEqual([]);
|
||||
expect(contentData.values[0].value.focalPoint).toEqual(defaultFocalPoint);
|
||||
});
|
||||
|
||||
test('can publish content with the image cropper data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const expectedState = 'Published';
|
||||
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.uploadFile(imageFilePath);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.variants[0].state).toBe(expectedState);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(dataTypeName));
|
||||
expect(contentData.values[0].value.src).toContain(AliasHelper.toAlias(imageFileName));
|
||||
// TODO: is no longer null, we need to set an expected crops value
|
||||
// expect(contentData.values[0].value.crops).toEqual([]);
|
||||
expect(contentData.values[0].value.focalPoint).toEqual(defaultFocalPoint);
|
||||
});
|
||||
|
||||
test('can create content with the custom image cropper data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const customDataTypeName = 'CustomImageCropper';
|
||||
const cropValue = ['TestCropLabel', 100, 50];
|
||||
const customDataTypeId = await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropValue[0], cropValue[1], cropValue[2]);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.uploadFile(imageFilePath);
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationsHaveCount(2);
|
||||
expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName));
|
||||
expect(contentData.values[0].value.src).toContain(AliasHelper.toAlias(imageFileName));
|
||||
expect(contentData.values[0].value.focalPoint).toEqual(defaultFocalPoint);
|
||||
expect(contentData.values[0].value.crops[0].alias).toEqual(AliasHelper.toAlias(cropValue[0]));
|
||||
expect(contentData.values[0].value.crops[0].width).toEqual(cropValue[1]);
|
||||
expect(contentData.values[0].value.crops[0].height).toEqual(cropValue[2]);
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user