Compare commits
132
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6ab394947 | ||
|
|
d8f68d2c40 | ||
|
|
ff88617db0 | ||
|
|
9f912aea0e | ||
|
|
ba95c12f09 | ||
|
|
14fbd20665 | ||
|
|
2d8b5e8786 | ||
|
|
747e095178 | ||
|
|
1cfa5a225e | ||
|
|
7888b9a4ce | ||
|
|
e31582b297 | ||
|
|
75cc017a18 | ||
|
|
d60137e6da | ||
|
|
9f9c88781a | ||
|
|
c7014e159b | ||
|
|
31e1acce67 | ||
|
|
35c51a029a | ||
|
|
8c1128c85b | ||
|
|
c9021ab2d2 | ||
|
|
67a71f8f82 | ||
|
|
edd0a4a4a9 | ||
|
|
11270eaaf5 | ||
|
|
2d71b5a63b | ||
|
|
9bab74d30e | ||
|
|
0ee0db8071 | ||
|
|
c17d4e1a60 | ||
|
|
fee222daff | ||
|
|
119fde2033 | ||
|
|
52c21b0fca | ||
|
|
99d5a7e609 | ||
|
|
7e1d1a1968 | ||
|
|
b743f6a2df | ||
|
|
a2511ff09b | ||
|
|
2c23e67c65 | ||
|
|
8e837d387d | ||
|
|
801fb5f885 | ||
|
|
ed517ecd86 | ||
|
|
b5e46ba880 | ||
|
|
cbf9f9bcd1 | ||
|
|
2735f17ed8 | ||
|
|
e37cf30690 | ||
|
|
118ac8e230 | ||
|
|
3e28e10cdf | ||
|
|
b18b6cc5e7 | ||
|
|
7a04baf9ef | ||
|
|
bc1ddaed8c | ||
|
|
860afb35f2 | ||
|
|
9799c550f4 | ||
|
|
b7e43a8def | ||
|
|
cdbbd6a921 | ||
|
|
5198e7c52d | ||
|
|
49f5d2e2d4 | ||
|
|
57b3a196bf | ||
|
|
dff90c6ec0 | ||
|
|
040495f359 | ||
|
|
a2ad95d965 | ||
|
|
64f2447c0e | ||
|
|
b648126d19 | ||
|
|
812b414d96 | ||
|
|
ec91c47158 | ||
|
|
e04a41be45 | ||
|
|
d5156aced2 | ||
|
|
fa9b0e4718 | ||
|
|
8915064780 | ||
|
|
677ff59d04 | ||
|
|
1fe2ec08be | ||
|
|
5dfa76600a | ||
|
|
83bcd37250 | ||
|
|
4a7ad4a562 | ||
|
|
cdd4d2a000 | ||
|
|
be5a740c96 | ||
|
|
13cc320f19 | ||
|
|
b5544aa520 | ||
|
|
9e3f8c7d0c | ||
|
|
cb76554455 | ||
|
|
37e4d80ce8 | ||
|
|
d5d4edbe92 | ||
|
|
8d9343b564 | ||
|
|
1ee891c8d5 | ||
|
|
bf1a5b9b28 | ||
|
|
696b74cdb6 | ||
|
|
bf8af4588e | ||
|
|
4781fb1d8b | ||
|
|
d714741bee | ||
|
|
ea33b73daa | ||
|
|
ba64b46824 | ||
|
|
04e59e9a9d | ||
|
|
be39b9c19a | ||
|
|
9a143c0850 | ||
|
|
df4cd63962 | ||
|
|
a17e398f28 | ||
|
|
9af9dd2c02 | ||
|
|
2266a98fc6 | ||
|
|
151fccee97 | ||
|
|
abfa07367f | ||
|
|
e5de8bf56b | ||
|
|
00bb5170a0 | ||
|
|
919138c299 | ||
|
|
ed65645a33 | ||
|
|
d0be4ab263 | ||
|
|
ddb26632f9 | ||
|
|
d5b1f5875b | ||
|
|
f8c1a560ff | ||
|
|
9e5ff305c8 | ||
|
|
ffaa77eb25 | ||
|
|
6777145079 | ||
|
|
92af8ac881 | ||
|
|
bde88caf14 | ||
|
|
f4ac6cdded | ||
|
|
132935c2d7 | ||
|
|
04b05714e5 | ||
|
|
7be1852827 | ||
|
|
158cef1692 | ||
|
|
1a4f3981eb | ||
|
|
7765f98ee1 | ||
|
|
6b72880c0b | ||
|
|
d9f3936a43 | ||
|
|
bf09c87f87 | ||
|
|
ec2134dea0 | ||
|
|
8ccc2faee0 | ||
|
|
0a4cc27a5d | ||
|
|
c8e054baa2 | ||
|
|
95ffdae475 | ||
|
|
ca8ac21f4b | ||
|
|
f85a0d1715 | ||
|
|
2fc29e638c | ||
|
|
56486b1ace | ||
|
|
fd043f6ecf | ||
|
|
39a102ed8a | ||
|
|
f750bca453 | ||
|
|
7e36203f60 | ||
|
|
334dff8645 |
@@ -49,4 +49,5 @@
|
||||
<PropertyGroup>
|
||||
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+102
-54
@@ -21,9 +21,33 @@ parameters:
|
||||
displayName: Upload API docs
|
||||
type: boolean
|
||||
default: false
|
||||
- name: forceReleaseTestFilter
|
||||
displayName: Force to use the release test filters
|
||||
type: boolean
|
||||
default: false
|
||||
- name: integrationNonReleaseTestFilter
|
||||
displayName: TestFilter used for non-release type builds
|
||||
type: string
|
||||
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
|
||||
- name: integrationReleaseTestFilter
|
||||
displayName: TestFilter used for release type builds
|
||||
type: string
|
||||
default: ' '
|
||||
- name: nonWindowsIntegrationNonReleaseTestFilter
|
||||
displayName: TestFilter used for non-release type builds on non windows agents
|
||||
type: string
|
||||
default: '--filter TestCategory!=LongRunning&TestCategory!=NonCritical'
|
||||
- name: nonWindowsIntegrationReleaseTestFilter
|
||||
displayName: TestFilter used for release type builds on non windows agents
|
||||
type: string
|
||||
default: ' '
|
||||
- name: isNightly
|
||||
displayName: 'Is nightly build (used for MyGet feed)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
variables:
|
||||
nodeVersion: 14.18.1
|
||||
nodeVersion: 20
|
||||
dotnetVersion: 6.x
|
||||
dotnetIncludePreviewVersions: false
|
||||
solution: umbraco.sln
|
||||
@@ -49,6 +73,7 @@ stages:
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
- task: Cache@2
|
||||
@@ -61,9 +86,9 @@ stages:
|
||||
path: $(npm_config_cache)
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
displayName: Run npm ci
|
||||
displayName: Run npm ci (Backoffice)
|
||||
- task: gulp@0
|
||||
displayName: Run gulp build
|
||||
displayName: Run gulp build (Backoffice)
|
||||
inputs:
|
||||
gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
|
||||
targets: coreBuild
|
||||
@@ -80,44 +105,12 @@ stages:
|
||||
command: restore
|
||||
projects: $(solution)
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet build
|
||||
name: build
|
||||
displayName: Run dotnet build and generate NuGet packages
|
||||
inputs:
|
||||
command: build
|
||||
projects: $(solution)
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore -p:ContinuousIntegrationBuild=true'
|
||||
- script: |
|
||||
version="$(Build.BuildNumber)"
|
||||
echo "varsion: $version"
|
||||
|
||||
major="$(echo $version | cut -d '.' -f 1)"
|
||||
echo "major version: $major"
|
||||
|
||||
echo "##vso[task.setvariable variable=majorVersion;isOutput=true]$major"
|
||||
displayName: Set major version
|
||||
name: determineMajorVersion
|
||||
- task: PowerShell@2
|
||||
displayName: Prepare nupkg
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
$umbracoVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
$templatePaths = Get-ChildItem 'templates/**/.template.config/template.json'
|
||||
|
||||
foreach ($templatePath in $templatePaths) {
|
||||
$a = Get-Content $templatePath -Raw | ConvertFrom-Json
|
||||
if ($a.symbols -and $a.symbols.UmbracoVersion) {
|
||||
$a.symbols.UmbracoVersion.defaultValue = $umbracoVersion
|
||||
$a | ConvertTo-Json -Depth 32 | Set-Content $templatePath
|
||||
}
|
||||
}
|
||||
|
||||
dotnet pack $(solution) --configuration $(buildConfiguration) -p:BuildProjectReferences=false --output $(Build.ArtifactStagingDirectory)/nupkg
|
||||
- script: |
|
||||
sha="$(Build.SourceVersion)"
|
||||
sha=${sha:0:7}
|
||||
buildnumber="$(Build.BuildNumber)_$(Build.BuildId)_$sha"
|
||||
echo "##vso[build.updatebuildnumber]$buildnumber"
|
||||
displayName: Update build number
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish nupkg
|
||||
inputs:
|
||||
@@ -130,11 +123,11 @@ stages:
|
||||
artifactName: build_output
|
||||
|
||||
- stage: Build_Docs
|
||||
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.buildApiDocs}}))
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}}))
|
||||
displayName: Prepare API Documentation
|
||||
dependsOn: Build
|
||||
variables:
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
|
||||
jobs:
|
||||
# C# API Reference
|
||||
- job:
|
||||
@@ -188,9 +181,10 @@ stages:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js 10.15.0
|
||||
displayName: Use Node.js 10.15.x
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: 10.15.0 # Won't work with higher versions
|
||||
versionSpec: 10.15.x # Won't work with higher versions
|
||||
- script: |
|
||||
npm ci --no-fund --no-audit --prefer-offline
|
||||
npx gulp docs
|
||||
@@ -259,6 +253,8 @@ stages:
|
||||
- stage: Integration
|
||||
displayName: Integration Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
releaseTestFilter: eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True')
|
||||
jobs:
|
||||
# Integration Tests (SQLite)
|
||||
- job:
|
||||
@@ -286,12 +282,30 @@ stages:
|
||||
performMultiLevelLookup: true
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
displayName: Run dotnet test Windows
|
||||
condition: eq(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
arguments: '--configuration $(buildConfiguration) --no-build'
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) ${{parameters.integrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: 'Sqlite'
|
||||
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test Non Windows
|
||||
condition: ne(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: 'Sqlite'
|
||||
Umbraco__CMS__Global__MainDomLock: 'FileSystemMainDomLock'
|
||||
@@ -299,7 +313,7 @@ stages:
|
||||
# Integration Tests (SQL Server)
|
||||
- job:
|
||||
timeoutInMinutes: 120
|
||||
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.sqlServerIntegrationTests}})
|
||||
condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}})
|
||||
displayName: Integration Tests (SQL Server)
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -310,7 +324,7 @@ stages:
|
||||
Linux:
|
||||
vmImage: 'ubuntu-latest'
|
||||
testDb: SqlServer
|
||||
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
|
||||
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
@@ -321,6 +335,11 @@ stages:
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET $(dotnetVersion)
|
||||
inputs:
|
||||
version: $(dotnetVersion)
|
||||
includePreviewVersions: $(dotnetIncludePreviewVersions)
|
||||
- powershell: sqllocaldb start mssqllocaldb
|
||||
displayName: Start localdb (Windows only)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
@@ -328,12 +347,31 @@ stages:
|
||||
displayName: Start SQL Server (Linux only)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
displayName: Run dotnet test Windows
|
||||
condition: eq(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
arguments: '--configuration $(buildConfiguration) --no-build'
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.integrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: $(testDb)
|
||||
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
|
||||
Umbraco__CMS__Global__MainDomLock: 'SqlMainDomLock'
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test NonWindows
|
||||
condition: ne(variables['Agent.OS'],'Windows_NT')
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.Integration.csproj'
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
${{ if or(variables.releaseTestFilter, parameters.forceReleaseTestFilter) }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationReleaseTestFilter}}'
|
||||
${{ else }}:
|
||||
arguments: '--configuration $(buildConfiguration) --no-build ${{parameters.nonWindowsIntegrationNonReleaseTestFilter}}'
|
||||
env:
|
||||
Tests__Database__DatabaseType: $(testDb)
|
||||
Tests__Database__SQLServerMasterConnectionString: $(connectionString)
|
||||
@@ -361,6 +399,7 @@ stages:
|
||||
dockerImageName: umbraco-linux
|
||||
Windows:
|
||||
vmImage: 'windows-latest'
|
||||
DOTNET_GENERATE_ASPNET_CERTIFICATE: true # Automatically generate HTTPS development certificate on Windows
|
||||
# Enable console logging in Release mode
|
||||
Serilog__WriteTo__0__Name: Async
|
||||
Serilog__WriteTo__0__Args__configure__0__Name: Console
|
||||
@@ -385,6 +424,7 @@ stages:
|
||||
path: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/misc/nupkg
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
- task: Cache@2
|
||||
@@ -450,6 +490,8 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
- pwsh: |
|
||||
docker logs $(dockerImageName) > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1
|
||||
docker stop $(dockerImageName)
|
||||
@@ -488,9 +530,11 @@ stages:
|
||||
- Unit
|
||||
- Integration
|
||||
# - E2E # TODO: Enable when stable.
|
||||
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.myGetDeploy}}))
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to pre-release feed
|
||||
steps:
|
||||
- checkout: none
|
||||
@@ -505,16 +549,20 @@ stages:
|
||||
command: 'push'
|
||||
packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
|
||||
nuGetFeedType: 'external'
|
||||
publishFeedCredentials: 'MyGet - Pre-releases'
|
||||
|
||||
${{ if eq(parameters.isNightly, true) }}:
|
||||
publishFeedCredentials: 'MyGet - Umbraco Nightly'
|
||||
${{ else }}:
|
||||
publishFeedCredentials: 'MyGet - Pre-releases'
|
||||
- stage: Deploy_NuGet
|
||||
displayName: NuGet release
|
||||
dependsOn:
|
||||
- Deploy_MyGet
|
||||
- Build_Docs
|
||||
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}}))
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to NuGet
|
||||
steps:
|
||||
- checkout: none
|
||||
@@ -535,12 +583,12 @@ stages:
|
||||
pool:
|
||||
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
|
||||
variables:
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
|
||||
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
|
||||
displayName: Upload API Documention
|
||||
dependsOn:
|
||||
- Build
|
||||
- Deploy_NuGet
|
||||
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}}))
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}}))
|
||||
jobs:
|
||||
- job:
|
||||
displayName: Upload C# Docs
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Nightly_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v10/dev
|
||||
- v12/dev
|
||||
- v13/dev
|
||||
- v14/dev
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: TriggerBuild@4
|
||||
inputs:
|
||||
definitionIsInCurrentTeamProject: true
|
||||
buildDefinition: '301'
|
||||
queueBuildForUserThatTriggeredBuild: true
|
||||
ignoreSslCertificateErrors: false
|
||||
useSameSourceVersion: false
|
||||
useCustomSourceVersion: false
|
||||
useSameBranch: true
|
||||
waitForQueuedBuildsToFinish: false
|
||||
storeInEnvironmentVariable: false
|
||||
templateParameters: 'sqlServerIntegrationTests: true, forceReleaseTestFilter: true, myGetDeploy: true, isNightly: true'
|
||||
authenticationMethod: 'OAuth Token'
|
||||
enableBuildInQueueCondition: false
|
||||
dependentOnSuccessfulBuildCondition: false
|
||||
dependentOnFailedBuildCondition: false
|
||||
checkbuildsoncurrentbranch: false
|
||||
failTaskIfConditionsAreNotFulfilled: false
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.300",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<NoWarn>NU1507</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.0.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.0.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageVersion Include="IPNetwork2" Version="2.6.618" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.6" />
|
||||
<PackageVersion Include="MailKit" Version="3.2.0" />
|
||||
<PackageVersion Include="Markdown" Version="2.2.1" />
|
||||
<PackageVersion Include="MessagePack" Version="2.5.187" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.24" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.24" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="6.0.24" />
|
||||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.24" />
|
||||
<PackageVersion Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="6.0.24" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="6.0.24" />
|
||||
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.2.22" />
|
||||
<PackageVersion Include="MiniProfiler.Shared" Version="4.2.22" />
|
||||
<PackageVersion Include="ncrontab" Version="3.3.3" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="NPoco.SqlServer" Version="5.3.2" />
|
||||
<PackageVersion Include="Serilog" Version="2.12.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="3.4.1" />
|
||||
<PackageVersion Include="Serilog.Extensions.Hosting" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact" Version="1.1.0" />
|
||||
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.10" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Web" Version="2.0.2" />
|
||||
<PackageVersion Include="Smidge.InMemory" Version="4.3.0" />
|
||||
<PackageVersion Include="Smidge.Nuglify" Version="4.2.1" />
|
||||
<PackageVersion Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="6.0.4" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
|
||||
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="6.0.0" />
|
||||
<PackageVersion Include="Umbraco.CSharpTest.Net.Collections" Version="14.906.1403.1085" />
|
||||
<!-- Add dependencies that we force an update to, even that we do not use them explicitly and they seems to be taken from the framework instead of from Nuget -->
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Security.Cryptography.Xml" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -3,16 +3,17 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.7.2" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
|
||||
<PackageReference Include="Umbraco.Deploy.Core" Version="10.2.2" />
|
||||
<PackageReference Include="Umbraco.Forms.Core" Version="10.5.0" />
|
||||
<PackageReference Include="Umbraco.Deploy.Core" Version="10.4.0" />
|
||||
<PackageReference Include="Umbraco.Forms.Core" Version="10.5.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<Description>Contains the presentation layer for the Umbraco CMS Management API.</Description>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnablePackageValidation>false</EnablePackageValidation>
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -12,7 +13,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.0.0" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="13.16.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.New.Cms.Core\Umbraco.New.Cms.Core.csproj" />
|
||||
@@ -20,7 +21,7 @@
|
||||
<ProjectReference Include="..\Umbraco.New.Cms.Web.Common\Umbraco.New.Cms.Web.Common.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="OpenApi.json" />
|
||||
</ItemGroup>
|
||||
|
||||
+6
-4
@@ -134,9 +134,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
|
||||
const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id";
|
||||
|
||||
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
|
||||
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
|
||||
|
||||
var i = db.ExecuteScalar<int?>(query, new { id = LockId });
|
||||
// execute the lock timeout query and the actual query in a single server roundtrip
|
||||
var i = db.ExecuteScalar<int?>($"{lockTimeoutQuery};{query}", new { id = LockId });
|
||||
|
||||
if (i == null)
|
||||
{
|
||||
@@ -169,9 +170,10 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
const string query =
|
||||
@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id";
|
||||
|
||||
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
|
||||
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
|
||||
|
||||
var i = db.Execute(query, new { id = LockId });
|
||||
// execute the lock timeout query and the actual query in a single server roundtrip
|
||||
var i = db.Execute($"{lockTimeoutQuery};{query}", new { id = LockId });
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -154,7 +154,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
|
||||
|
||||
try
|
||||
{
|
||||
var i = command.ExecuteNonQuery();
|
||||
var i = db.ExecuteNonQuery(command);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<Description>Installs Umbraco CMS with all default dependencies in your ASP.NET Core project.</Description>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -84,8 +84,8 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
isolatedCache.ClearOfType<IContent>((k, v) => v.Path?.Contains(pathid) ?? false);
|
||||
}
|
||||
|
||||
// if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
|
||||
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
|
||||
// if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
|
||||
if (payload.Blueprint is false && payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove))
|
||||
{
|
||||
idsRemoved.Add(payload.Id);
|
||||
}
|
||||
@@ -120,7 +120,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
// should rename it, and then, this is only for Deploy, and then, ???
|
||||
// if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
|
||||
// ...
|
||||
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
|
||||
if (payloads.Any(x => x.Blueprint is false))
|
||||
{
|
||||
// Only notify if the payload contains actual (non-blueprint) contents
|
||||
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
|
||||
}
|
||||
|
||||
base.Refresh(payloads);
|
||||
}
|
||||
@@ -157,8 +161,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (V14): Change into a record
|
||||
public class JsonPayload
|
||||
{
|
||||
public JsonPayload()
|
||||
{ }
|
||||
|
||||
[Obsolete("Use the default constructor and property initializers.")]
|
||||
public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes)
|
||||
{
|
||||
Id = id;
|
||||
@@ -166,11 +175,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
ChangeTypes = changeTypes;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
public int Id { get; init; }
|
||||
|
||||
public Guid? Key { get; }
|
||||
public Guid? Key { get; init; }
|
||||
|
||||
public TreeChangeTypes ChangeTypes { get; }
|
||||
public TreeChangeTypes ChangeTypes { get; init; }
|
||||
|
||||
public bool Blueprint { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -134,8 +134,14 @@ public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase<LanguageC
|
||||
ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items
|
||||
|
||||
// now refresh all nucache
|
||||
ContentCacheRefresher.JsonPayload[] clearContentPayload =
|
||||
new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
|
||||
ContentCacheRefresher.JsonPayload[] clearContentPayload = new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
ChangeTypes = TreeChangeTypes.RefreshAll
|
||||
}
|
||||
};
|
||||
|
||||
ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace Umbraco.Cms.Core.Cache;
|
||||
/// </summary>
|
||||
public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan _readLockTimeout = TimeSpan.FromSeconds(5);
|
||||
private static readonly TimeSpan _writeLockTimeout = TimeSpan.FromSeconds(5);
|
||||
|
||||
private readonly ReaderWriterLockSlim _locker = new(LockRecursionPolicy.SupportsRecursion);
|
||||
private bool _disposedValue;
|
||||
|
||||
@@ -33,7 +36,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
Lazy<object?>? result;
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_locker.TryEnterReadLock(_readLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when getting item");
|
||||
}
|
||||
result = MemoryCache.Get(key) as Lazy<object?>; // null if key not found
|
||||
}
|
||||
finally
|
||||
@@ -195,7 +201,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing item");
|
||||
}
|
||||
if (MemoryCache[key] == null)
|
||||
{
|
||||
return;
|
||||
@@ -223,8 +232,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
var isInterface = type.IsInterface;
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by type");
|
||||
}
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
@@ -259,7 +270,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by generic type");
|
||||
}
|
||||
Type typeOfT = typeof(T);
|
||||
var isInterface = typeOfT.IsInterface;
|
||||
|
||||
@@ -296,7 +310,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing generic type with predicate");
|
||||
}
|
||||
Type typeOfT = typeof(T);
|
||||
var isInterface = typeOfT.IsInterface;
|
||||
|
||||
@@ -338,7 +355,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cache when clearing with prefix");
|
||||
}
|
||||
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
@@ -365,7 +385,10 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
|
||||
{
|
||||
throw new TimeoutException("Timeout exceeded to the memory cach when clearing by regex");
|
||||
}
|
||||
|
||||
// ToArray required to remove
|
||||
foreach (var key in MemoryCache
|
||||
|
||||
@@ -3,30 +3,26 @@ using System.Collections;
|
||||
namespace Umbraco.Cms.Core.Composing;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class for builder collections.
|
||||
/// Provides a base class for builder collections.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public abstract class BuilderCollectionBase<TItem> : IBuilderCollection<TItem>
|
||||
{
|
||||
private readonly LazyReadOnlyCollection<TItem> _items;
|
||||
|
||||
/// Initializes a new instance of the
|
||||
/// <see cref="BuilderCollectionBase{TItem}" />
|
||||
/// with items.
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuilderCollectionBase{TItem}" /> with items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public BuilderCollectionBase(Func<IEnumerable<TItem>> items) => _items = new LazyReadOnlyCollection<TItem>(items);
|
||||
public BuilderCollectionBase(Func<IEnumerable<TItem>> items)
|
||||
=> _items = new LazyReadOnlyCollection<TItem>(items);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _items.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<TItem> GetEnumerator() => _items.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
namespace Umbraco.Cms.Core.Composing;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder collection, ie an immutable enumeration of items.
|
||||
/// Represents a builder collection, ie an immutable enumeration of items.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public interface IBuilderCollection<out TItem> : IEnumerable<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the number of items in the collection.
|
||||
/// Gets the number of items in the collection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
int Count { get; }
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public class NuCacheSettings
|
||||
internal const string StaticNuCacheSerializerType = "MessagePack";
|
||||
internal const int StaticSqlPageSize = 1000;
|
||||
internal const int StaticKitBatchSize = 1;
|
||||
internal const bool StaticUsePagedSqlQuery = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value defining the BTree block size.
|
||||
@@ -40,4 +41,7 @@ public class NuCacheSettings
|
||||
public int KitBatchSize { get; set; } = StaticKitBatchSize;
|
||||
|
||||
public bool UnPublishedContentCompression { get; set; } = false;
|
||||
|
||||
[DefaultValue(StaticUsePagedSqlQuery)]
|
||||
public bool UsePagedSqlQuery { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
@@ -24,6 +25,8 @@ public class SecuritySettings
|
||||
|
||||
internal const int StaticMemberDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
internal const int StaticUserDefaultLockoutTimeInMinutes = 30 * 24 * 60;
|
||||
private const long StaticUserDefaultFailedLoginDurationInMilliseconds = 1000;
|
||||
private const long StaticUserMinimumFailedLoginDurationInMilliseconds = 250;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to keep the user logged in.
|
||||
@@ -109,4 +112,26 @@ public class SecuritySettings
|
||||
[Obsolete("Use ContentSettings.AllowEditFromInvariant instead")]
|
||||
[DefaultValue(StaticAllowEditInvariantFromNonDefault)]
|
||||
public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// The user login endpoint ensures that failed login attempts take at least as long as the average successful login.
|
||||
/// However, if no successful logins have occurred, this value is used as the default duration.
|
||||
/// </remarks>
|
||||
[DefaultValue(StaticUserDefaultFailedLoginDurationInMilliseconds)]
|
||||
public long UserDefaultFailedLoginDurationInMilliseconds { get; set; } = StaticUserDefaultFailedLoginDurationInMilliseconds;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The minimum duration (in milliseconds) of failed login attempts.
|
||||
/// </value>
|
||||
[DefaultValue(StaticUserMinimumFailedLoginDurationInMilliseconds)]
|
||||
public long UserMinimumFailedLoginDurationInMilliseconds { get; set; } = StaticUserMinimumFailedLoginDurationInMilliseconds;
|
||||
}
|
||||
|
||||
@@ -318,8 +318,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddSingleton<ConflictingPackageData>();
|
||||
Services.AddSingleton<CompiledPackageXmlParser>();
|
||||
|
||||
// Register a noop IHtmlSanitizer to be replaced
|
||||
// Register a noop IHtmlSanitizer & IMarkdownSanitizer to be replaced
|
||||
Services.AddUnique<IHtmlSanitizer, NoopHtmlSanitizer>();
|
||||
Services.AddUnique<IMarkdownSanitizer, NoopMarkdownSanitizer>();
|
||||
|
||||
Services.AddUnique<IPropertyTypeUsageService, PropertyTypeUsageService>();
|
||||
Services.AddUnique<IDataTypeUsageService, DataTypeUsageService>();
|
||||
|
||||
@@ -134,27 +134,6 @@ public static class PublishedElementExtensions
|
||||
|
||||
#endregion
|
||||
|
||||
#region CheckVariation
|
||||
/// <summary>
|
||||
/// Method to check if VariationContext culture differs from culture parameter, if so it will update the VariationContext for the PublishedValueFallback.
|
||||
/// </summary>
|
||||
/// <param name="publishedValueFallback">The requested PublishedValueFallback.</param>
|
||||
/// <param name="culture">The requested culture.</param>
|
||||
/// <param name="segment">The requested segment.</param>
|
||||
/// <returns></returns>
|
||||
private static void EventuallyUpdateVariationContext(IPublishedValueFallback publishedValueFallback, string? culture, string? segment)
|
||||
{
|
||||
IVariationContextAccessor? variationContextAccessor = publishedValueFallback.VariationContextAccessor;
|
||||
|
||||
//If there is a difference in requested culture and the culture that is set in the VariationContext, it will pick wrong localized content.
|
||||
//This happens for example using links to localized content in a RichText Editor.
|
||||
if (!string.IsNullOrEmpty(culture) && variationContextAccessor?.VariationContext?.Culture != culture)
|
||||
{
|
||||
variationContextAccessor!.VariationContext = new VariationContext(culture, segment);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value<T>
|
||||
|
||||
/// <summary>
|
||||
@@ -195,8 +174,6 @@ public static class PublishedElementExtensions
|
||||
{
|
||||
IPublishedProperty? property = content.GetProperty(alias);
|
||||
|
||||
EventuallyUpdateVariationContext(publishedValueFallback, culture, segment);
|
||||
|
||||
// if we have a property, and it has a value, return that value
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
{
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
|
||||
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,19 +7,21 @@ namespace Umbraco.Cms.Core.IO;
|
||||
|
||||
internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
{
|
||||
private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private const string ShadowFsPath = "ShadowFs";
|
||||
|
||||
private readonly Func<bool?>? _isScoped;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly string _shadowPath;
|
||||
private readonly Func<bool?>? _isScoped;
|
||||
|
||||
private string? _shadowDir;
|
||||
private ShadowFileSystem? _shadowFileSystem;
|
||||
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, string shadowPath, Func<bool?>? isScoped = null)
|
||||
{
|
||||
InnerFileSystem = innerFileSystem;
|
||||
|
||||
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
_loggerFactory = loggerFactory;
|
||||
@@ -35,18 +37,19 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isScoped is not null && _shadowFileSystem is not null)
|
||||
Func<bool?>? isScoped = _isScoped;
|
||||
if (isScoped is not null && _shadowFileSystem is not null)
|
||||
{
|
||||
var isScoped = _isScoped!();
|
||||
bool? scoped = isScoped();
|
||||
|
||||
// if the filesystem is created *after* shadowing starts, it won't be shadowing
|
||||
// better not ignore that situation and raised a meaningful (?) exception
|
||||
if (isScoped.HasValue && isScoped.Value && _shadowFileSystem == null)
|
||||
// better not ignore that situation and raise a meaningful (?) exception
|
||||
if (scoped.HasValue && scoped.Value && _shadowFileSystem == null)
|
||||
{
|
||||
throw new Exception("The filesystems are shadowing, but this filesystem is not.");
|
||||
}
|
||||
|
||||
return isScoped.HasValue && isScoped.Value
|
||||
return scoped.HasValue && scoped.Value
|
||||
? _shadowFileSystem
|
||||
: InnerFileSystem;
|
||||
}
|
||||
@@ -56,8 +59,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFileProvider? Create() =>
|
||||
InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
|
||||
public IFileProvider? Create() => InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null;
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path) => FileSystem.GetDirectories(path);
|
||||
|
||||
@@ -69,8 +71,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
|
||||
public void AddFile(string path, Stream stream) => FileSystem.AddFile(path, stream);
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting) =>
|
||||
FileSystem.AddFile(path, stream, overrideExisting);
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting) => FileSystem.AddFile(path, stream, overrideExisting);
|
||||
|
||||
public IEnumerable<string> GetFiles(string path) => FileSystem.GetFiles(path);
|
||||
|
||||
@@ -107,8 +108,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
{
|
||||
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
|
||||
|
||||
var virt = ShadowFsPath + "/" + id;
|
||||
var shadowDir = hostingEnvironment.MapPathContentRoot(virt);
|
||||
var shadowDir = Path.Combine(hostingEnvironment.LocalTempPath, ShadowFsPath, id);
|
||||
if (Directory.Exists(shadowDir))
|
||||
{
|
||||
continue;
|
||||
@@ -129,10 +129,10 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
|
||||
// on ShadowFileSystemsScope.None - and if None is false then we should be running
|
||||
// in a single thread anyways
|
||||
var virt = Path.Combine(ShadowFsPath, id, _shadowPath);
|
||||
_shadowDir = _hostingEnvironment.MapPathContentRoot(virt);
|
||||
var rootUrl = Path.Combine(ShadowFsPath, id, _shadowPath);
|
||||
_shadowDir = Path.Combine(_hostingEnvironment.LocalTempPath, rootUrl);
|
||||
Directory.CreateDirectory(_shadowDir);
|
||||
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, _hostingEnvironment.ToAbsolute(virt));
|
||||
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, rootUrl);
|
||||
_shadowFileSystem = new ShadowFileSystem(InnerFileSystem, tempfs);
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
|
||||
|
||||
// shadowPath make be path/to/dir, remove each
|
||||
dir = dir!.Replace('/', Path.DirectorySeparatorChar);
|
||||
var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length;
|
||||
var min = Path.Combine(_hostingEnvironment.LocalTempPath, ShadowFsPath).Length;
|
||||
var pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
|
||||
while (pos > min)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,9 @@ public class Issuu : EmbedProviderBase
|
||||
|
||||
public override Dictionary<string, string> RequestParams => new()
|
||||
{
|
||||
// ApiUrl/?iframe=true
|
||||
{ "iframe", "true" },
|
||||
|
||||
// ApiUrl/?format=xml
|
||||
{ "format", "xml" },
|
||||
};
|
||||
|
||||
@@ -55,11 +55,12 @@ public abstract class OEmbedProviderBase : IEmbedProvider
|
||||
if (_httpClient == null)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS");
|
||||
}
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
{
|
||||
HttpResponseMessage response = _httpClient.SendAsync(request).Result;
|
||||
HttpResponseMessage response = _httpClient.SendAsync(request).GetAwaiter().GetResult();
|
||||
return response.Content.ReadAsStringAsync().Result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,88 @@
|
||||
namespace Umbraco.Cms.Core.Models.Editors;
|
||||
|
||||
/// <summary>
|
||||
/// Used to track reference to other entities in a property value
|
||||
/// Used to track a reference to another entity in a property value.
|
||||
/// </summary>
|
||||
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
|
||||
{
|
||||
private static readonly UmbracoEntityReference _empty = new(UnknownTypeUdi.Instance, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoEntityReference" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
/// <param name="relationTypeAlias">The relation type alias.</param>
|
||||
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
|
||||
{
|
||||
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
|
||||
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoEntityReference" /> struct for a document or media item.
|
||||
/// </summary>
|
||||
/// <param name="udi">The UDI.</param>
|
||||
public UmbracoEntityReference(Udi udi)
|
||||
{
|
||||
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
|
||||
|
||||
switch (udi.EntityType)
|
||||
{
|
||||
case Constants.UdiEntityType.Document:
|
||||
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
|
||||
break;
|
||||
case Constants.UdiEntityType.Media:
|
||||
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
|
||||
break;
|
||||
default:
|
||||
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
|
||||
// No relation type alias convention for this entity type, so leave it empty
|
||||
RelationTypeAlias = string.Empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UDI.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The UDI.
|
||||
/// </value>
|
||||
public Udi Udi { get; }
|
||||
|
||||
public static UmbracoEntityReference Empty() => _empty;
|
||||
|
||||
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relation type alias.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The relation type alias.
|
||||
/// </value>
|
||||
public string RelationTypeAlias { get; }
|
||||
|
||||
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
|
||||
/// <summary>
|
||||
/// Gets an empty reference.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An empty reference.
|
||||
/// </returns>
|
||||
public static UmbracoEntityReference Empty() => _empty;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified reference is empty.
|
||||
/// </summary>
|
||||
/// <param name="reference">The reference.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified reference is empty; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => obj is UmbracoEntityReference reference && Equals(reference);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(UmbracoEntityReference other) =>
|
||||
EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
|
||||
RelationTypeAlias == other.RelationTypeAlias;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -487348478;
|
||||
@@ -52,5 +91,9 @@ public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
|
||||
|
||||
/// <inheritdoc />
|
||||
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) => !(left == right);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
/// </summary>
|
||||
public interface IPublishedValueFallback
|
||||
{
|
||||
[Obsolete("Scheduled for removal in v14")]
|
||||
/// <summary>
|
||||
/// VariationContextAccessor that is not required to be implemented, therefore throws NotImplementedException as default.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ public class PublishedValueFallback : IPublishedValueFallback
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
[Obsolete("Scheduled for removal in v14")]
|
||||
public IVariationContextAccessor VariationContextAccessor { get { return _variationContextAccessor; } }
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Notification that is send out when a Content item has been scaffolded from an original item and basic cleaning has been performed
|
||||
/// </summary>
|
||||
public sealed class ContentScaffoldedNotification : ScaffoldedNotification<IContent>
|
||||
{
|
||||
public ContentScaffoldedNotification(IContent original, IContent scaffold, int parentId, EventMessages messages)
|
||||
: base(original, scaffold, parentId, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Events;
|
||||
|
||||
namespace Umbraco.Cms.Core.Notifications;
|
||||
|
||||
public abstract class ScaffoldedNotification<T> : CancelableObjectNotification<T>
|
||||
where T : class
|
||||
{
|
||||
protected ScaffoldedNotification(T original, T scaffold, int parentId, EventMessages messages)
|
||||
: base(original, messages)
|
||||
{
|
||||
Scaffold = scaffold;
|
||||
ParentId = parentId;
|
||||
}
|
||||
|
||||
public T Original => Target;
|
||||
|
||||
public T Scaffold { get; }
|
||||
|
||||
public int ParentId { get; }
|
||||
}
|
||||
@@ -4,64 +4,156 @@ using Umbraco.Cms.Core.Models.Editors;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a builder collection for <see cref="IDataValueReferenceFactory" /> items.
|
||||
/// </summary>
|
||||
public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataValueReferenceFactory>
|
||||
{
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
|
||||
: base(items)
|
||||
{
|
||||
}
|
||||
|
||||
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
|
||||
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
|
||||
public IEnumerable<UmbracoEntityReference> GetAllReferences(
|
||||
IPropertyCollection properties,
|
||||
PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
var trackedRelations = new HashSet<UmbracoEntityReference>();
|
||||
|
||||
foreach (IProperty p in properties)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
|
||||
: base(items)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique references from the specified properties.
|
||||
/// </summary>
|
||||
/// <param name="properties">The properties.</param>
|
||||
/// <param name="propertyEditors">The property editors.</param>
|
||||
/// <returns>
|
||||
/// The unique references from the specified properties.
|
||||
/// </returns>
|
||||
public ISet<UmbracoEntityReference> GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
var references = new HashSet<UmbracoEntityReference>();
|
||||
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
{
|
||||
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor))
|
||||
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: We will need to change this once we support tracking via variants/segments
|
||||
// for now, we are tracking values from ALL variants
|
||||
foreach (IPropertyValue propertyVal in p.Values)
|
||||
// Use distinct values to avoid duplicate parsing of the same value
|
||||
var values = new HashSet<object?>(properties.Count);
|
||||
foreach (IPropertyValue propertyValue in propertyValuesByPropertyEditorAlias.SelectMany(x => x))
|
||||
{
|
||||
var val = propertyVal.EditedValue;
|
||||
values.Add(propertyValue.EditedValue);
|
||||
values.Add(propertyValue.PublishedValue);
|
||||
}
|
||||
|
||||
IDataValueEditor? valueEditor = editor?.GetValueEditor();
|
||||
if (valueEditor is IDataValueReference reference)
|
||||
{
|
||||
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
|
||||
foreach (UmbracoEntityReference r in refs)
|
||||
{
|
||||
trackedRelations.Add(r);
|
||||
}
|
||||
}
|
||||
references.UnionWith(GetReferences(dataEditor, values));
|
||||
}
|
||||
|
||||
// Loop over collection that may be add to existing property editors
|
||||
// implementation of GetReferences in IDataValueReference.
|
||||
// Allows developers to add support for references by a
|
||||
// package /property editor that did not implement IDataValueReference themselves
|
||||
foreach (IDataValueReferenceFactory item in this)
|
||||
{
|
||||
// Check if this value reference is for this datatype/editor
|
||||
// Then call it's GetReferences method - to see if the value stored
|
||||
// in the dataeditor/property has referecnes to media/content items
|
||||
if (item.IsForEditor(editor))
|
||||
{
|
||||
foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val))
|
||||
{
|
||||
trackedRelations.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return references;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the references.
|
||||
/// </summary>
|
||||
/// <param name="dataEditor">The data editor.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <returns>
|
||||
/// The references.
|
||||
/// </returns>
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, params object?[] values)
|
||||
=> GetReferences(dataEditor, (IEnumerable<object?>)values);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the references.
|
||||
/// </summary>
|
||||
/// <param name="dataEditor">The data editor.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <returns>
|
||||
/// The references.
|
||||
/// </returns>
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values)
|
||||
{
|
||||
// TODO: We will need to change this once we support tracking via variants/segments
|
||||
// for now, we are tracking values from ALL variants
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
|
||||
{
|
||||
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return trackedRelations;
|
||||
// Loop over collection that may be add to existing property editors
|
||||
// implementation of GetReferences in IDataValueReference.
|
||||
// Allows developers to add support for references by a
|
||||
// package /property editor that did not implement IDataValueReference themselves
|
||||
foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
|
||||
{
|
||||
// Check if this value reference is for this datatype/editor
|
||||
// Then call it's GetReferences method - to see if the value stored
|
||||
// in the dataeditor/property has references to media/content items
|
||||
if (dataValueReferenceFactory.IsForEditor(dataEditor))
|
||||
{
|
||||
IDataValueReference factoryDataValueReference = dataValueReferenceFactory.GetDataValueReference();
|
||||
foreach (UmbracoEntityReference reference in values.SelectMany(factoryDataValueReference.GetReferences))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all relation type aliases that are automatically tracked.
|
||||
/// </summary>
|
||||
/// <param name="propertyEditors">The property editors.</param>
|
||||
/// <returns>
|
||||
/// All relation type aliases that are automatically tracked.
|
||||
/// </returns>
|
||||
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
// Always add default automatic relation types
|
||||
var automaticRelationTypeAliases = new HashSet<string>(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
|
||||
|
||||
// Add relation types for all property editors
|
||||
foreach (IDataEditor dataEditor in propertyEditors)
|
||||
{
|
||||
automaticRelationTypeAliases.UnionWith(GetAutomaticRelationTypesAliases(dataEditor));
|
||||
}
|
||||
|
||||
return automaticRelationTypeAliases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the automatic relation types aliases.
|
||||
/// </summary>
|
||||
/// <param name="dataEditor">The data editor.</param>
|
||||
/// <returns>
|
||||
/// The automatic relation types aliases.
|
||||
/// </returns>
|
||||
public IEnumerable<string> GetAutomaticRelationTypesAliases(IDataEditor dataEditor)
|
||||
{
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
|
||||
{
|
||||
// Return custom relation types from value editor implementation
|
||||
foreach (var alias in dataValueReference.GetAutomaticRelationTypesAliases())
|
||||
{
|
||||
yield return alias;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (IDataValueReferenceFactory dataValueReferenceFactory in this)
|
||||
{
|
||||
if (dataValueReferenceFactory.IsForEditor(dataEditor))
|
||||
{
|
||||
// Return custom relation types from factory
|
||||
foreach (var alias in dataValueReferenceFactory.GetDataValueReference().GetAutomaticRelationTypesAliases())
|
||||
{
|
||||
yield return alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,22 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
/// </summary>
|
||||
public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures)
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
|
||||
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
{
|
||||
yield return new KeyValuePair<string, IEnumerable<object?>>(
|
||||
property.Alias,
|
||||
property.GetValue(culture, segment, published).Yield());
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use the non-obsolete overload, scheduled for removal in v14")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture,
|
||||
string? segment, bool published, IEnumerable<string> availableCultures)
|
||||
=> GetIndexValues(property, culture, segment, published, availableCultures,
|
||||
new Dictionary<Guid, IContentType>());
|
||||
|
||||
[Obsolete("Use the non-obsolete overload, scheduled for removal in v14")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
|
||||
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>());
|
||||
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>(), new Dictionary<Guid, IContentType>());
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@ public class DefaultPropertyValueConverterAttribute : Attribute
|
||||
/// a DefaultPropertyValueConverter can be more specific than another one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An example where this is useful is that both the RelatedLiksEditorValueConverter and the JsonValueConverter
|
||||
/// An example where this is useful is that both the MultiUrlPickerValueConverter and the JsonValueConverter
|
||||
/// will be returned as value converters for the Related Links Property editor, however the JsonValueConverter
|
||||
/// is a very generic converter and the RelatedLiksEditorValueConverter is more specific than it, so the
|
||||
/// RelatedLiksEditorValueConverter
|
||||
/// can specify that it 'shadows' the JsonValueConverter.
|
||||
/// is a very generic converter and the MultiUrlPickerValueConverter is more specific than it, so the
|
||||
/// MultiUrlPickerValueConverter can specify that it 'shadows' the JsonValueConverter.
|
||||
/// </remarks>
|
||||
public Type[] DefaultConvertersToShadow { get; }
|
||||
}
|
||||
|
||||
@@ -22,9 +22,14 @@ public interface IPropertyIndexValueFactory
|
||||
/// more than one value for a given field.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture,
|
||||
string? segment, bool published, IEnumerable<string> availableCultures,
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary) => GetIndexValues(property, culture, segment, published);
|
||||
|
||||
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures)
|
||||
=> GetIndexValues(property, culture, segment, published);
|
||||
|
||||
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
|
||||
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -39,13 +40,13 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures)
|
||||
IEnumerable<string> availableCultures,
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
{
|
||||
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
|
||||
@@ -63,7 +64,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
return result;
|
||||
}
|
||||
|
||||
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures));
|
||||
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures, contentTypeDictionary));
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
@@ -87,9 +88,31 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
return summary;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 14.")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures)
|
||||
=> GetIndexValues(
|
||||
property,
|
||||
culture,
|
||||
segment,
|
||||
published,
|
||||
Enumerable.Empty<string>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
[Obsolete("Use method overload that has availableCultures, scheduled for removal in v14")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
|
||||
=> GetIndexValues(property, culture, segment, published, Enumerable.Empty<string>());
|
||||
=> GetIndexValues(
|
||||
property,
|
||||
culture,
|
||||
segment,
|
||||
published,
|
||||
Enumerable.Empty<string>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
/// <summary>
|
||||
/// Method to return a list of summary of the content. By default this returns an empty list
|
||||
@@ -104,7 +127,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
/// <summary>
|
||||
/// Method that handle the deserialized object.
|
||||
/// </summary>
|
||||
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
|
||||
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
|
||||
protected abstract IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
@@ -112,6 +135,15 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
string? segment,
|
||||
bool published);
|
||||
|
||||
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
|
||||
protected virtual IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures) => Handle(deserializedPropertyValue, property, culture, segment, published);
|
||||
|
||||
/// <summary>
|
||||
/// Method that handle the deserialized object.
|
||||
/// </summary>
|
||||
@@ -121,6 +153,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures) =>
|
||||
Handle(deserializedPropertyValue, property, culture, segment, published);
|
||||
IEnumerable<string> availableCultures,
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
=> Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// A custom value editor to ensure that macro syntax is parsed when being persisted and formatted correctly for
|
||||
/// display in the editor
|
||||
/// </summary>
|
||||
internal class MarkDownPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
private readonly IMarkdownSanitizer _markdownSanitizer;
|
||||
|
||||
public MarkDownPropertyValueEditor(
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
IMarkdownSanitizer markdownSanitizer)
|
||||
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) => _markdownSanitizer = markdownSanitizer;
|
||||
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editorValue.Value?.ToString()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sanitized = _markdownSanitizer.Sanitize(editorValue.Value.ToString()!);
|
||||
|
||||
return sanitized.NullOrWhiteSpaceAsNull();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
@@ -50,4 +51,11 @@ public class MarkdownPropertyEditor : DataEditor
|
||||
/// <inheritdoc />
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new MarkdownConfigurationEditor(_ioHelper, _editorConfigurationParser);
|
||||
|
||||
/// <summary>
|
||||
/// Create a custom value editor
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<MarkDownPropertyValueEditor>(Attribute!);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
public class NoopPropertyIndexValueFactory : IPropertyIndexValueFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
|
||||
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
=> Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
|
||||
|
||||
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
|
||||
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Xml.XPath;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Xml;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -9,10 +11,24 @@ public abstract class PublishedCacheBase : IPublishedCache
|
||||
{
|
||||
private readonly IVariationContextAccessor? _variationContextAccessor;
|
||||
|
||||
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor) => _variationContextAccessor =
|
||||
variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
|
||||
protected PublishedCacheBase(bool previewDefault) => PreviewDefault = previewDefault;
|
||||
[Obsolete("Use ctor with all parameters. This will be removed in V15")]
|
||||
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor)
|
||||
: this(variationContextAccessor, false)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use ctor with all parameters. This will be removed in V15")]
|
||||
protected PublishedCacheBase(bool previewDefault)
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<IVariationContextAccessor>(), previewDefault)
|
||||
{
|
||||
}
|
||||
|
||||
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor, bool previewDefault)
|
||||
{
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
PreviewDefault = previewDefault;
|
||||
}
|
||||
|
||||
public bool PreviewDefault { get; }
|
||||
|
||||
|
||||
@@ -50,4 +50,28 @@ public class WebPath
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided web path is well-formed according to the specified UriKind.
|
||||
/// </summary>
|
||||
/// <param name="webPath">The web path to check. This can be null.</param>
|
||||
/// <param name="uriKind">The kind of Uri (Absolute, Relative, or RelativeOrAbsolute).</param>
|
||||
/// <returns>
|
||||
/// true if <paramref name="webPath"/> is well-formed; otherwise, false.
|
||||
/// </returns>
|
||||
public static bool IsWellFormedWebPath(string? webPath, UriKind uriKind)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(webPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (webPath.StartsWith("//"))
|
||||
{
|
||||
return uriKind is not UriKind.Relative;
|
||||
}
|
||||
|
||||
return Uri.IsWellFormedUriString(webPath, uriKind);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,12 +167,7 @@ public class ContentPermissions
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (permissionsToCheck == null)
|
||||
{
|
||||
permissionsToCheck = Array.Empty<char>();
|
||||
}
|
||||
|
||||
bool? hasPathAccess = null;
|
||||
bool hasPathAccess;
|
||||
entity = null;
|
||||
|
||||
if (nodeId == Constants.System.Root)
|
||||
@@ -183,19 +178,17 @@ public class ContentPermissions
|
||||
{
|
||||
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
|
||||
}
|
||||
|
||||
if (hasPathAccess.HasValue)
|
||||
else
|
||||
{
|
||||
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
|
||||
}
|
||||
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
|
||||
|
||||
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
|
||||
if (entity == null)
|
||||
{
|
||||
return ContentAccess.NotFound;
|
||||
}
|
||||
if (entity == null)
|
||||
{
|
||||
return ContentAccess.NotFound;
|
||||
}
|
||||
|
||||
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
|
||||
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
|
||||
}
|
||||
|
||||
if (hasPathAccess == false)
|
||||
{
|
||||
@@ -208,7 +201,8 @@ public class ContentPermissions
|
||||
}
|
||||
|
||||
// get the implicit/inherited permissions for the user for this path
|
||||
return CheckPermissionsPath(entity.Path, user, permissionsToCheck)
|
||||
// if there is no entity for this id, than just use the id as the path (i.e. -1 or -20)
|
||||
return CheckPermissionsPath(entity?.Path ?? nodeId.ToString(), user, permissionsToCheck)
|
||||
? ContentAccess.Granted
|
||||
: ContentAccess.Denied;
|
||||
}
|
||||
@@ -235,12 +229,7 @@ public class ContentPermissions
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
if (permissionsToCheck == null)
|
||||
{
|
||||
permissionsToCheck = Array.Empty<char>();
|
||||
}
|
||||
|
||||
bool? hasPathAccess = null;
|
||||
bool hasPathAccess;
|
||||
contentItem = null;
|
||||
|
||||
if (nodeId == Constants.System.Root)
|
||||
@@ -251,19 +240,17 @@ public class ContentPermissions
|
||||
{
|
||||
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
|
||||
}
|
||||
|
||||
if (hasPathAccess.HasValue)
|
||||
else
|
||||
{
|
||||
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
|
||||
}
|
||||
contentItem = _contentService.GetById(nodeId);
|
||||
|
||||
contentItem = _contentService.GetById(nodeId);
|
||||
if (contentItem == null)
|
||||
{
|
||||
return ContentAccess.NotFound;
|
||||
}
|
||||
if (contentItem == null)
|
||||
{
|
||||
return ContentAccess.NotFound;
|
||||
}
|
||||
|
||||
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
|
||||
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
|
||||
}
|
||||
|
||||
if (hasPathAccess == false)
|
||||
{
|
||||
@@ -276,7 +263,8 @@ public class ContentPermissions
|
||||
}
|
||||
|
||||
// get the implicit/inherited permissions for the user for this path
|
||||
return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck)
|
||||
// if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
|
||||
return CheckPermissionsPath(contentItem?.Path ?? nodeId.ToString(), user, permissionsToCheck)
|
||||
? ContentAccess.Granted
|
||||
: ContentAccess.Denied;
|
||||
}
|
||||
@@ -288,8 +276,7 @@ public class ContentPermissions
|
||||
permissionsToCheck = Array.Empty<char>();
|
||||
}
|
||||
|
||||
// get the implicit/inherited permissions for the user for this path,
|
||||
// if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
|
||||
// get the implicit/inherited permissions for the user for this path
|
||||
EntityPermissionSet permission = _userService.GetPermissionsForPath(user, path);
|
||||
|
||||
var allowed = true;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Sanitizer service for the markdown editor.
|
||||
/// </summary>
|
||||
public interface IMarkdownSanitizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Sanitizes Markdown
|
||||
/// </summary>
|
||||
/// <param name="markdown">Markdown to be sanitized</param>
|
||||
/// <returns>Sanitized Markdown</returns>
|
||||
string Sanitize(string markdown);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class NoopMarkdownSanitizer : IMarkdownSanitizer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Sanitize(string markdown) => markdown;
|
||||
}
|
||||
@@ -372,7 +372,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
// TODO: what about culture?
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
// locking the content tree secures content types too
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
@@ -395,6 +395,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
Save(content, userId);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -416,7 +418,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
// locking the content tree secures content types too
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
@@ -431,6 +433,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
Save(content, userId);
|
||||
|
||||
scope.Complete();
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -508,10 +511,11 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <inheritdoc />
|
||||
public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
_documentRepository.PersistContentSchedule(content, contentSchedule);
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2105,7 +2109,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs);
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2140,7 +2144,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs);
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2164,7 +2168,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// (SaveAndPublishBranchOne does *not* do it)
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -2185,8 +2189,10 @@ public class ContentService : RepositoryService, IContentService
|
||||
ICollection<IContent> publishedDocuments,
|
||||
EventMessages evtMsgs,
|
||||
int userId,
|
||||
IReadOnlyCollection<ILanguage> allLangs)
|
||||
IReadOnlyCollection<ILanguage> allLangs,
|
||||
out IDictionary<string, object?> notificationState)
|
||||
{
|
||||
notificationState = new Dictionary<string, object?>();
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
|
||||
// null = do not include
|
||||
@@ -2218,6 +2224,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (result.Success)
|
||||
{
|
||||
publishedDocuments.Add(document);
|
||||
notificationState = savingNotification.State;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -2957,7 +2964,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
@@ -2970,6 +2977,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
@@ -3565,6 +3574,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}");
|
||||
|
||||
scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, evtMsgs));
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -3579,6 +3589,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
_documentBlueprintRepository.Delete(content);
|
||||
scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, evtMsgs));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
@@ -3679,6 +3690,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(blueprints, TreeChangeTypes.Remove, evtMsgs));
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,6 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
|
||||
}
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.GetMany(ids.ToArray());
|
||||
|
||||
@@ -68,7 +68,7 @@ internal class ContentVersionService : IContentVersionService
|
||||
/// <inheritdoc />
|
||||
public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
|
||||
{
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
_documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
|
||||
@@ -77,6 +77,7 @@ internal class ContentVersionService : IContentVersionService
|
||||
|
||||
if (version is null)
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,6 +88,7 @@ internal class ContentVersionService : IContentVersionService
|
||||
var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
|
||||
|
||||
Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,13 +122,14 @@ internal class ContentVersionService : IContentVersionService
|
||||
*
|
||||
* tl;dr lots of scopes to enable other connections to use the DB whilst we work.
|
||||
*/
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
IReadOnlyCollection<ContentVersionMeta>? allHistoricVersions =
|
||||
_documentVersionRepository.GetDocumentVersionsEligibleForCleanup();
|
||||
|
||||
if (allHistoricVersions is null)
|
||||
{
|
||||
scope.Complete();
|
||||
return Array.Empty<ContentVersionMeta>();
|
||||
}
|
||||
|
||||
@@ -149,6 +152,8 @@ internal class ContentVersionService : IContentVersionService
|
||||
|
||||
versionsToDelete.Add(version);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
if (!versionsToDelete.Any())
|
||||
@@ -161,7 +166,7 @@ internal class ContentVersionService : IContentVersionService
|
||||
|
||||
foreach (IEnumerable<ContentVersionMeta> group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount))
|
||||
{
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
var groupEnumerated = group.ToList();
|
||||
@@ -174,12 +179,16 @@ internal class ContentVersionService : IContentVersionService
|
||||
scope.Notifications.Publish(
|
||||
new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
using (_scopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy");
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
return versionsToDelete;
|
||||
|
||||
@@ -608,7 +608,7 @@ namespace Umbraco.Cms.Core.Services.Implement
|
||||
|
||||
public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true);
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
return _dataTypeRepository.FindUsages(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
|
||||
|
||||
var theRest = new List<ContentVersionMeta>();
|
||||
|
||||
using (_scopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
var policyOverrides = _documentVersionRepository.GetCleanupPolicies()?
|
||||
.ToDictionary(x => x.ContentTypeId);
|
||||
@@ -77,6 +77,8 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
|
||||
yield return version;
|
||||
}
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1197,7 +1197,7 @@ namespace Umbraco.Cms.Core.Services
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MediaTree);
|
||||
|
||||
@@ -1210,6 +1210,7 @@ namespace Umbraco.Cms.Core.Services
|
||||
scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteUserLoginsAsync(Guid userOrMemberKey)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -155,8 +157,12 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> DisableAsync(Guid userOrMemberKey, string providerName)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
var result = await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -173,9 +179,10 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
|
||||
/// <inheritdoc />
|
||||
public Task SaveAsync(TwoFactorLogin twoFactorLogin)
|
||||
{
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
_twoFactorLoginRepository.Save(twoFactorLogin);
|
||||
|
||||
scope.Complete();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Umbraco.Extensions;
|
||||
public static class UserServiceExtensions
|
||||
{
|
||||
public static EntityPermission? GetPermissions(this IUserService userService, IUser? user, string path)
|
||||
{
|
||||
return userService.GetAllPermissions(user, path).FirstOrDefault();
|
||||
}
|
||||
|
||||
public static EntityPermissionCollection GetAllPermissions(this IUserService userService, IUser? user, string path)
|
||||
{
|
||||
var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x =>
|
||||
@@ -23,7 +28,7 @@ public static class UserServiceExtensions
|
||||
" could not be parsed into an array of integers or the path was empty");
|
||||
}
|
||||
|
||||
return userService.GetPermissions(user, ids[^1]).FirstOrDefault();
|
||||
return userService.GetPermissions(user, ids[^1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Umbraco.Cms.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Makes a code block timed (take at least a certain amount of time). This class cannot be inherited.
|
||||
/// </summary>
|
||||
public sealed class TimedScope : IDisposable, IAsyncDisposable
|
||||
{
|
||||
private readonly TimeSpan _duration;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly Stopwatch _stopwatch;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed time.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The elapsed time.
|
||||
/// </value>
|
||||
public TimeSpan Elapsed => _stopwatch.Elapsed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining time.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The remaining time.
|
||||
/// </value>
|
||||
public TimeSpan Remaining
|
||||
=> TryGetRemaining(out TimeSpan remaining) ? remaining : TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimedScope" /> class.
|
||||
/// </summary>
|
||||
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
|
||||
public TimedScope(long millisecondsDuration)
|
||||
: this(TimeSpan.FromMilliseconds(millisecondsDuration))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimedScope" /> class.
|
||||
/// </summary>
|
||||
/// <param name="millisecondsDuration">The number of milliseconds the scope should at least take.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public TimedScope(long millisecondsDuration, CancellationToken cancellationToken)
|
||||
: this(TimeSpan.FromMilliseconds(millisecondsDuration), cancellationToken)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimedScope"/> class.
|
||||
/// </summary>
|
||||
/// <param name="duration">The duration the scope should at least take.</param>
|
||||
public TimedScope(TimeSpan duration)
|
||||
: this(duration, new CancellationTokenSource())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimedScope" /> class.
|
||||
/// </summary>
|
||||
/// <param name="duration">The duration the scope should at least take.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public TimedScope(TimeSpan duration, CancellationToken cancellationToken)
|
||||
: this(duration, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||
{ }
|
||||
|
||||
private TimedScope(TimeSpan duration, CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
_duration = duration;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
_stopwatch = new Stopwatch();
|
||||
_stopwatch.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the timed scope.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
=> _cancellationTokenSource.Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will block using <see cref="Thread.Sleep(TimeSpan)" /> until the remaining time has elapsed, if not cancelled.
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested is false &&
|
||||
TryGetRemaining(out TimeSpan remaining))
|
||||
{
|
||||
Thread.Sleep(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous dispose operation.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This will delay using <see cref="Task.Delay(TimeSpan, CancellationToken)" /> until the remaining time has elapsed, if not cancelled.
|
||||
/// </remarks>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_cancellationTokenSource.IsCancellationRequested is false &&
|
||||
TryGetRemaining(out TimeSpan remaining))
|
||||
{
|
||||
await Task.Delay(remaining, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetRemaining(out TimeSpan remaining)
|
||||
{
|
||||
remaining = _duration.Subtract(Elapsed);
|
||||
|
||||
return remaining > TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<PackageId>Umbraco.Cms.Core</PackageId>
|
||||
<Title>Umbraco CMS - Core</Title>
|
||||
@@ -7,22 +7,22 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" Version="6.0.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations"/>
|
||||
<PackageReference Include="System.ComponentModel.Annotations" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" />
|
||||
<PackageReference Include="System.Runtime.Caching" />
|
||||
<!-- Add dependencies that we force an update to, even that we do not use them explicitly and they seems to be taken from the framework instead of from Nuget -->
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Security.Cryptography.Xml" Version="6.0.1" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.Http" />
|
||||
<PackageReference Include="System.Security.Cryptography.Xml" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Examine" Version="3.0.1" />
|
||||
<PackageReference Include="Examine" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -67,15 +67,6 @@ public class DistributedCacheBinder :
|
||||
_distributedCache.RefreshContentCache(notification.Changes.ToArray());
|
||||
}
|
||||
|
||||
// private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs<IContent> e)
|
||||
// {
|
||||
// _distributedCache.RefreshUnpublishedPageCache(e.SavedEntities.ToArray());
|
||||
// }
|
||||
|
||||
// private void ContentService_DeletedBlueprint(IContentService sender, DeleteEventArgs<IContent> e)
|
||||
// {
|
||||
// _distributedCache.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray());
|
||||
// }
|
||||
#endregion
|
||||
|
||||
#region LocalizationService / Dictionary
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
@@ -132,7 +132,13 @@ public static class DistributedCacheExtensions
|
||||
|
||||
public static void RefreshAllContentCache(this DistributedCache dc)
|
||||
{
|
||||
ContentCacheRefresher.JsonPayload[] payloads = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
|
||||
ContentCacheRefresher.JsonPayload[] payloads = new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
ChangeTypes = TreeChangeTypes.RefreshAll
|
||||
}
|
||||
};
|
||||
|
||||
// note: refresh all content cache does refresh content types too
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
@@ -145,8 +151,13 @@ public static class DistributedCacheExtensions
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<ContentCacheRefresher.JsonPayload> payloads = changes
|
||||
.Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes));
|
||||
IEnumerable<ContentCacheRefresher.JsonPayload> payloads = changes.Select(x => new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
Id = x.Item.Id,
|
||||
Key = x.Item.Key,
|
||||
ChangeTypes = x.ChangeTypes,
|
||||
Blueprint = x.Item.Blueprint
|
||||
});
|
||||
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
}
|
||||
|
||||
@@ -358,10 +358,13 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder
|
||||
.AddNotificationHandler<ContentSavingNotification, BlockListPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentCopyingNotification, BlockListPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentScaffoldedNotification, BlockListPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentSavingNotification, BlockGridPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentCopyingNotification, BlockGridPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentScaffoldedNotification, BlockGridPropertyNotificationHandler>()
|
||||
.AddNotificationHandler<ContentSavingNotification, NestedContentPropertyHandler>()
|
||||
.AddNotificationHandler<ContentCopyingNotification, NestedContentPropertyHandler>()
|
||||
.AddNotificationHandler<ContentScaffoldedNotification, NestedContentPropertyHandler>()
|
||||
.AddNotificationHandler<ContentCopiedNotification, FileUploadPropertyEditor>()
|
||||
.AddNotificationHandler<ContentDeletedNotification, FileUploadPropertyEditor>()
|
||||
.AddNotificationHandler<MediaDeletedNotification, FileUploadPropertyEditor>()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Examine;
|
||||
@@ -24,9 +27,26 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
|
||||
|
||||
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
|
||||
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values)
|
||||
=> AddPropertyValue(property, culture, segment, values, Enumerable.Empty<string>());
|
||||
=> AddPropertyValue(
|
||||
property,
|
||||
culture,
|
||||
segment,
|
||||
values,
|
||||
Enumerable.Empty<string>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures)
|
||||
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
|
||||
protected void AddPropertyValue(IProperty property, string? culture, string? segment,
|
||||
IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures)
|
||||
=> AddPropertyValue(
|
||||
property,
|
||||
culture,
|
||||
segment,
|
||||
values,
|
||||
Enumerable.Empty<string>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
protected void AddPropertyValue(IProperty property, string? culture, string? segment, IDictionary<string, IEnumerable<object?>>? values, IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
{
|
||||
IDataEditor? editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
|
||||
if (editor == null)
|
||||
@@ -35,7 +55,7 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
|
||||
}
|
||||
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> indexVals =
|
||||
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly, availableCultures);
|
||||
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly, availableCultures, contentTypeDictionary);
|
||||
foreach (KeyValuePair<string, IEnumerable<object?>> keyVal in indexVals)
|
||||
{
|
||||
if (keyVal.Key.IsNullOrWhiteSpace())
|
||||
|
||||
@@ -21,13 +21,34 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
private static readonly object[] NoValue = new[] { "n" };
|
||||
private static readonly object[] YesValue = new[] { "y" };
|
||||
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly ICoreScopeProvider _scopeProvider;
|
||||
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public ContentValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
IUserService userService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
ICoreScopeProvider scopeProvider,
|
||||
bool publishedValuesOnly,
|
||||
ILocalizationService localizationService,
|
||||
IContentTypeService contentTypeService)
|
||||
: base(propertyEditors, publishedValuesOnly)
|
||||
{
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
_userService = userService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_scopeProvider = scopeProvider;
|
||||
_localizationService = localizationService;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
public ContentValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
@@ -36,16 +57,20 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
IScopeProvider scopeProvider,
|
||||
bool publishedValuesOnly,
|
||||
ILocalizationService localizationService)
|
||||
: base(propertyEditors, publishedValuesOnly)
|
||||
: this(
|
||||
propertyEditors,
|
||||
urlSegmentProviders,
|
||||
userService,
|
||||
shortStringHelper,
|
||||
scopeProvider,
|
||||
publishedValuesOnly,
|
||||
localizationService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
{
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
_userService = userService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_scopeProvider = scopeProvider;
|
||||
_localizationService = localizationService;
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that takes an ILocalizationService, scheduled for removal in v14")]
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
public ContentValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
@@ -60,7 +85,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
shortStringHelper,
|
||||
scopeProvider,
|
||||
publishedValuesOnly,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>())
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -72,7 +98,7 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
|
||||
// We can lookup all of the creator/writer names at once which can save some
|
||||
// processing below instead of one by one.
|
||||
using (IScope scope = _scopeProvider.CreateScope())
|
||||
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
|
||||
{
|
||||
creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray())
|
||||
.ToDictionary(x => x.Id, x => x);
|
||||
@@ -86,6 +112,8 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
|
||||
private IEnumerable<ValueSet> GetValueSetsEnumerable(IContent[] content, Dictionary<int, IProfile> creatorIds, Dictionary<int, IProfile> writerIds)
|
||||
{
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
|
||||
|
||||
// TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways
|
||||
// but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since
|
||||
// Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]`
|
||||
@@ -162,13 +190,13 @@ public class ContentValueSetBuilder : BaseValueSetBuilder<IContent>, IContentVal
|
||||
{
|
||||
if (!property.PropertyType.VariesByCulture())
|
||||
{
|
||||
AddPropertyValue(property, null, null, values, availableCultures);
|
||||
AddPropertyValue(property, null, null, values, availableCultures, contentTypeDictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var culture in c.AvailableCultures)
|
||||
{
|
||||
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures);
|
||||
AddPropertyValue(property, culture.ToLowerInvariant(), null, values, availableCultures, contentTypeDictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,17 @@ public class ExamineIndexRebuilder : IIndexRebuilder
|
||||
_logger.LogInformation("Starting async background thread for rebuilding index {indexName}.", indexName);
|
||||
|
||||
_backgroundTaskQueue.QueueBackgroundWorkItem(
|
||||
cancellationToken => Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken)));
|
||||
cancellationToken =>
|
||||
{
|
||||
// Do not flow AsyncLocal to the child thread
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
Task.Run(() => RebuildIndex(indexName, delay.Value, cancellationToken));
|
||||
|
||||
// immediately return so the queue isn't waiting.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -93,12 +103,16 @@ public class ExamineIndexRebuilder : IIndexRebuilder
|
||||
_backgroundTaskQueue.QueueBackgroundWorkItem(
|
||||
cancellationToken =>
|
||||
{
|
||||
// This is a fire/forget task spawned by the background thread queue (which means we
|
||||
// don't need to worry about ExecutionContext flowing).
|
||||
Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
|
||||
// Do not flow AsyncLocal to the child thread
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
// This is a fire/forget task spawned by the background thread queue (which means we
|
||||
// don't need to worry about ExecutionContext flowing).
|
||||
Task.Run(() => RebuildIndexes(onlyEmptyIndexes, delay.Value, cancellationToken));
|
||||
|
||||
// immediately return so the queue isn't waiting.
|
||||
return Task.CompletedTask;
|
||||
// immediately return so the queue isn't waiting.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Examine;
|
||||
@@ -14,6 +16,7 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
|
||||
private readonly ContentSettings _contentSettings;
|
||||
private readonly MediaUrlGeneratorCollection _mediaUrlGenerators;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
@@ -23,19 +26,41 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
|
||||
MediaUrlGeneratorCollection mediaUrlGenerators,
|
||||
IUserService userService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IOptions<ContentSettings> contentSettings)
|
||||
IOptions<ContentSettings> contentSettings,
|
||||
IContentTypeService contentTypeService)
|
||||
: base(propertyEditors, false)
|
||||
{
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
_mediaUrlGenerators = mediaUrlGenerators;
|
||||
_userService = userService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_contentTypeService = contentTypeService;
|
||||
_contentSettings = contentSettings.Value;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
public MediaValueSetBuilder(
|
||||
PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
MediaUrlGeneratorCollection mediaUrlGenerators,
|
||||
IUserService userService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IOptions<ContentSettings> contentSettings)
|
||||
: this(propertyEditors,
|
||||
urlSegmentProviders,
|
||||
mediaUrlGenerators,
|
||||
userService,
|
||||
shortStringHelper,
|
||||
contentSettings,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
{
|
||||
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<ValueSet> GetValueSets(params IMedia[] media)
|
||||
{
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
|
||||
|
||||
foreach (IMedia m in media)
|
||||
{
|
||||
var urlValue = m.GetUrlSegment(_shortStringHelper, _urlSegmentProviders);
|
||||
@@ -65,7 +90,7 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
|
||||
|
||||
foreach (IProperty property in m.Properties)
|
||||
{
|
||||
AddPropertyValue(property, null, null, values, m.AvailableCultures);
|
||||
AddPropertyValue(property, null, null, values, m.AvailableCultures, contentTypeDictionary);
|
||||
}
|
||||
|
||||
var vs = new ValueSet(m.Id.ToInvariantString(), IndexTypes.Media, m.ContentType.Alias, values);
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Examine;
|
||||
|
||||
public class MemberValueSetBuilder : BaseValueSetBuilder<IMember>
|
||||
{
|
||||
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors)
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors, IContentTypeService contentTypeService)
|
||||
: base(propertyEditors, false)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete ctor, scheduled for removal in v14")]
|
||||
public MemberValueSetBuilder(PropertyEditorCollection propertyEditors)
|
||||
: this(propertyEditors, StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<ValueSet> GetValueSets(params IMember[] members)
|
||||
{
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary = _contentTypeService.GetAll().ToDictionary(x => x.Key);
|
||||
|
||||
foreach (IMember m in members)
|
||||
{
|
||||
var values = new Dictionary<string, IEnumerable<object?>>
|
||||
@@ -37,7 +51,7 @@ public class MemberValueSetBuilder : BaseValueSetBuilder<IMember>
|
||||
|
||||
foreach (IProperty property in m.Properties)
|
||||
{
|
||||
AddPropertyValue(property, null, null, values, m.AvailableCultures);
|
||||
AddPropertyValue(property, null, null, values, m.AvailableCultures, contentTypeDictionary);
|
||||
}
|
||||
|
||||
var vs = new ValueSet(m.Id.ToInvariantString(), IndexTypes.Member, m.ContentType.Alias, values);
|
||||
|
||||
@@ -94,7 +94,7 @@ public class ScheduledPublishing : RecurringHostedServiceBase
|
||||
// but then what should be its "scope"? could we attach it to scopes?
|
||||
// - and we should definitively *not* have to flush it here (should be auto)
|
||||
using UmbracoContextReference contextReference = _umbracoContextFactory.EnsureUmbracoContext();
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
/* We used to assume that there will never be two instances running concurrently where (IsMainDom && ServerRole == SchedulingPublisher)
|
||||
* However this is possible during an azure deployment slot swap for the SchedulingPublisher instance when trying to achieve zero downtime deployments.
|
||||
@@ -113,6 +113,8 @@ public class ScheduledPublishing : RecurringHostedServiceBase
|
||||
grouped.Count(),
|
||||
grouped.Key);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class ThreadAbortExceptionEnricher : ILogEventEnricher
|
||||
|
||||
private void DumpThreadAborts(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (!IsTimeoutThreadAbortException(logEvent.Exception))
|
||||
if (logEvent.Exception is null || !IsTimeoutThreadAbortException(logEvent.Exception))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope;
|
||||
|
||||
namespace Umbraco.Cms.Core.Logging.Viewer;
|
||||
@@ -10,6 +13,21 @@ public class LogViewerConfig : ILogViewerConfig
|
||||
private readonly ILogViewerQueryRepository _logViewerQueryRepository;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
[Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")]
|
||||
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider scopeProvider)
|
||||
: this(logViewerQueryRepository, StaticServiceProvider.Instance.GetRequiredService<IScopeProvider>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//Temp ctor used by MSDI (Greedy)
|
||||
[Obsolete("Use non-obsolete ctor. This will be removed in Umbraco 14.")]
|
||||
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, Umbraco.Cms.Core.Scoping.IScopeProvider coreScopeProvider, IScopeProvider scopeProvider)
|
||||
: this(logViewerQueryRepository, scopeProvider)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LogViewerConfig(ILogViewerQueryRepository logViewerQueryRepository, IScopeProvider scopeProvider)
|
||||
{
|
||||
_logViewerQueryRepository = logViewerQueryRepository;
|
||||
@@ -26,15 +44,16 @@ public class LogViewerConfig : ILogViewerConfig
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? AddSavedSearch(string? name, string? query)
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
using IScope scope = _scopeProvider.CreateScope();
|
||||
_logViewerQueryRepository.Save(new LogViewerQuery(name, query));
|
||||
|
||||
scope.Complete();
|
||||
return GetSavedSearches();
|
||||
}
|
||||
|
||||
public IReadOnlyList<SavedLogSearch>? DeleteSavedSearch(string? name, string? query)
|
||||
{
|
||||
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
|
||||
using IScope scope = _scopeProvider.CreateScope();
|
||||
ILogViewerQuery? item = name is null ? null : _logViewerQueryRepository.GetByName(name);
|
||||
if (item is not null)
|
||||
{
|
||||
@@ -42,6 +61,8 @@ public class LogViewerConfig : ILogViewerConfig
|
||||
}
|
||||
|
||||
// Return the updated object - so we can instantly reset the entire array from the API response
|
||||
return GetSavedSearches();
|
||||
IReadOnlyList<SavedLogSearch> result = GetSavedSearches()!;
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,11 @@ public class ManifestParser : IManifestParser
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
|
||||
var files = Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories);
|
||||
|
||||
// Ensure a consistent, alphabetical sorting of paths, because this is not guaranteed to be the same between file systems or OSes
|
||||
Array.Sort(files);
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public static class PublishedModelUtility
|
||||
switch (itemType)
|
||||
{
|
||||
case PublishedItemType.Content:
|
||||
case PublishedItemType.Element:
|
||||
return publishedSnapshot.Content?.GetContentType(alias);
|
||||
case PublishedItemType.Media:
|
||||
return publishedSnapshot.Media?.GetContentType(alias);
|
||||
|
||||
@@ -37,5 +37,5 @@ internal class AccessDto
|
||||
|
||||
[ResultColumn]
|
||||
[Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")]
|
||||
public List<AccessRuleDto> Rules { get; set; } = null!;
|
||||
public List<AccessRuleDto> Rules { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Data.Common;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
||||
|
||||
@@ -33,4 +34,7 @@ public interface IUmbracoDatabase : IDatabase
|
||||
bool IsUmbracoInstalled();
|
||||
|
||||
DatabaseSchemaResult ValidateSchema();
|
||||
|
||||
/// <returns>The number of rows affected.</returns>
|
||||
int ExecuteNonQuery(DbCommand command) => command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
+60
-64
@@ -1082,82 +1082,78 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
|
||||
protected void PersistRelations(TEntity entity)
|
||||
{
|
||||
// Get all references from our core built in DataEditors/Property Editors
|
||||
// Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
|
||||
var trackedRelations = new List<UmbracoEntityReference>();
|
||||
trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
|
||||
// Get all references and automatic relation type aliases
|
||||
ISet<UmbracoEntityReference> references = _dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors);
|
||||
ISet<string> automaticRelationTypeAliases = _dataValueReferenceFactories.GetAllAutomaticRelationTypesAliases(PropertyEditors);
|
||||
|
||||
var relationTypeAliases = GetAutomaticRelationTypesAliases(entity.Properties, PropertyEditors).ToArray();
|
||||
|
||||
// First delete all auto-relations for this entity
|
||||
RelationRepository.DeleteByParent(entity.Id, relationTypeAliases);
|
||||
|
||||
if (trackedRelations.Count == 0)
|
||||
if (references.Count == 0)
|
||||
{
|
||||
// Delete all relations using the automatic relation type aliases
|
||||
RelationRepository.DeleteByParent(entity.Id, automaticRelationTypeAliases.ToArray());
|
||||
|
||||
// No need to add new references/relations
|
||||
return;
|
||||
}
|
||||
|
||||
trackedRelations = trackedRelations.Distinct().ToList();
|
||||
var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
|
||||
.ToDictionary(x => (Udi)x!, x => x!.Guid);
|
||||
// Lookup all relation type IDs
|
||||
var relationTypeLookup = RelationTypeRepository.GetMany(Array.Empty<int>())
|
||||
.Where(x => automaticRelationTypeAliases.Contains(x.Alias))
|
||||
.ToDictionary(x => x.Alias, x => x.Id);
|
||||
|
||||
// lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
|
||||
var keyToIds = Database.Fetch<NodeIdKey>(Sql()
|
||||
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
|
||||
.ToDictionary(x => x.UniqueId, x => x.NodeId);
|
||||
|
||||
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())?
|
||||
.ToDictionary(x => x.Alias, x => x);
|
||||
|
||||
IEnumerable<ReadOnlyRelation> toSave = trackedRelations.Select(rel =>
|
||||
{
|
||||
if (allRelationTypes is null || !allRelationTypes.TryGetValue(rel.RelationTypeAlias, out IRelationType? relationType))
|
||||
{
|
||||
throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
|
||||
}
|
||||
|
||||
if (!udiToGuids.TryGetValue(rel.Udi, out Guid guid))
|
||||
{
|
||||
return null; // This shouldn't happen!
|
||||
}
|
||||
|
||||
if (!keyToIds.TryGetValue(guid, out var id))
|
||||
{
|
||||
return null; // This shouldn't happen!
|
||||
}
|
||||
|
||||
return new ReadOnlyRelation(entity.Id, id, relationType.Id);
|
||||
}).WhereNotNull();
|
||||
|
||||
// Save bulk relations
|
||||
RelationRepository.SaveBulk(toSave);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAutomaticRelationTypesAliases(
|
||||
IPropertyCollection properties,
|
||||
PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
var automaticRelationTypesAliases = new HashSet<string>(Constants.Conventions.RelationTypes.AutomaticRelationTypes);
|
||||
|
||||
foreach (IProperty property in properties)
|
||||
// Lookup node IDs for all GUID based UDIs
|
||||
IEnumerable<Guid> keys = references.Select(x => x.Udi).OfType<GuidUdi>().Select(x => x.Guid);
|
||||
var keysLookup = Database.FetchByGroups<NodeIdKey, Guid>(keys, Constants.Sql.MaxParameterCount, guids =>
|
||||
{
|
||||
if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out IDataEditor? editor) is false )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return Sql()
|
||||
.Select<NodeDto>(x => x.NodeId, x => x.UniqueId)
|
||||
.From<NodeDto>()
|
||||
.WhereIn<NodeDto>(x => x.UniqueId, guids);
|
||||
}).ToDictionary(x => x.UniqueId, x => x.NodeId);
|
||||
|
||||
if (editor.GetValueEditor() is IDataValueReference reference)
|
||||
// Get all valid relations
|
||||
var relations = new List<(int ChildId, int RelationTypeId)>(references.Count);
|
||||
foreach (UmbracoEntityReference reference in references)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reference.RelationTypeAlias))
|
||||
{
|
||||
foreach (var alias in reference.GetAutomaticRelationTypesAliases())
|
||||
{
|
||||
automaticRelationTypesAliases.Add(alias);
|
||||
}
|
||||
// Reference does not specify a relation type alias, so skip adding a relation
|
||||
Logger.LogDebug("The reference to {Udi} does not specify a relation type alias, so it will not be saved as relation.", reference.Udi);
|
||||
}
|
||||
else if (!automaticRelationTypeAliases.Contains(reference.RelationTypeAlias))
|
||||
{
|
||||
// Returning a reference that doesn't use an automatic relation type is an issue that should be fixed in code
|
||||
Logger.LogError("The reference to {Udi} uses a relation type {RelationTypeAlias} that is not an automatic relation type.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (!relationTypeLookup.TryGetValue(reference.RelationTypeAlias, out int relationTypeId))
|
||||
{
|
||||
// A non-existent relation type could be caused by an environment issue (e.g. it was manually removed)
|
||||
Logger.LogWarning("The reference to {Udi} uses a relation type {RelationTypeAlias} that does not exist.", reference.Udi, reference.RelationTypeAlias);
|
||||
}
|
||||
else if (reference.Udi is not GuidUdi udi || !keysLookup.TryGetValue(udi.Guid, out var id))
|
||||
{
|
||||
// Relations only support references to items that are stored in the NodeDto table (because of foreign key constraints)
|
||||
Logger.LogInformation("The reference to {Udi} can not be saved as relation, because doesn't have a node ID.", reference.Udi);
|
||||
}
|
||||
else
|
||||
{
|
||||
relations.Add((id, relationTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
return automaticRelationTypesAliases;
|
||||
// Get all existing relations (optimize for adding new and keeping existing relations)
|
||||
var query = Query<IRelation>().Where(x => x.ParentId == entity.Id).WhereIn(x => x.RelationTypeId, relationTypeLookup.Values);
|
||||
var existingRelations = RelationRepository.GetPagedRelationsByQuery(query, 0, int.MaxValue, out _, null)
|
||||
.ToDictionary(x => (x.ChildId, x.RelationTypeId)); // Relations are unique by parent ID, child ID and relation type ID
|
||||
|
||||
// Add relations that don't exist yet
|
||||
var relationsToAdd = relations.Except(existingRelations.Keys).Select(x => new ReadOnlyRelation(entity.Id, x.ChildId, x.RelationTypeId));
|
||||
RelationRepository.SaveBulk(relationsToAdd);
|
||||
|
||||
// Delete relations that don't exist anymore
|
||||
foreach (IRelation relation in existingRelations.Where(x => !relations.Contains(x.Key)).Select(x => x.Value))
|
||||
{
|
||||
RelationRepository.Delete(relation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+18
-7
@@ -1223,8 +1223,11 @@ AND umbracoNode.id <> @id",
|
||||
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true
|
||||
/// when it should be false
|
||||
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values
|
||||
/// stored for each
|
||||
/// property, culture and current/published version.
|
||||
/// stored for each property, culture and current/published version.
|
||||
///
|
||||
/// Some of the sql statements in this function have a tendency to take a lot of parameters (nodeIds)
|
||||
/// as the WhereIn Npoco method translates all the nodeIds being passed in as parameters when using the SqlClient provider.
|
||||
/// this results in to many parameters (>2100) error => We need to batch the calls
|
||||
/// </remarks>
|
||||
private void RenormalizeDocumentEditedFlags(
|
||||
IReadOnlyCollection<int> propertyTypeIds,
|
||||
@@ -1380,15 +1383,23 @@ AND umbracoNode.id <> @id",
|
||||
// Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
|
||||
foreach (IGrouping<bool, DocumentCultureVariationDto> editValue in toUpdate.GroupBy(x => x.Edited))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
|
||||
// update in batches to account for maximum parameter count
|
||||
foreach (IEnumerable<DocumentCultureVariationDto> batchedValues in editValue.InGroupsOf(Constants.Sql.MaxParameterCount))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentCultureVariationDto>(x => x.Id, batchedValues.Select(x => x.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
// Now bulk update the umbracoDocument table
|
||||
foreach (IGrouping<bool, KeyValuePair<int, bool>> editValue in editedDocument.GroupBy(x => x.Value))
|
||||
foreach (IGrouping<bool, KeyValuePair<int, bool>> groupByValue in editedDocument.GroupBy(x => x.Value))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
|
||||
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
|
||||
// update in batches to account for maximum parameter count
|
||||
foreach (IEnumerable<KeyValuePair<int, bool>> batch in groupByValue.InGroupsOf(Constants.Sql.MaxParameterCount))
|
||||
{
|
||||
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, groupByValue.Key))
|
||||
.WhereIn<DocumentDto>(x => x.NodeId, batch.Select(x => x.Key)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -264,9 +264,16 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
|
||||
_hostingEnvironment.MapPathContentRoot(Path.Combine(
|
||||
_createdPackagesFolderPath,
|
||||
definition.Name.Replace(' ', '_')));
|
||||
|
||||
var expectedRoot = Path.GetFullPath(_hostingEnvironment.MapPathContentRoot(_createdPackagesFolderPath));
|
||||
var finalPackagePath = Path.GetFullPath(Path.Combine(directoryName, fileName));
|
||||
if (finalPackagePath.StartsWith(expectedRoot) == false)
|
||||
{
|
||||
throw new IOException("Invalid path due to the package name");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
var finalPackagePath = Path.Combine(directoryName, fileName);
|
||||
|
||||
// Clean existing files
|
||||
foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath })
|
||||
|
||||
@@ -224,6 +224,14 @@ public class UmbracoDatabase : Database, IUmbracoDatabase
|
||||
return databaseSchemaValidationResult ?? new DatabaseSchemaResult();
|
||||
}
|
||||
|
||||
public int ExecuteNonQuery(DbCommand command)
|
||||
{
|
||||
OnExecutingCommand(command);
|
||||
var i = command.ExecuteNonQuery();
|
||||
OnExecutedCommand(command);
|
||||
return i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if Umbraco database tables are detected to be installed
|
||||
/// </summary>
|
||||
|
||||
@@ -17,12 +17,14 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
|
||||
{
|
||||
private BlockEditorValues? _blockEditorValues;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly ILogger<BlockEditorPropertyValueEditor> _logger;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
|
||||
private readonly ILogger<BlockEditorPropertyValueEditor> _logger;
|
||||
|
||||
protected BlockEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
IDataTypeService dataTypeService,
|
||||
ILocalizedTextService textService,
|
||||
ILogger<BlockEditorPropertyValueEditor> logger,
|
||||
@@ -32,6 +34,7 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
|
||||
: base(textService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataValueReferenceFactories = dataValueReferenceFactories;
|
||||
_dataTypeService = dataTypeService;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -42,73 +45,60 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
|
||||
set => _blockEditorValues = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
{
|
||||
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
|
||||
|
||||
var result = new List<UmbracoEntityReference>();
|
||||
BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
|
||||
if (blockEditorData == null)
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (var valuesByPropertyEditorAlias in GetAllPropertyValues(value).GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Value))
|
||||
{
|
||||
return Enumerable.Empty<UmbracoEntityReference>();
|
||||
}
|
||||
|
||||
// loop through all content and settings data
|
||||
foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
|
||||
{
|
||||
foreach (KeyValuePair<string, BlockItemData.BlockPropertyValue> prop in row.PropertyValues)
|
||||
if (!_propertyEditors.TryGet(valuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
||||
continue;
|
||||
}
|
||||
|
||||
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
|
||||
if (!(valueEditor is IDataValueReference reference))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var val = prop.Value.Value?.ToString();
|
||||
|
||||
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
|
||||
|
||||
result.AddRange(refs);
|
||||
// Use distinct values to avoid duplicate parsing of the same value
|
||||
foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, valuesByPropertyEditorAlias.Distinct()))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
|
||||
{
|
||||
foreach (BlockItemData.BlockPropertyValue propertyValue in GetAllPropertyValues(value))
|
||||
{
|
||||
if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
|
||||
dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
|
||||
foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
|
||||
{
|
||||
yield return tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<BlockItemData.BlockPropertyValue> GetAllPropertyValues(object? value)
|
||||
{
|
||||
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
|
||||
|
||||
BlockEditorData? blockEditorData = BlockEditorValues.DeserializeAndClean(rawJson);
|
||||
if (blockEditorData == null)
|
||||
if (blockEditorData is null)
|
||||
{
|
||||
return Enumerable.Empty<ITag>();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var result = new List<ITag>();
|
||||
// loop through all content and settings data
|
||||
foreach (BlockItemData row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
|
||||
// Return all property values from the content and settings data
|
||||
IEnumerable<BlockItemData> data = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData);
|
||||
foreach (BlockItemData.BlockPropertyValue propertyValue in data.SelectMany(x => x.PropertyValues.Select(x => x.Value)))
|
||||
{
|
||||
foreach (KeyValuePair<string, BlockItemData.BlockPropertyValue> prop in row.PropertyValues)
|
||||
{
|
||||
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
||||
|
||||
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
|
||||
if (valueEditor is not IDataValueTags tagsProvider)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
|
||||
|
||||
result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
|
||||
}
|
||||
yield return propertyValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Convert database // editor
|
||||
@@ -119,7 +109,6 @@ internal abstract class BlockEditorPropertyValueEditor : DataValueEditor, IDataV
|
||||
/// Ensure that sub-editor values are translated through their ToEditor methods
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="dataTypeService"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -50,6 +50,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
|
||||
public BlockGridEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
IDataTypeService dataTypeService,
|
||||
ILocalizedTextService textService,
|
||||
ILogger<BlockEditorPropertyValueEditor> logger,
|
||||
@@ -58,7 +59,7 @@ public abstract class BlockGridPropertyEditorBase : DataEditor
|
||||
IIOHelper ioHelper,
|
||||
IContentTypeService contentTypeService,
|
||||
IPropertyValidationService propertyValidationService)
|
||||
: base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
|
||||
: base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
|
||||
{
|
||||
BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger);
|
||||
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
|
||||
|
||||
@@ -46,6 +46,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
public BlockListEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
IDataTypeService dataTypeService,
|
||||
IContentTypeService contentTypeService,
|
||||
ILocalizedTextService textService,
|
||||
@@ -54,7 +55,7 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
IPropertyValidationService propertyValidationService) :
|
||||
base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
|
||||
base(attribute, propertyEditors, dataValueReferenceFactories,dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
|
||||
{
|
||||
BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, logger);
|
||||
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
|
||||
|
||||
@@ -38,10 +38,13 @@ internal sealed class BlockValuePropertyIndexValueFactory :
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
|
||||
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input) =>
|
||||
_contentTypeService.Get(input.ContentTypeKey);
|
||||
|
||||
protected override IContentType? GetContentTypeOfNestedItem(BlockItemData input, IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
=> contentTypeDictionary.TryGetValue(input.ContentTypeKey, out var result) ? result : null;
|
||||
|
||||
protected override IDictionary<string, object?> GetRawProperty(BlockItemData blockItemData) =>
|
||||
blockItemData.RawPropertyValues;
|
||||
|
||||
|
||||
+14
-1
@@ -10,9 +10,16 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Handles nested Udi keys when
|
||||
/// - saving: Empty keys get generated
|
||||
/// - copy: keys get replaced by new ones while keeping references intact
|
||||
/// - scaffolding: keys get replaced by new ones while keeping references intact
|
||||
/// </summary>
|
||||
public abstract class ComplexPropertyEditorContentNotificationHandler :
|
||||
INotificationHandler<ContentSavingNotification>,
|
||||
INotificationHandler<ContentCopyingNotification>
|
||||
INotificationHandler<ContentCopyingNotification>,
|
||||
INotificationHandler<ContentScaffoldedNotification>
|
||||
{
|
||||
protected abstract string EditorAlias { get; }
|
||||
|
||||
@@ -31,6 +38,12 @@ public abstract class ComplexPropertyEditorContentNotificationHandler :
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(ContentScaffoldedNotification notification)
|
||||
{
|
||||
IEnumerable<IProperty> props = notification.Scaffold.GetPropertiesByEditor(EditorAlias);
|
||||
UpdatePropertyValues(props, false);
|
||||
}
|
||||
|
||||
protected abstract string FormatPropertyValue(string rawJson, bool onlyMissingKeys);
|
||||
|
||||
private void UpdatePropertyValues(IEnumerable<IProperty> props, bool onlyMissingKeys)
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Runtime.Serialization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -26,11 +27,57 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly ILogger<MultiUrlPickerValueEditor> _logger;
|
||||
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IMediaService _mediaService;
|
||||
|
||||
public MultiUrlPickerValueEditor(
|
||||
ILogger<MultiUrlPickerValueEditor> logger,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
DataEditorAttribute attribute,
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
IContentService contentService,
|
||||
IMediaService mediaService)
|
||||
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
_contentService = contentService;
|
||||
_mediaService = mediaService;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 14.")]
|
||||
public MultiUrlPickerValueEditor(
|
||||
IEntityService entityService,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
ILogger<MultiUrlPickerValueEditor> logger,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
DataEditorAttribute attribute,
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
IContentService contentService,
|
||||
IMediaService mediaService)
|
||||
:this(
|
||||
logger,
|
||||
localizedTextService,
|
||||
shortStringHelper,
|
||||
attribute,
|
||||
publishedUrlProvider,
|
||||
jsonSerializer,
|
||||
ioHelper,
|
||||
contentService,
|
||||
mediaService)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 14.")]
|
||||
public MultiUrlPickerValueEditor(
|
||||
IEntityService entityService,
|
||||
IPublishedSnapshotAccessor publishedSnapshotAccessor,
|
||||
@@ -41,13 +88,18 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper)
|
||||
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
: this(
|
||||
logger,
|
||||
localizedTextService,
|
||||
shortStringHelper,
|
||||
attribute,
|
||||
publishedUrlProvider,
|
||||
jsonSerializer,
|
||||
ioHelper,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IMediaService>())
|
||||
{
|
||||
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
|
||||
_publishedSnapshotAccessor = publishedSnapshotAccessor ??
|
||||
throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
@@ -86,26 +138,6 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
{
|
||||
List<LinkDto>? links = JsonConvert.DeserializeObject<List<LinkDto>>(value);
|
||||
|
||||
List<LinkDto>? documentLinks = links?.FindAll(link =>
|
||||
link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Document);
|
||||
List<LinkDto>? mediaLinks = links?.FindAll(link =>
|
||||
link.Udi != null && link.Udi.EntityType == Constants.UdiEntityType.Media);
|
||||
|
||||
var entities = new List<IEntitySlim>();
|
||||
if (documentLinks?.Count > 0)
|
||||
{
|
||||
entities.AddRange(
|
||||
_entityService.GetAll(
|
||||
UmbracoObjectTypes.Document,
|
||||
documentLinks.Select(link => link.Udi!.Guid).ToArray()));
|
||||
}
|
||||
|
||||
if (mediaLinks?.Count > 0)
|
||||
{
|
||||
entities.AddRange(
|
||||
_entityService.GetAll(UmbracoObjectTypes.Media, mediaLinks.Select(link => link.Udi!.Guid).ToArray()));
|
||||
}
|
||||
|
||||
var result = new List<LinkDisplay>();
|
||||
if (links is null)
|
||||
{
|
||||
@@ -114,7 +146,7 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
|
||||
foreach (LinkDto dto in links)
|
||||
{
|
||||
GuidUdi? udi = null;
|
||||
GuidUdi? udi = dto.Udi;
|
||||
var icon = "icon-link";
|
||||
var published = true;
|
||||
var trashed = false;
|
||||
@@ -122,35 +154,30 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
|
||||
if (dto.Udi != null)
|
||||
{
|
||||
IUmbracoEntity? entity = entities.Find(e => e.Key == dto.Udi.Guid);
|
||||
if (entity == null)
|
||||
if (dto.Udi.EntityType == Constants.UdiEntityType.Document)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
url = _publishedUrlProvider.GetUrl(dto.Udi.Guid, UrlMode.Relative, culture);
|
||||
IContent? c = _contentService.GetById(dto.Udi.Guid);
|
||||
|
||||
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
|
||||
if (entity is IDocumentEntitySlim documentEntity)
|
||||
{
|
||||
icon = documentEntity.ContentTypeIcon;
|
||||
published = culture == null
|
||||
? documentEntity.Published
|
||||
: documentEntity.PublishedCultures.Contains(culture);
|
||||
udi = new GuidUdi(Constants.UdiEntityType.Document, documentEntity.Key);
|
||||
url = publishedSnapshot.Content?.GetById(entity.Key)?.Url(_publishedUrlProvider) ?? "#";
|
||||
trashed = documentEntity.Trashed;
|
||||
if (c is not null)
|
||||
{
|
||||
published = culture == null
|
||||
? c.Published
|
||||
: c.PublishedCultures.Contains(culture);
|
||||
icon = c.ContentType.Icon;
|
||||
trashed = c.Trashed;
|
||||
}
|
||||
}
|
||||
else if (entity is IContentEntitySlim contentEntity)
|
||||
else if (dto.Udi.EntityType == Constants.UdiEntityType.Media)
|
||||
{
|
||||
icon = contentEntity.ContentTypeIcon;
|
||||
published = !contentEntity.Trashed;
|
||||
udi = new GuidUdi(Constants.UdiEntityType.Media, contentEntity.Key);
|
||||
url = publishedSnapshot.Media?.GetById(entity.Key)?.Url(_publishedUrlProvider) ?? "#";
|
||||
trashed = contentEntity.Trashed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not supported
|
||||
continue;
|
||||
url = _publishedUrlProvider.GetMediaUrl(dto.Udi.Guid, UrlMode.Relative, culture);
|
||||
IMedia? m = _mediaService.GetById(dto.Udi.Guid);
|
||||
if (m is not null)
|
||||
{
|
||||
published = m.Trashed is false;
|
||||
icon = m.ContentType.Icon;
|
||||
trashed = m.Trashed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,9 +89,10 @@ public class NestedContentPropertyEditor : DataEditor
|
||||
internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference, IDataValueTags
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
|
||||
private readonly ILogger<NestedContentPropertyEditor> _logger;
|
||||
private readonly NestedContentValues _nestedContentValues;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
|
||||
public NestedContentPropertyValueEditor(
|
||||
IDataTypeService dataTypeService,
|
||||
@@ -100,16 +101,19 @@ public class NestedContentPropertyEditor : DataEditor
|
||||
IShortStringHelper shortStringHelper,
|
||||
DataEditorAttribute attribute,
|
||||
PropertyEditorCollection propertyEditors,
|
||||
DataValueReferenceFactoryCollection dataValueReferenceFactories,
|
||||
ILogger<NestedContentPropertyEditor> logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
IPropertyValidationService propertyValidationService)
|
||||
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataTypeService = dataTypeService;
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataValueReferenceFactories = dataValueReferenceFactories;
|
||||
_logger = logger;
|
||||
_nestedContentValues = new NestedContentValues(contentTypeService);
|
||||
|
||||
Validators.Add(new NestedContentValidator(propertyValidationService, _nestedContentValues, contentTypeService));
|
||||
}
|
||||
|
||||
@@ -137,66 +141,47 @@ public class NestedContentPropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
{
|
||||
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
|
||||
|
||||
var result = new List<UmbracoEntityReference>();
|
||||
|
||||
foreach (NestedContentValues.NestedContentRowValue row in _nestedContentValues.GetPropertyValues(rawJson))
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (var valuesByPropertyEditorAlias in GetAllPropertyValues(value).GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Value))
|
||||
{
|
||||
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> prop in
|
||||
row.PropertyValues)
|
||||
if (!_propertyEditors.TryGet(valuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
||||
continue;
|
||||
}
|
||||
|
||||
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
|
||||
if (!(valueEditor is IDataValueReference reference))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var val = prop.Value.Value?.ToString();
|
||||
|
||||
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
|
||||
|
||||
result.AddRange(refs);
|
||||
// Use distinct values to avoid duplicate parsing of the same value
|
||||
foreach (UmbracoEntityReference reference in _dataValueReferenceFactories.GetReferences(dataEditor, valuesByPropertyEditorAlias.Distinct()))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ITag> GetTags(object? value, object? dataTypeConfiguration, int? languageId)
|
||||
{
|
||||
IReadOnlyList<NestedContentValues.NestedContentRowValue> rows =
|
||||
_nestedContentValues.GetPropertyValues(value);
|
||||
|
||||
var result = new List<ITag>();
|
||||
|
||||
foreach (NestedContentValues.NestedContentRowValue row in rows.ToList())
|
||||
foreach (NestedContentValues.NestedContentPropertyValue propertyValue in GetAllPropertyValues(value))
|
||||
{
|
||||
foreach (KeyValuePair<string, NestedContentValues.NestedContentPropertyValue> prop in row.PropertyValues
|
||||
.ToList())
|
||||
if (!_propertyEditors.TryGet(propertyValue.PropertyType.PropertyEditorAlias, out IDataEditor? dataEditor) ||
|
||||
dataEditor.GetValueEditor() is not IDataValueTags dataValueTags)
|
||||
{
|
||||
IDataEditor? propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
||||
continue;
|
||||
}
|
||||
|
||||
IDataValueEditor? valueEditor = propEditor?.GetValueEditor();
|
||||
if (valueEditor is not IDataValueTags tagsProvider)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object? configuration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeKey)?.Configuration;
|
||||
|
||||
result.AddRange(tagsProvider.GetTags(prop.Value.Value, configuration, languageId));
|
||||
object? configuration = _dataTypeService.GetDataType(propertyValue.PropertyType.DataTypeKey)?.Configuration;
|
||||
foreach (ITag tag in dataValueTags.GetTags(propertyValue.Value, configuration, languageId))
|
||||
{
|
||||
yield return tag;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<NestedContentValues.NestedContentPropertyValue> GetAllPropertyValues(object? value)
|
||||
=> _nestedContentValues.GetPropertyValues(value).SelectMany(x => x.PropertyValues.Values);
|
||||
|
||||
#region DB to String
|
||||
|
||||
public override string ConvertDbToString(IPropertyType propertyType, object? propertyValue)
|
||||
@@ -422,7 +407,8 @@ public class NestedContentPropertyEditor : DataEditor
|
||||
// set values to null
|
||||
row.PropertyValues[elementTypeProp.Alias] = new NestedContentValues.NestedContentPropertyValue
|
||||
{
|
||||
PropertyType = elementTypeProp, Value = null,
|
||||
PropertyType = elementTypeProp,
|
||||
Value = null,
|
||||
};
|
||||
row.RawPropertyValues[elementTypeProp.Alias] = null;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ internal sealed class NestedContentPropertyIndexValueFactory
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
protected override IContentType? GetContentTypeOfNestedItem(
|
||||
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input, IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
=> contentTypeDictionary.Values.FirstOrDefault(x=>x.Alias.Equals(input.ContentTypeAlias));
|
||||
|
||||
[Obsolete("Use non-obsolete overload, scheduled for removal in v14")]
|
||||
protected override IContentType? GetContentTypeOfNestedItem(
|
||||
NestedContentPropertyEditor.NestedContentValues.NestedContentRowValue input)
|
||||
=> _contentTypeService.Get(input.ContentTypeAlias);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
@@ -42,20 +43,39 @@ internal abstract class NestedPropertyIndexValueFactoryBase<TSerialized, TItem>
|
||||
bool published) =>
|
||||
Handle(deserializedPropertyValue, property, culture, segment, published, Enumerable.Empty<string>());
|
||||
|
||||
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
|
||||
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures)
|
||||
IEnumerable<string> availableCultures) =>
|
||||
Handle(
|
||||
deserializedPropertyValue,
|
||||
property,
|
||||
culture,
|
||||
segment,
|
||||
published,
|
||||
Enumerable.Empty<string>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures,
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
{
|
||||
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
|
||||
var index = 0;
|
||||
foreach (TItem nestedContentRowValue in GetDataItems(deserializedPropertyValue))
|
||||
{
|
||||
IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue);
|
||||
IContentType? contentType = GetContentTypeOfNestedItem(nestedContentRowValue, contentTypeDictionary);
|
||||
|
||||
if (contentType is null)
|
||||
{
|
||||
@@ -125,6 +145,9 @@ internal abstract class NestedPropertyIndexValueFactoryBase<TSerialized, TItem>
|
||||
/// <summary>
|
||||
/// Gets the content type using the nested item.
|
||||
/// </summary>
|
||||
protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem, IDictionary<Guid, IContentType> contentTypeDictionary);
|
||||
|
||||
[Obsolete("Use non-obsolete overload. Scheduled for removal in Umbraco 14.")]
|
||||
protected abstract IContentType? GetContentTypeOfNestedItem(TItem nestedItem);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,10 +23,9 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
private readonly bool _autoComplete;
|
||||
private readonly CoreDebugSettings _coreDebugSettings;
|
||||
|
||||
private readonly object _dictionaryLocker;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IsolationLevel _isolationLevel;
|
||||
private readonly object _lockQueueLocker = new();
|
||||
private readonly object _locker = new();
|
||||
private readonly ILogger<Scope> _logger;
|
||||
private readonly MediaFileManager _mediaFileManager;
|
||||
private readonly RepositoryCacheMode _repositoryCacheMode;
|
||||
@@ -87,7 +86,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
_scopeFileSystem = scopeFileSystems;
|
||||
_autoComplete = autoComplete;
|
||||
Detachable = detachable;
|
||||
_dictionaryLocker = new object();
|
||||
|
||||
#if DEBUG_SCOPES
|
||||
_scopeProvider.RegisterScope(this);
|
||||
@@ -562,7 +560,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
DisposeLastScope();
|
||||
}
|
||||
|
||||
lock (_lockQueueLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
_queuedLocks?.Clear();
|
||||
}
|
||||
@@ -573,24 +571,24 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
public void EagerReadLock(params int[] lockIds) => EagerReadLockInner(InstanceId, null, lockIds);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadLock(params int[] lockIds) => LazyReadLockInner(InstanceId, lockIds);
|
||||
public void ReadLock(params int[] lockIds) => EagerReadLockInner(InstanceId, null, lockIds);
|
||||
|
||||
public void EagerReadLock(TimeSpan timeout, int lockId) =>
|
||||
EagerReadLockInner(InstanceId, timeout, lockId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadLock(TimeSpan timeout, int lockId) => LazyReadLockInner(InstanceId, timeout, lockId);
|
||||
public void ReadLock(TimeSpan timeout, int lockId) => EagerReadLockInner(InstanceId, timeout, lockId);
|
||||
|
||||
public void EagerWriteLock(params int[] lockIds) => EagerWriteLockInner(InstanceId, null, lockIds);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(params int[] lockIds) => LazyWriteLockInner(InstanceId, lockIds);
|
||||
public void WriteLock(params int[] lockIds) => EagerWriteLockInner(InstanceId, null, lockIds);
|
||||
|
||||
public void EagerWriteLock(TimeSpan timeout, int lockId) =>
|
||||
EagerWriteLockInner(InstanceId, timeout, lockId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteLock(TimeSpan timeout, int lockId) => LazyWriteLockInner(InstanceId, timeout, lockId);
|
||||
public void WriteLock(TimeSpan timeout, int lockId) => EagerWriteLockInner(InstanceId, timeout, lockId);
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing. Ensures and gets any queued read locks.
|
||||
@@ -659,7 +657,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_lockQueueLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
if (_queuedLocks?.Count > 0)
|
||||
{
|
||||
@@ -970,7 +968,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
_readLocksDictionary?.Remove(instanceId);
|
||||
_writeLocksDictionary?.Remove(instanceId);
|
||||
@@ -1045,7 +1043,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
|
||||
private void LazyLockInner(DistributedLockType lockType, Guid instanceId, params int[] lockIds)
|
||||
{
|
||||
lock (_lockQueueLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
if (_queuedLocks == null)
|
||||
{
|
||||
@@ -1061,7 +1059,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
|
||||
private void LazyLockInner(DistributedLockType lockType, Guid instanceId, TimeSpan timeout, int lockId)
|
||||
{
|
||||
lock (_lockQueueLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
if (_queuedLocks == null)
|
||||
{
|
||||
@@ -1088,7 +1086,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
@@ -1122,7 +1120,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_dictionaryLocker)
|
||||
lock (_locker)
|
||||
{
|
||||
foreach (var lockId in lockIds)
|
||||
{
|
||||
|
||||
@@ -51,13 +51,15 @@ public sealed class ContentIndexingNotificationHandler : INotificationHandler<Co
|
||||
|
||||
foreach (ContentCacheRefresher.JsonPayload payload in (ContentCacheRefresher.JsonPayload[])args.MessageObject)
|
||||
{
|
||||
if (payload.Blueprint)
|
||||
{
|
||||
// Skip blueprints
|
||||
continue;
|
||||
}
|
||||
|
||||
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
|
||||
{
|
||||
if (deleteBatch == null)
|
||||
{
|
||||
deleteBatch = new HashSet<int>();
|
||||
}
|
||||
|
||||
deleteBatch ??= new HashSet<int>();
|
||||
deleteBatch.Add(payload.Id);
|
||||
}
|
||||
else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
|
||||
|
||||
@@ -96,7 +96,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
// create member
|
||||
IMember memberEntity = _memberService.CreateMember(
|
||||
@@ -150,6 +150,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
|
||||
x.Value)));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -179,7 +180,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
|
||||
throw new InvalidOperationException("The user id must be an integer to work with the Umbraco");
|
||||
}
|
||||
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
|
||||
IMember? found = _memberService.GetById(asInt);
|
||||
if (found != null)
|
||||
@@ -220,6 +221,7 @@ public class MemberUserStore : UmbracoUserStore<MemberIdentityUser, UmbracoIdent
|
||||
}
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
return Task.FromResult(IdentityResult.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -134,8 +134,8 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
|
||||
/// <inheritdoc />
|
||||
public override async Task<bool> CheckPasswordAsync(TUser user, string? password)
|
||||
{
|
||||
// we cannot proceed if the user passed in does not have an identity
|
||||
if (user.HasIdentity == false)
|
||||
// we cannot proceed if the user passed in does not have an identity, or if no password is provided.
|
||||
if (user.HasIdentity == false || password is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
|
||||
public async Task<bool> ValidateCredentialsAsync(string username, string password)
|
||||
{
|
||||
TUser user = await FindByNameAsync(username);
|
||||
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return false;
|
||||
@@ -263,7 +263,7 @@ public abstract class UmbracoUserManager<TUser, TPasswordConfig> : UserManager<T
|
||||
throw new NotSupportedException("The current user store does not implement " +
|
||||
typeof(IUserPasswordStore<>));
|
||||
}
|
||||
|
||||
|
||||
var result = await VerifyPasswordAsync(userPasswordStore, user, password);
|
||||
|
||||
return result == PasswordVerificationResult.Success || result == PasswordVerificationResult.SuccessRehashNeeded;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -6,6 +7,7 @@ using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Macros;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Templates;
|
||||
@@ -15,12 +17,23 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
private readonly ILogger<HtmlMacroParameterParser> _logger;
|
||||
private readonly IMacroService _macroService;
|
||||
private readonly ParameterEditorCollection _parameterEditors;
|
||||
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
|
||||
|
||||
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
|
||||
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors)
|
||||
: this(
|
||||
macroService,
|
||||
logger,
|
||||
parameterEditors,
|
||||
StaticServiceProvider.Instance.GetRequiredService<DataValueReferenceFactoryCollection>())
|
||||
{ }
|
||||
|
||||
public HtmlMacroParameterParser(IMacroService macroService, ILogger<HtmlMacroParameterParser> logger, ParameterEditorCollection parameterEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
|
||||
{
|
||||
_macroService = macroService;
|
||||
_logger = logger;
|
||||
_parameterEditors = parameterEditors;
|
||||
_dataValueReferenceFactories = dataValueReferenceFactories;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,6 +54,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
(macroAlias, macroAttributes) => foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(
|
||||
macroAlias,
|
||||
new Dictionary<string, string>(macroAttributes, StringComparer.OrdinalIgnoreCase))));
|
||||
|
||||
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacros(foundMacros))
|
||||
{
|
||||
yield return umbracoEntityReference;
|
||||
@@ -52,8 +66,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
/// </summary>
|
||||
/// <param name="macroGridControls"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(
|
||||
IEnumerable<GridValue.GridControl> macroGridControls)
|
||||
public IEnumerable<UmbracoEntityReference> FindUmbracoEntityReferencesFromGridControlMacros(IEnumerable<GridValue.GridControl> macroGridControls)
|
||||
{
|
||||
var foundMacros = new List<Tuple<string?, Dictionary<string, string>>>();
|
||||
|
||||
@@ -65,8 +78,7 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
// Collect any macro parameters that contain the media udi format
|
||||
if (gridMacro is not null && gridMacro.MacroParameters is not null && gridMacro.MacroParameters.Any())
|
||||
{
|
||||
foundMacros.Add(
|
||||
new Tuple<string?, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
|
||||
foundMacros.Add(new Tuple<string?, Dictionary<string, string>>(gridMacro.MacroAlias, gridMacro.MacroParameters));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,14 +113,12 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
continue;
|
||||
}
|
||||
|
||||
foundMacroUmbracoEntityReferences.Add(
|
||||
new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
|
||||
foundMacroUmbracoEntityReferences.Add(new UmbracoEntityReference(Udi.Create(Constants.UdiEntityType.Macro, macroConfig.Key)));
|
||||
|
||||
// Only do this if the macros actually have parameters
|
||||
if (macroConfig.Properties.Keys.Any(f => f != "macroAlias"))
|
||||
{
|
||||
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(
|
||||
macro.Item2, macroConfig, _parameterEditors))
|
||||
foreach (UmbracoEntityReference umbracoEntityReference in GetUmbracoEntityReferencesFromMacroParameters(macro.Item2, macroConfig, _parameterEditors))
|
||||
{
|
||||
yield return umbracoEntityReference;
|
||||
}
|
||||
@@ -130,41 +140,23 @@ public sealed class HtmlMacroParameterParser : IHtmlMacroParameterParser
|
||||
/// look up the corresponding property editor for a macro parameter
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(
|
||||
Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
|
||||
private IEnumerable<UmbracoEntityReference> GetUmbracoEntityReferencesFromMacroParameters(Dictionary<string, string> macroParameters, IMacro macroConfig, ParameterEditorCollection parameterEditors)
|
||||
{
|
||||
var foundUmbracoEntityReferences = new List<UmbracoEntityReference>();
|
||||
foreach (IMacroProperty parameter in macroConfig.Properties)
|
||||
{
|
||||
if (macroParameters.TryGetValue(parameter.Alias, out var parameterValue))
|
||||
{
|
||||
var parameterEditorAlias = parameter.EditorAlias;
|
||||
|
||||
// Lookup propertyEditor from the registered ParameterEditors with the implmementation to avoid looking up for each parameter
|
||||
IDataEditor? parameterEditor = parameterEditors.FirstOrDefault(f =>
|
||||
string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
|
||||
IDataEditor? parameterEditor = parameterEditors.FirstOrDefault(f => string.Equals(f.Alias, parameterEditorAlias, StringComparison.OrdinalIgnoreCase));
|
||||
if (parameterEditor is not null)
|
||||
{
|
||||
// Get the ParameterValueEditor for this PropertyEditor (where the GetReferences method is implemented) - cast as IDataValueReference to determine if 'it is' implemented for the editor
|
||||
if (parameterEditor.GetValueEditor() is IDataValueReference parameterValueEditor)
|
||||
foreach (UmbracoEntityReference entityReference in _dataValueReferenceFactories.GetReferences(parameterEditor, parameterValue))
|
||||
{
|
||||
foreach (UmbracoEntityReference entityReference in parameterValueEditor.GetReferences(
|
||||
parameterValue))
|
||||
{
|
||||
foundUmbracoEntityReferences.Add(entityReference);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"{0} doesn't have a ValueEditor that implements IDataValueReference",
|
||||
parameterEditor.Alias);
|
||||
yield return entityReference;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundUmbracoEntityReferences;
|
||||
}
|
||||
|
||||
// Poco class to deserialise the Json for a Macro Control
|
||||
|
||||
@@ -11,36 +11,36 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Examine.Core" Version="3.0.1" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="IPNetwork2" Version="2.5.422" />
|
||||
<PackageReference Include="MailKit" Version="3.2.0" />
|
||||
<PackageReference Include="Markdown" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.5" />
|
||||
<PackageReference Include="MiniProfiler.Shared" Version="4.2.22" />
|
||||
<PackageReference Include="ncrontab" Version="3.3.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NPoco.SqlServer" Version="5.3.2" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.2" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.2.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="6.0.1" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
|
||||
<PackageReference Include="Examine.Core"/>
|
||||
<PackageReference Include="HtmlAgilityPack"/>
|
||||
<PackageReference Include="IPNetwork2" />
|
||||
<PackageReference Include="MailKit" />
|
||||
<PackageReference Include="Markdown" />
|
||||
<PackageReference Include="Microsoft.CSharp" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" />
|
||||
<PackageReference Include="MiniProfiler.Shared" />
|
||||
<PackageReference Include="ncrontab" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="NPoco.SqlServer" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="Serilog.Enrichers.Process" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" />
|
||||
<PackageReference Include="Serilog.Expressions" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact.Reader" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" />
|
||||
<PackageReference Include="Serilog.Sinks.File" />
|
||||
<PackageReference Include="Serilog.Sinks.Map" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" />
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab
|
||||
IDomainCache domainCache,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: base(previewDefault)
|
||||
: base(variationContextAccessor, previewDefault)
|
||||
{
|
||||
_snapshot = snapshot;
|
||||
_snapshotCache = snapshotCache;
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
/// </remarks>
|
||||
public class ContentStore
|
||||
{
|
||||
private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
// TODO: collection trigger (ok for now)
|
||||
// see SnapDictionary notes
|
||||
private const long CollectMinGenDelta = 8;
|
||||
@@ -330,7 +332,12 @@ public class ContentStore
|
||||
throw new InvalidOperationException("Recursive locks not allowed");
|
||||
}
|
||||
|
||||
Monitor.Enter(_wlocko, ref lockInfo.Taken);
|
||||
Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken);
|
||||
|
||||
if (Monitor.IsEntered(_wlocko) is false)
|
||||
{
|
||||
throw new TimeoutException("Could not enter monitor before timeout in content store");
|
||||
}
|
||||
|
||||
lock (_rlocko)
|
||||
{
|
||||
@@ -736,16 +743,22 @@ public class ContentStore
|
||||
{
|
||||
EnsureLocked();
|
||||
|
||||
IPublishedContentType?[] contentTypes = _contentTypesById
|
||||
IPublishedContentType[] contentTypes = _contentTypesById
|
||||
.Where(kvp =>
|
||||
kvp.Value.Value != null &&
|
||||
kvp.Value.Value.PropertyTypes.Any(p => dataTypeIds.Contains(p.DataType.Id)))
|
||||
.Select(kvp => kvp.Value.Value)
|
||||
.Select(x => getContentType(x!.Id))
|
||||
.Where(x => x != null) // poof, gone, very unlikely and probably an anomaly
|
||||
.WhereNotNull() // poof, gone, very unlikely and probably an anomaly
|
||||
.ToArray();
|
||||
|
||||
var contentTypeIdsA = contentTypes.Select(x => x!.Id).ToArray();
|
||||
// all content types that are affected by this data type update must be updated
|
||||
foreach (IPublishedContentType contentType in contentTypes)
|
||||
{
|
||||
SetContentTypeLocked(contentType);
|
||||
}
|
||||
|
||||
var contentTypeIdsA = contentTypes.Select(x => x.Id).ToArray();
|
||||
var contentTypeNodes = new Dictionary<int, List<int>>();
|
||||
foreach (var id in contentTypeIdsA)
|
||||
{
|
||||
@@ -761,7 +774,7 @@ public class ContentStore
|
||||
}
|
||||
}
|
||||
|
||||
foreach (IPublishedContentType contentType in contentTypes.WhereNotNull())
|
||||
foreach (IPublishedContentType contentType in contentTypes)
|
||||
{
|
||||
// again, weird situation
|
||||
if (contentTypeNodes.ContainsKey(contentType.Id) == false)
|
||||
|
||||
@@ -17,7 +17,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa
|
||||
#region Constructors
|
||||
|
||||
public MediaCache(bool previewDefault, ContentStore.Snapshot snapshot, IVariationContextAccessor variationContextAccessor)
|
||||
: base(previewDefault)
|
||||
: base(variationContextAccessor, previewDefault)
|
||||
{
|
||||
_snapshot = snapshot;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
|
||||
@@ -221,19 +221,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document));
|
||||
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateContentNodeKit(row, serializer);
|
||||
}
|
||||
@@ -246,19 +239,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.Append(SqlWhereNodeIdX(SqlContext, id))
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount(SqlContentSourcesSelectUmbracoNodeJoin)
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
|
||||
.Append(SqlWhereNodeIdX(SqlContext, id));
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateContentNodeKit(row, serializer);
|
||||
}
|
||||
@@ -276,19 +262,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext> sqlCountQuery = SqlContentSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document))
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids);
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Document);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetContentNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateContentNodeKit(row, serializer);
|
||||
}
|
||||
@@ -319,18 +298,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media));
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateMediaNodeKit(row, serializer);
|
||||
}
|
||||
@@ -343,19 +316,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.Append(SqlWhereNodeIdX(SqlContext, id))
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount(SqlContentSourcesSelectUmbracoNodeJoin)
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
|
||||
.Append(SqlWhereNodeIdX(SqlContext, id));
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateMediaNodeKit(row, serializer);
|
||||
}
|
||||
@@ -373,19 +339,12 @@ AND cmsContentNu.nodeId IS NULL
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids)
|
||||
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
|
||||
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext> sqlCountQuery = SqlMediaSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media))
|
||||
.WhereIn<ContentDto>(x => x.ContentTypeId, ids);
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
IContentCacheDataSerializer serializer =
|
||||
_contentCacheDataSerializerFactory.Create(ContentCacheDataSerializerEntityType.Media);
|
||||
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
foreach (ContentSourceDto row in Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount))
|
||||
IEnumerable<ContentSourceDto> dtos = GetMediaNodeDtos(sql);
|
||||
|
||||
foreach (ContentSourceDto row in dtos)
|
||||
{
|
||||
yield return CreateMediaNodeKit(row, serializer);
|
||||
}
|
||||
@@ -1030,4 +989,54 @@ WHERE cmsContentNu.nodeId IN (
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentSourceDto> GetMediaNodeDtos(Sql<ISqlContext> sql)
|
||||
{
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
// QueryPaged is very slow on large sites however, so use fetch if UsePagedSqlQuery is disabled.
|
||||
IEnumerable<ContentSourceDto> dtos;
|
||||
if (_nucacheSettings.Value.UsePagedSqlQuery)
|
||||
{
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlMediaSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Media));
|
||||
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
dtos = Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
dtos = Database.Fetch<ContentSourceDto>(sql);
|
||||
}
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentSourceDto> GetContentNodeDtos(Sql<ISqlContext> sql)
|
||||
{
|
||||
// We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
|
||||
// We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
|
||||
// QueryPaged is very slow on large sites however, so use fetch if UsePagedSqlQuery is disabled.
|
||||
IEnumerable<ContentSourceDto> dtos;
|
||||
if (_nucacheSettings.Value.UsePagedSqlQuery)
|
||||
{
|
||||
// Use a more efficient COUNT query
|
||||
Sql<ISqlContext>? sqlCountQuery = SqlContentSourcesCount()
|
||||
.Append(SqlObjectTypeNotTrashed(SqlContext, Constants.ObjectTypes.Document));
|
||||
|
||||
Sql<ISqlContext>? sqlCount =
|
||||
SqlContext.Sql("SELECT COUNT(*) FROM (").Append(sqlCountQuery).Append(") npoco_tbl");
|
||||
|
||||
dtos = Database.QueryPaged<ContentSourceDto>(_nucacheSettings.Value.SqlPageSize, sql, sqlCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
dtos = Database.Fetch<ContentSourceDto>(sql);
|
||||
}
|
||||
|
||||
return dtos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,9 +127,25 @@ public class NuCacheContentService : RepositoryService, INuCacheContentService
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(repositoryCacheMode: RepositoryCacheMode.Scoped))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
scope.ReadLock(Constants.Locks.MediaTree);
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
if (contentTypeIds is null && mediaTypeIds is null && memberTypeIds is null)
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTree,Constants.Locks.MediaTree,Constants.Locks.MemberTree);
|
||||
}
|
||||
|
||||
if (contentTypeIds is not null && contentTypeIds.Any())
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
}
|
||||
|
||||
if (mediaTypeIds is not null && mediaTypeIds.Any())
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MediaTree);
|
||||
}
|
||||
|
||||
if (memberTypeIds is not null && memberTypeIds.Any())
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
}
|
||||
|
||||
_repository.Rebuild(contentTypeIds, mediaTypeIds, memberTypeIds);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ internal class PublishedContent : PublishedContentBase
|
||||
// add one property per property type - this is required, for the indexing to work
|
||||
// if contentData supplies pdatas, use them, else use null
|
||||
contentData.Properties.TryGetValue(propertyType.Alias, out PropertyData[]? pdatas); // else will be null
|
||||
properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor);
|
||||
properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor, propertyType.CacheLevel);
|
||||
}
|
||||
|
||||
PropertiesArray = properties;
|
||||
|
||||
@@ -210,7 +210,16 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
// they require.
|
||||
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
||||
{
|
||||
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
|
||||
NotifyLocked(
|
||||
new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
ChangeTypes = TreeChangeTypes.RefreshAll
|
||||
}
|
||||
},
|
||||
out _,
|
||||
out _);
|
||||
}
|
||||
|
||||
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
||||
@@ -852,6 +861,12 @@ internal class PublishedSnapshotService : IPublishedSnapshotService
|
||||
// contentStore is write-locked during changes - see note above, calls to this method are wrapped in contentStore.GetScopedWriteLock
|
||||
foreach (ContentCacheRefresher.JsonPayload payload in payloads)
|
||||
{
|
||||
if (payload.Blueprint)
|
||||
{
|
||||
// Skip blueprints
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id);
|
||||
|
||||
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
|
||||
|
||||
@@ -9,6 +9,8 @@ public class SnapDictionary<TKey, TValue>
|
||||
where TValue : class
|
||||
where TKey : notnull
|
||||
{
|
||||
private static readonly TimeSpan _monitorTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
// minGenDelta to be adjusted
|
||||
// we may want to throttle collects even if delta is reached
|
||||
// we may want to force collect if delta is not reached but very old
|
||||
@@ -198,7 +200,12 @@ public class SnapDictionary<TKey, TValue>
|
||||
throw new InvalidOperationException("Recursive locks not allowed");
|
||||
}
|
||||
|
||||
Monitor.Enter(_wlocko, ref lockInfo.Taken);
|
||||
Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken);
|
||||
|
||||
if (Monitor.IsEntered(_wlocko) is false)
|
||||
{
|
||||
throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary");
|
||||
}
|
||||
|
||||
lock (_rlocko)
|
||||
{
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Umbraco.CSharpTest.Net.Collections" Version="14.906.1403.1085" />
|
||||
<PackageReference Include="MessagePack" Version="2.3.85" />
|
||||
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.16" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Umbraco.CSharpTest.Net.Collections" />
|
||||
<PackageReference Include="MessagePack" />
|
||||
<PackageReference Include="K4os.Compression.LZ4" />
|
||||
<PackageReference Include="Newtonsoft.Json"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -19,6 +19,8 @@ public class
|
||||
{
|
||||
private readonly ContentPermissions _contentPermissions;
|
||||
|
||||
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Document;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentPermissionsQueryStringHandler" /> class.
|
||||
/// </summary>
|
||||
@@ -47,7 +49,11 @@ public class
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var argument = routeVal.ToString();
|
||||
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
|
||||
// It's the first one that'll be processed by the protected method so we should verify that.
|
||||
var argument = routeVal.Count == 1
|
||||
? routeVal.ToString()
|
||||
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
|
||||
|
||||
if (!TryParseNodeId(argument, out nodeId))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
|
||||
{
|
||||
private readonly MediaPermissions _mediaPermissions;
|
||||
|
||||
protected override UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Media;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaPermissionsQueryStringHandler" /> class.
|
||||
/// </summary>
|
||||
@@ -44,7 +46,11 @@ public class MediaPermissionsQueryStringHandler : PermissionsQueryStringHandler<
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var argument = routeVal.ToString();
|
||||
// Handle case where the incoming querystring could contain more than one value (e.g. ?id=1000&id=1001).
|
||||
// It's the first one that'll be processed by the protected method so we should verify that.
|
||||
var argument = routeVal.Count == 1
|
||||
? routeVal.ToString()
|
||||
: routeVal.FirstOrDefault()?.ToString() ?? string.Empty;
|
||||
|
||||
if (!TryParseNodeId(argument, out var nodeId))
|
||||
{
|
||||
|
||||
@@ -49,12 +49,18 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
|
||||
/// </summary>
|
||||
protected IEntityService EntityService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to Unknown so all types are allowed, since Keys are unique across all node types this works,
|
||||
/// but it if you are certain you are looking for a specific type this should be overwritten for DB query performance.
|
||||
/// </summary>
|
||||
protected virtual UmbracoObjectTypes KeyParsingFilterType => UmbracoObjectTypes.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a node ID from a string representation found in a querystring value.
|
||||
/// </summary>
|
||||
/// <param name="argument">Querystring value.</param>
|
||||
/// <param name="nodeId">Output parsed Id.</param>
|
||||
/// <returns>True of node ID could be parased, false it not.</returns>
|
||||
/// <returns>True of node ID could be parsed, false it not.</returns>
|
||||
protected bool TryParseNodeId(string argument, out int nodeId)
|
||||
{
|
||||
// If the argument is an int, it will parse and can be assigned to nodeId.
|
||||
@@ -75,7 +81,7 @@ public abstract class PermissionsQueryStringHandler<T> : MustSatisfyRequirementA
|
||||
|
||||
if (Guid.TryParse(argument, out Guid key))
|
||||
{
|
||||
nodeId = EntityService.GetId(key, UmbracoObjectTypes.Document).Result;
|
||||
nodeId = EntityService.GetId(key, KeyParsingFilterType).Result;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers;
|
||||
|
||||
[Authorize(Policy = AuthorizationPolicies.SectionAccessSettings)]
|
||||
public class AnalyticsController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IMetricsConsentService _metricsConsentService;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user