Compare commits
224
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 | ||
|
|
9fd140f81d | ||
|
|
1198c76d67 | ||
|
|
869b480dae | ||
|
|
12704b112e | ||
|
|
3c37653012 | ||
|
|
e5a8d01004 | ||
|
|
9fd0b1986c | ||
|
|
80ea5174c6 | ||
|
|
e1b4aebb0f | ||
|
|
38910a8d5c | ||
|
|
0dc18982ee | ||
|
|
311d322129 | ||
|
|
e7af98027d | ||
|
|
30f0cfc15d | ||
|
|
b743e715d4 | ||
|
|
d3243f8700 | ||
|
|
264e4f8b57 | ||
|
|
2b3f048db7 | ||
|
|
18ee016ae9 | ||
|
|
512199c8ab | ||
|
|
4e410a9dd5 | ||
|
|
e16e36c68f | ||
|
|
f384e0c30c | ||
|
|
04b74dddd0 | ||
|
|
43700d2bcb | ||
|
|
eed9feb5f3 | ||
|
|
b2a5ce3e48 | ||
|
|
0ae404ce03 | ||
|
|
ab836d2326 | ||
|
|
c12cce6d8c | ||
|
|
78e1da3231 | ||
|
|
82eae48d09 | ||
|
|
a5ba32bfd7 | ||
|
|
c695f2192f | ||
|
|
dc5c7a14a5 | ||
|
|
c751e90928 | ||
|
|
d86e4b2d38 | ||
|
|
3f196a9de4 | ||
|
|
30ec9d9bc7 | ||
|
|
3087fc3ba9 | ||
|
|
7fd7a8715d | ||
|
|
62fd860a9e | ||
|
|
76ad99beb0 | ||
|
|
ebe375d23b | ||
|
|
2f35967bb2 | ||
|
|
f93f3d88da | ||
|
|
dbd009ffcd | ||
|
|
b99e14e288 | ||
|
|
b8415ed7f0 | ||
|
|
8f6370a480 | ||
|
|
aa4a1b3498 | ||
|
|
b716e98780 | ||
|
|
e86fb0eeb6 | ||
|
|
fe1080dd64 | ||
|
|
1133c4e7f2 | ||
|
|
f5b834974d | ||
|
|
75113a90fb | ||
|
|
7e573c70a3 | ||
|
|
5c5de77965 | ||
|
|
2f486610bb | ||
|
|
d19a30768f | ||
|
|
b451bb802b | ||
|
|
785390efd7 | ||
|
|
3b3e1cec40 | ||
|
|
89d104e176 | ||
|
|
9a3e655645 | ||
|
|
5f15b8abb1 | ||
|
|
4df3e19af7 | ||
|
|
89d670372d | ||
|
|
b5ab115318 | ||
|
|
407263d090 | ||
|
|
b37bb517ab | ||
|
|
999a83995e | ||
|
|
b3a93ac86c | ||
|
|
e79246368b | ||
|
|
b67c7fbe77 | ||
|
|
16f448a802 | ||
|
|
b3bedb4efe | ||
|
|
5b73fc19ee | ||
|
|
4cdab08910 | ||
|
|
391cade75f | ||
|
|
e27e8d1189 | ||
|
|
1e0ace7a34 | ||
|
|
21c6828b59 | ||
|
|
f7f361239c | ||
|
|
05908e2c78 | ||
|
|
3a7ba530a2 | ||
|
|
f4073c1dc7 | ||
|
|
7b5233803e | ||
|
|
822febb8e8 | ||
|
|
bf7b9a20a8 | ||
|
|
9aadb41f4a |
@@ -11,15 +11,10 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install node-fetch@2
|
||||
- name: Fetch random comment 🗣️ and add it to the PR
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const fetch = require('node-fetch')
|
||||
|
||||
const response = await fetch('https://collaboratorsv2.euwest01.umbraco.io/umbraco/api/comments/PostComment', {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
@@ -46,13 +41,13 @@ jobs:
|
||||
});
|
||||
} else {
|
||||
console.log("Returned data not indicate success.");
|
||||
|
||||
|
||||
if(response.status !== 200) {
|
||||
console.log("Status code:", response.status)
|
||||
}
|
||||
|
||||
console.log("Returned data:", data);
|
||||
|
||||
|
||||
if(data === '') {
|
||||
console.log("An empty response usually indicates that either no comment was found or the actor user was not eligible for getting an automated response (HQ users are not getting auto-responses).")
|
||||
}
|
||||
|
||||
@@ -49,4 +49,5 @@
|
||||
<PropertyGroup>
|
||||
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+104
-56
@@ -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
|
||||
@@ -368,7 +407,7 @@ stages:
|
||||
Umbraco__CMS__Unattended__InstallUnattended: true
|
||||
Umbraco__CMS__Global__InstallMissingDatabase: true
|
||||
UmbracoDatabaseServer: (LocalDB)\MSSQLLocalDB
|
||||
UmbracoDatabaseName: Playwright
|
||||
UmbracoDatabaseName: AcceptanceTestDB
|
||||
ConnectionStrings__umbracoDbDSN: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
|
||||
# Custom Umbraco settings
|
||||
Umbraco__CMS__Global__VersionCheckPeriod: 0
|
||||
@@ -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
|
||||
@@ -429,7 +469,7 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
|
||||
- pwsh: |
|
||||
dotnet new --install ./nupkg/Umbraco.Templates.*.nupkg
|
||||
dotnet new umbraco --name Playwright --no-restore --output .
|
||||
dotnet new umbraco --name AcceptanceTestProject --no-restore --output .
|
||||
dotnet restore --configfile ./nuget.config
|
||||
dotnet build --configuration $(buildConfiguration) --no-restore
|
||||
dotnet dev-certs https
|
||||
@@ -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>
|
||||
@@ -51,6 +51,7 @@ namespace JsonSchema
|
||||
public ImagingSettings? Imaging { get; set; }
|
||||
|
||||
public IndexCreatorSettings? Examine { get; set; }
|
||||
public IndexingSettings? Indexing { get; set; }
|
||||
|
||||
public KeepAliveSettings? KeepAlive { get; set; }
|
||||
|
||||
@@ -88,13 +89,27 @@ namespace JsonSchema
|
||||
|
||||
public HelpPageSettings? HelpPage { get; set; }
|
||||
|
||||
public InstallDefaultDataSettings? DefaultDataCreation { get; set; }
|
||||
public InstallDefaultData? InstallDefaultData { get; set; }
|
||||
|
||||
public DataTypesSettings? DataTypes { get; set; }
|
||||
|
||||
public MarketplaceSettings? Marketplace { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configurations for the Umbraco CMS InstallDefaultData configuration.
|
||||
/// </summary>
|
||||
public class InstallDefaultData
|
||||
{
|
||||
public InstallDefaultDataSettings? Languages { get; set; }
|
||||
|
||||
public InstallDefaultDataSettings? DataTypes { get; set; }
|
||||
|
||||
public InstallDefaultDataSettings? MediaTypes { get; set; }
|
||||
|
||||
public InstallDefaultDataSettings? MemberTypes { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configurations for the Umbraco Forms package to Umbraco CMS
|
||||
/// </summary>
|
||||
|
||||
@@ -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.1.3" />
|
||||
<PackageReference Include="Umbraco.Forms.Core" Version="10.3.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
|
||||
|
||||
@@ -88,10 +88,6 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase<DataTypeC
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not sure I like these?
|
||||
TagsValueConverter.ClearCaches();
|
||||
SliderValueConverter.ClearCaches();
|
||||
|
||||
// refresh the models and cache
|
||||
_publishedModelFactory.WithSafeLiveFactoryReset(() =>
|
||||
_publishedSnapshotService.Notify(payloads));
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class TypeFinder : ITypeFinder
|
||||
/// its an exact name match
|
||||
/// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll"
|
||||
/// </remarks>
|
||||
internal static readonly string[] KnownAssemblyExclusionFilter =
|
||||
internal static string[] KnownAssemblyExclusionFilter =
|
||||
{
|
||||
"mscorlib,", "netstandard,", "System,", "Antlr3.", "AutoMapper,", "AutoMapper.", "Autofac,", // DI
|
||||
"Autofac.", "AzureDirectory,", "Castle.", // DI, tests
|
||||
@@ -49,11 +49,20 @@ public class TypeFinder : ITypeFinder
|
||||
private string[]? _assembliesAcceptingLoadExceptions;
|
||||
private volatile HashSet<Assembly>? _localFilteredAssemblyCache;
|
||||
|
||||
[Obsolete("Please use the constructor taking all parameters. This constructor will be removed in V14.")]
|
||||
public TypeFinder(ILogger<TypeFinder> logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig? typeFinderConfig = null)
|
||||
: this(logger, assemblyProvider, null, typeFinderConfig)
|
||||
{ }
|
||||
|
||||
public TypeFinder(ILogger<TypeFinder> logger, IAssemblyProvider assemblyProvider, string[]? additionalExlusionAssemblies, ITypeFinderConfig? typeFinderConfig = null)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_assemblyProvider = assemblyProvider;
|
||||
_typeFinderConfig = typeFinderConfig;
|
||||
if (additionalExlusionAssemblies is not null)
|
||||
{
|
||||
KnownAssemblyExclusionFilter = KnownAssemblyExclusionFilter.Union(additionalExlusionAssemblies).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Cms.Core.Configuration.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Typed configuration options for index creator settings.
|
||||
/// </summary>
|
||||
[UmbracoOptions(Constants.Configuration.ConfigIndexing)]
|
||||
public class IndexingSettings
|
||||
{
|
||||
private const bool StaticExplicitlyIndexEachNestedProperty = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for whether each nested property should have it's own indexed value. Requires a rebuild of indexes when changed.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticExplicitlyIndexEachNestedProperty)]
|
||||
public bool ExplicitlyIndexEachNestedProperty { get; set; } = StaticExplicitlyIndexEachNestedProperty;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -116,18 +116,18 @@ public class RichTextEditorSettings
|
||||
new Dictionary<string, string> { ["entity_encoding"] = "raw" };
|
||||
|
||||
/// <summary>
|
||||
/// HTML RichText Editor TinyMCE Commands
|
||||
/// HTML RichText Editor TinyMCE Commands.
|
||||
/// </summary>
|
||||
/// WB-TODO Custom Array of objects
|
||||
public RichTextEditorCommand[] Commands { get; set; } = Default_commands;
|
||||
|
||||
/// <summary>
|
||||
/// HTML RichText Editor TinyMCE Plugins
|
||||
/// HTML RichText Editor TinyMCE Plugins.
|
||||
/// </summary>
|
||||
public string[] Plugins { get; set; } = Default_plugins;
|
||||
|
||||
/// <summary>
|
||||
/// HTML RichText Editor TinyMCE Custom Config
|
||||
/// HTML RichText Editor TinyMCE Custom Config.
|
||||
/// </summary>
|
||||
/// WB-TODO Custom Dictionary
|
||||
public IDictionary<string, string> CustomConfig { get; set; } = Default_custom_config;
|
||||
@@ -138,7 +138,7 @@ public class RichTextEditorSettings
|
||||
public string ValidElements { get; set; } = StaticValidElements;
|
||||
|
||||
/// <summary>
|
||||
/// Invalid HTML elements for RichText Editor
|
||||
/// Invalid HTML elements for RichText Editor.
|
||||
/// </summary>
|
||||
[DefaultValue(StaticInvalidElements)]
|
||||
public string InvalidElements { get; set; } = StaticInvalidElements;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -22,4 +22,9 @@ public class TypeFinderSettings
|
||||
/// scanning for plugins based on different root referenced assemblies you can add the assembly name to this list.
|
||||
/// </summary>
|
||||
public IEnumerable<string>? AdditionalEntryAssemblies { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the assemblies that will be excluded from scanning.
|
||||
/// </summary>
|
||||
public string[] AdditionalAssemblyExclusionEntries { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public static partial class Constants
|
||||
public const string ConfigHosting = ConfigPrefix + "Hosting";
|
||||
public const string ConfigImaging = ConfigPrefix + "Imaging";
|
||||
public const string ConfigExamine = ConfigPrefix + "Examine";
|
||||
public const string ConfigIndexing = ConfigPrefix + "Indexing";
|
||||
public const string ConfigKeepAlive = ConfigPrefix + "KeepAlive";
|
||||
public const string ConfigLogging = ConfigPrefix + "Logging";
|
||||
public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword";
|
||||
|
||||
@@ -28,5 +28,6 @@ public static partial class Constants
|
||||
public static string IsDebug = "IsDebug";
|
||||
public static string DatabaseProvider = "DatabaseProvider";
|
||||
public static string CurrentServerRole = "CurrentServerRole";
|
||||
public static string BackofficeExternalLoginProviderCount = "BackofficeExternalLoginProviderCount";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder
|
||||
.AddUmbracoOptions<ModelsBuilderSettings>()
|
||||
.AddUmbracoOptions<ActiveDirectorySettings>()
|
||||
.AddUmbracoOptions<IndexCreatorSettings>()
|
||||
.AddUmbracoOptions<MarketplaceSettings>()
|
||||
.AddUmbracoOptions<ContentSettings>()
|
||||
.AddUmbracoOptions<CoreDebugSettings>()
|
||||
@@ -64,7 +65,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddUmbracoOptions<HealthChecksSettings>()
|
||||
.AddUmbracoOptions<HostingSettings>()
|
||||
.AddUmbracoOptions<ImagingSettings>()
|
||||
.AddUmbracoOptions<IndexCreatorSettings>()
|
||||
.AddUmbracoOptions<IndexingSettings>()
|
||||
.AddUmbracoOptions<KeepAliveSettings>()
|
||||
.AddUmbracoOptions<LoggingSettings>()
|
||||
.AddUmbracoOptions<MemberPasswordConfigurationSettings>()
|
||||
|
||||
@@ -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>();
|
||||
@@ -327,6 +328,9 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
Services.AddUnique<ICultureImpactFactory>(provider => new CultureImpactFactory(provider.GetRequiredService<IOptionsMonitor<ContentSettings>>()));
|
||||
Services.AddUnique<IDictionaryService, DictionaryService>();
|
||||
Services.AddUnique<ITemporaryMediaService, TemporaryMediaService>();
|
||||
|
||||
// Register filestream security analyzers
|
||||
Services.AddUnique<IFileStreamSecurityValidator,FileStreamSecurityValidator>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1948,7 +1948,7 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po
|
||||
<key alias="mailBody"><![CDATA[
|
||||
Zdravo %0%
|
||||
|
||||
Ovo je automatska pošta koja vas obavještava da je za
|
||||
Ovo je automatska pošta koja vas obavještava da je za
|
||||
dokument '%1%' zatražen prevod na '%5%' od %2%.
|
||||
|
||||
Idi na http://%3%/translation/details.aspx?id=%4% za uređivanje.
|
||||
@@ -2116,15 +2116,10 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po
|
||||
<key alias="userInvitedSuccessHelp">Novom korisniku je poslana pozivnica s detaljima o tome kako se prijaviti
|
||||
Umbraco.
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Pozdrav i dobrodošli u Umbraco! Za samo 1 minut možete krenuti, mi
|
||||
samo trebate postaviti lozinku i dodati sliku za svoj avatar.
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Pozdrav i dobrodošli u Umbraco! Za samo 1 minut možete krenuti, mi samo trebate postaviti lozinku.</key>
|
||||
<key alias="userinviteExpiredMessage">Dobrodošli u Umbraco! Nažalost, vaš poziv je istekao. Molimo kontaktirajte svoju
|
||||
administratora i zamolite ih da ga ponovo pošalju.
|
||||
</key>
|
||||
<key alias="userinviteAvatarMessage">Ako otpremite svoju fotografiju, drugi korisnici će ga lako prepoznati
|
||||
ti. Kliknite na krug iznad da otpremite svoju fotografiju.
|
||||
</key>
|
||||
<key alias="writer">Pisac</key>
|
||||
<key alias="change">Promjena</key>
|
||||
<key alias="yourProfile" version="7.0">Vaš profil</key>
|
||||
@@ -2238,8 +2233,8 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po
|
||||
<key alias="stateInactive">Neaktivan</key>
|
||||
<key alias="sortNameAscending">Ime (A-Z)</key>
|
||||
<key alias="sortNameDescending">Ime (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Najnovije</key>
|
||||
<key alias="sortCreateDateDescending">Najstarije</key>
|
||||
<key alias="sortCreateDateAscending">Najstarije</key>
|
||||
<key alias="sortCreateDateDescending">Najnovije</key>
|
||||
<key alias="sortLastLoginDateDescending">Zadnja prijava</key>
|
||||
<key alias="noUserGroupsAdded">Nijedna korisnička grupa nije dodana</key>
|
||||
<key alias="2faDisableText">Ako želite da onemogućite ovog dvofaktorskog provajdera, onda morate uneti kod prikazan na vašem uređaju za autentifikaciju:</key>
|
||||
@@ -2296,7 +2291,7 @@ Da upravljate svojom web lokacijom, jednostavno otvorite Umbraco backoffice i po
|
||||
1: Recommended value
|
||||
-->
|
||||
<key alias="macroErrorModeCheckSuccessMessage">Makro greške su postavljene na '%0%'.</key>
|
||||
<key alias="macroErrorModeCheckErrorMessage">Greške makroa su postavljene na '%0%' što će spriječiti potpuno učitavanje nekih ili svih stranica
|
||||
<key alias="macroErrorModeCheckErrorMessage">Greške makroa su postavljene na '%0%' što će spriječiti potpuno učitavanje nekih ili svih stranica
|
||||
na vašem sajtu ako postoje greške u makroima. Ako ovo ispravite, vrijednost će biti postavljena na '%1%'.
|
||||
</key>
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -1754,9 +1754,8 @@
|
||||
<key alias="usergroup">Uživatelská skupina</key>
|
||||
<key alias="userInvited">byl pozván</key>
|
||||
<key alias="userInvitedSuccessHelp">Novému uživateli byla zaslána pozvánka s informacemi, jak se přihlásit do Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Dobrý den, vítejte v Umbraco! Za pouhou 1 minutu budete moci používat Umbraco. Jenom od vás potřebujeme, abyste si nastavili heslo a přidali obrázek pro svůj avatar.</key>
|
||||
<key alias="userinviteWelcomeMessage">Dobrý den, vítejte v Umbraco! Za pouhou 1 minutu budete moci používat Umbraco. Jenom od vás potřebujeme, abyste si nastavili heslo.</key>
|
||||
<key alias="userinviteExpiredMessage">Vítejte v Umbraco! Vaše pozvánka bohužel vypršela. Obraťte se na svého správce a požádejte jej, aby jí znovu odeslal.</key>
|
||||
<key alias="userinviteAvatarMessage">Nahrání vaší fotografie usnadní ostatním uživatelům, aby vás poznali. Kliknutím na kruh výše nahrajte svou fotku.</key>
|
||||
<key alias="writer">Spisovatel</key>
|
||||
<key alias="change">Změnit</key>
|
||||
<key alias="yourProfile" version="7.0">Váš profil</key>
|
||||
@@ -1869,8 +1868,8 @@
|
||||
<key alias="stateInactive">Neaktivní</key>
|
||||
<key alias="sortNameAscending">Jméno (A-Z)</key>
|
||||
<key alias="sortNameDescending">Jméno (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Nejnovější</key>
|
||||
<key alias="sortCreateDateDescending">Nejstarší</key>
|
||||
<key alias="sortCreateDateAscending">Nejstarší</key>
|
||||
<key alias="sortCreateDateDescending">Nejnovější</key>
|
||||
<key alias="sortLastLoginDateDescending">Poslední přihlášení</key>
|
||||
<key alias="noUserGroupsAdded">Nebyly přidány žádné skupiny uživatelů</key>
|
||||
</area>
|
||||
|
||||
@@ -576,7 +576,7 @@
|
||||
<area alias="dictionary">
|
||||
<key alias="noItems">Nid oes unrhyw eitemau geiriadur.</key>
|
||||
<key alias="importDictionaryItemHelp">
|
||||
I fewnforio eitem geiriadur, dewch o hyd i'r ffeil ".udt" ar eich cyfrifiadur trwy glicio
|
||||
I fewnforio eitem geiriadur, dewch o hyd i'r ffeil ".udt" ar eich cyfrifiadur trwy glicio
|
||||
ar y botwm "Mewnforio" (bydd gofyn i chi am gadarnhad ar y sgrin nesaf)
|
||||
</key>
|
||||
<key alias="itemDoesNotExists">Nid yw eitem geiriadur yn bodoli.</key>
|
||||
@@ -2308,9 +2308,8 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang
|
||||
<key alias="userGroups">Grwpiau defnyddiwr</key>
|
||||
<key alias="userInvited">wedi'i wahodd</key>
|
||||
<key alias="userInvitedSuccessHelp">Mae gwahoddiad wedi cael ei anfon at y defnyddiwr newydd gyda manylion ar sut i fewngofnodi i Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Helo a chroeso i Umbraco! Mewn 1 munud yn unig, byddech chi'n barod i fynd, rydym dim ond angen gosod cyfrinair a llun ar gyfer eich avatar.</key>
|
||||
<key alias="userinviteWelcomeMessage">Helo a chroeso i Umbraco! Mewn 1 munud yn unig, byddech chi'n barod i fynd, rydym dim ond angen gosod cyfrinair.</key>
|
||||
<key alias="userinviteExpiredMessage">Croeso i Umbraco! Yn anffodus, mae eich gwahoddiad wedi terfynu. Cysylltwch â'ch gweinyddwr a gofynnwch iddynt ail-anfon.</key>
|
||||
<key alias="userinviteAvatarMessage">Lanlwythwch lun i wneud o'n haws i boble eich adnabod chi.</key>
|
||||
<key alias="writer">Ysgrifennydd</key>
|
||||
<key alias="translator">Cyfieithydd</key>
|
||||
<key alias="change">Newid</key>
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
<key alias="setPermissions">Sæt rettigheder</key>
|
||||
<key alias="unlock">Lås op</key>
|
||||
<key alias="createblueprint">Opret indholdsskabelon</key>
|
||||
<key alias="resendInvite">Gensend Invitation</key>
|
||||
<key alias="defaultValue">Standard værdi</key>
|
||||
<key alias="resendInvite">Gensend invitation</key>
|
||||
<key alias="defaultValue">Standardværdi</key>
|
||||
</area>
|
||||
<area alias="actionCategories">
|
||||
<key alias="content">Indhold</key>
|
||||
@@ -717,6 +717,7 @@
|
||||
<key alias="by">af</key>
|
||||
<key alias="cancel">Fortryd</key>
|
||||
<key alias="cellMargin">Celle margen</key>
|
||||
<key alias="change">Skift</key>
|
||||
<key alias="choose">Vælg</key>
|
||||
<key alias="clear">Ryd</key>
|
||||
<key alias="close">Luk</key>
|
||||
@@ -1894,14 +1895,11 @@ Mange hilsner fra Umbraco robotten
|
||||
logger ind i Umbraco.
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i
|
||||
gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar.
|
||||
gang, vi skal bare have dig til at oprette en adgangskode.
|
||||
</key>
|
||||
<key alias="userinviteExpiredMessage">Velkommen til Umbraco! Desværre er din invitation udløbet. Kontakt din
|
||||
administrator og bed om at gensende invitationen.
|
||||
</key>
|
||||
<key alias="userinviteAvatarMessage">Hvis du uploader et billede af dig selv, gør du det nemt for andre brugere at
|
||||
genkende dig. Klik på cirklen ovenfor for at uploade et billede.
|
||||
</key>
|
||||
<key alias="writer">Forfatter</key>
|
||||
<key alias="configureTwoFactor">Konfigurer totrinsbekræftelse</key>
|
||||
<key alias="change">Skift</key>
|
||||
@@ -1927,8 +1925,8 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="stateInactive">Inaktiv</key>
|
||||
<key alias="sortNameAscending">Navn (A-Å)</key>
|
||||
<key alias="sortNameDescending">Navn (Å-A)</key>
|
||||
<key alias="sortCreateDateAscending">Nyeste</key>
|
||||
<key alias="sortCreateDateDescending">Ældste</key>
|
||||
<key alias="sortCreateDateAscending">Ældste</key>
|
||||
<key alias="sortCreateDateDescending">Nyeste</key>
|
||||
<key alias="sortLastLoginDateDescending">Sidst logget ind</key>
|
||||
<key alias="noUserGroupsAdded">Ingen brugere er blevet tilføjet</key>
|
||||
<key alias="2faDisableText">Hvis du ønsker at slå denne totrinsbekræftelse fra, så skal du nu indtaste koden fra din enhed:</key>
|
||||
@@ -2232,7 +2230,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="allowedBlockColumnsHelp">Vælg de forskellige antal kolonner denne blok må optage i layoutet. Dette forhindre ikke blokken i at optræde i et mindre område.</key>
|
||||
<key alias="allowedBlockRows">TIlgængelige række-størrelser</key>
|
||||
<key alias="allowedBlockRowsHelp">Vælg hvor mange rækker denne blok på optage i layoutet.</key>
|
||||
<key alias="allowBlockInRoot">Tillad på rodniveay</key>
|
||||
<key alias="allowBlockInRoot">Tillad på rodniveau</key>
|
||||
<key alias="allowBlockInRootHelp">Gør denne blok tilgængelig i layoutets rodniveau. Hvis dette ikke er valgt, kan denne blok kun bruges inden for andre blokkes definerede områder.</key>
|
||||
<key alias="areas">Blok-områder</key>
|
||||
<key alias="areasLayoutColumns">Layout-kolonner</key>
|
||||
@@ -2240,7 +2238,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="areasConfigurations">Opsætning af områder</key>
|
||||
<key alias="areasConfigurationsHelp">Hvis det skal være muligt at indsætte nye blokke indeni denne blok, skal der oprettes ét eller flere områder til at indsætte de nye blokke i.</key>
|
||||
<key alias="invalidDropPosition">Ikke tilladt placering.</key>
|
||||
<key alias="defaultLayoutStylesheet">Standart layout stylesheet</key>
|
||||
<key alias="defaultLayoutStylesheet">Standardlayout stylesheet</key>
|
||||
<key alias="confirmPasteDisallowedNestedBlockHeadline">Ikke tilladt indhold blev afvist</key>
|
||||
<key alias="confirmPasteDisallowedNestedBlockMessage">
|
||||
<![CDATA[Det indsatte indhold bestod af ikke tilladt del-indhold, disse dele er blevet afvist. Vil du beholde det resterene alligevel?]]></key>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
Außerdem werden Pfade mit erstem URL-Segment unterstützt z. B.: "example.com/en" or "/en".]]></key>
|
||||
<key alias="inherit">Vererben</key>
|
||||
<key alias="setLanguage">Kultur</key>
|
||||
<key alias="setLanguageHelp">Definiert die Kultureinstellung für untergeordnete Elemente dieses Elements oder vererbt vom übergeordneten Element.
|
||||
<key alias="setLanguageHelp">Definiert die Kultureinstellung für untergeordnete Elemente dieses Elements oder vererbt vom übergeordneten Element.
|
||||
Wird auch auf das aktuelle Element angewendet, sofern auf tieferer Ebene keine Domain zugeordnet ist.</key>
|
||||
<key alias="setDomains">Domainen</key>
|
||||
</area>
|
||||
@@ -232,10 +232,10 @@
|
||||
<key alias="noMediaLink">Dieses Media-Element hat keinen Link</key>
|
||||
<key alias="noProperties">Diesem Element kann kein Inhalt zugewiesen werden</key>
|
||||
<key alias="otherElements">Eigenschaften</key>
|
||||
<key alias="parentNotPublished">Dieses Dokument ist veröffentlicht aber nicht sichtbar,
|
||||
<key alias="parentNotPublished">Dieses Dokument ist veröffentlicht aber nicht sichtbar,
|
||||
da das übergeordnete Dokument '%0%' nicht publiziert ist
|
||||
</key>
|
||||
<key alias="parentCultureNotPublished">Diese Kultur wurde veröffentlicht, aber wird nicht angezeigt,
|
||||
<key alias="parentCultureNotPublished">Diese Kultur wurde veröffentlicht, aber wird nicht angezeigt,
|
||||
weil sie auf dem Oberknoten '%0%' unveröffentlicht ist
|
||||
</key>
|
||||
<key alias="parentNotPublishedAnomaly">Ups! Dieses Dokument ist veröffentlicht aber nicht im internen Cache aufzufinden: Systemfehler.</key>
|
||||
@@ -255,7 +255,7 @@
|
||||
<key alias="removeDate">Datum entfernen</key>
|
||||
<key alias="setDate">Datum wählen</key>
|
||||
<key alias="sortDone">Sortierung abgeschlossen</key>
|
||||
<key alias="sortHelp">Um die Dokumente zu sortieren, ziehen Sie sie einfach an die gewünschte Position.
|
||||
<key alias="sortHelp">Um die Dokumente zu sortieren, ziehen Sie sie einfach an die gewünschte Position.
|
||||
Sie können mehrere Zeilen markieren indem Sie die Umschalttaste ("Shift") oder die Steuerungstaste ("Strg") gedrückt halten
|
||||
</key>
|
||||
<key alias="statistics">Statistiken</key>
|
||||
@@ -281,7 +281,7 @@
|
||||
<![CDATA[<a href="https://docs.umbraco.com/umbraco-cms/fundamentals/data/scheduled-publishing#timezones" target="_blank" rel="noopener">Was bedeutet dies?</a>]]></key>
|
||||
<key alias="nestedContentDeleteItem">Wollen Sie dieses Element wirklich entfernen?</key>
|
||||
<key alias="nestedContentDeleteAllItems">Sicher das Sie alle Elemente entfernen wollen?</key>
|
||||
<key alias="nestedContentEditorNotSupported">Eigenschaft %0% verwendet Editor %1%,
|
||||
<key alias="nestedContentEditorNotSupported">Eigenschaft %0% verwendet Editor %1%,
|
||||
welcher nicht von Nested Content unterstützt wird.
|
||||
</key>
|
||||
<key alias="nestedContentNoContentTypes">Keine Dokument-Typen für diese Eigenschaft konfiguriert.</key>
|
||||
@@ -299,14 +299,14 @@
|
||||
<key alias="removeTextBox">Entferne dieses Textfeld</key>
|
||||
<key alias="contentRoot">Inhalt-Basis</key>
|
||||
<key alias="includeUnpublished">Inklusive Entwürfen: veröffentliche auch unveröffentlichte Elemente.</key>
|
||||
<key alias="isSensitiveValue">Dieser Wert ist verborgen.
|
||||
<key alias="isSensitiveValue">Dieser Wert ist verborgen.
|
||||
Wenn Sie diesen Wert einsehen müssen, wenden Sie sich bitte an einen Administrator.
|
||||
</key>
|
||||
<key alias="isSensitiveValue_short">Dieser Wert ist verborgen.</key>
|
||||
<key alias="languagesToPublish">Welche Sprache möchten Sie veröffentlichen?</key>
|
||||
<key alias="languagesToSendForApproval">Welche Sprachen möchten Sie zur Freigabe schicken?</key>
|
||||
<key alias="languagesToSchedule">Welche Sprachen möchten Sie zu einer bestimmten Zeit veröffentlichen?</key>
|
||||
<key alias="languagesToUnpublish">Wählen Sie die Sprachen, deren Veröffentlichung zurück genommen werden soll.
|
||||
<key alias="languagesToUnpublish">Wählen Sie die Sprachen, deren Veröffentlichung zurück genommen werden soll.
|
||||
Das Zurücknehmen der Veröffentlichung einer Pflichtsprache betrifft alle Sprachen.
|
||||
</key>
|
||||
<key alias="variantsWillBeSaved">Alle neuen Variationen werden gespeichert.</key>
|
||||
@@ -335,7 +335,7 @@
|
||||
<key alias="createdBlueprintHeading">Inhaltsvorlage erzeugt</key>
|
||||
<key alias="createdBlueprintMessage">Inhaltsvorlage von '%0%' wurde erzeugt</key>
|
||||
<key alias="duplicateBlueprintMessage">Eine gleichnamige Inhaltsvorlage ist bereits vorhanden</key>
|
||||
<key alias="blueprintDescription">Eine Inhaltsvorlage ist vordefinierter Inhalt,
|
||||
<key alias="blueprintDescription">Eine Inhaltsvorlage ist vordefinierter Inhalt,
|
||||
den ein Redakteur als Basis für neuen Inhalt verwenden kann
|
||||
</key>
|
||||
</area>
|
||||
@@ -343,7 +343,7 @@
|
||||
<key alias="clickToUpload">Für Upload klicken</key>
|
||||
<key alias="orClickHereToUpload">oder klicken Sie hier um eine Datei zu wählen</key>
|
||||
<key alias="disallowedFileType">Dieser Dateityp darf nicht hochgeladen werden</key>
|
||||
|
||||
|
||||
<key alias="invalidFileName">Diese Datei kann nicht hochgeladen werden wil der Dateiname ungültig ist.</key><key alias="disallowedMediaType">Diese Datei kann nicht hochgeladen werden, der Medienttype mit dem Alias '%0%' ist hier nicht erlaubt.</key>
|
||||
<key alias="maxFileSize">Max. Dateigröße ist</key>
|
||||
<key alias="mediaRoot">Media-Basis</key>
|
||||
@@ -386,16 +386,16 @@
|
||||
<![CDATA[Es stehen keine erlaubten Dokumenttypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren.]]></key>
|
||||
<key alias="noDocumentTypesAtRoot">
|
||||
<![CDATA[Es stehen keine erlaubten Dokumenttypen zur Verfügung. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren.]]></key>
|
||||
<key alias="noDocumentTypesWithNoSettingsAccess">Die im Inhaltsbaum ausgewählte Seite
|
||||
<key alias="noDocumentTypesWithNoSettingsAccess">Die im Inhaltsbaum ausgewählte Seite
|
||||
erlaubt keine Unterseiten.
|
||||
</key>
|
||||
<key alias="noDocumentTypesEditPermissions">Bearbeitungsrechte für diesen Dokumenttyp</key>
|
||||
<key alias="noDocumentTypesCreateNew">Neuen Dokumenttypen erstellen</key>
|
||||
<key alias="noDocumentTypesAllowedAtRoot">
|
||||
<![CDATA[Keine Dokumenttypen vorhanden welche hier eingefügt werden dürfen. Sie müssen diese in den Einstellungen (unter "Dokumenttypen") aktivieren.]]></key>
|
||||
<key alias="noMediaTypes" version="7.0"><![CDATA[Es stehen keine erlaubten Medientypen zur Verfügung.
|
||||
<key alias="noMediaTypes" version="7.0"><![CDATA[Es stehen keine erlaubten Medientypen zur Verfügung.
|
||||
Sie müssen diese in den Einstellungen (unter "Medientypen") aktivieren.]]></key>
|
||||
<key alias="noMediaTypesWithNoSettingsAccess">Das im Strukturbaum ausgewählte Medienelement
|
||||
<key alias="noMediaTypesWithNoSettingsAccess">Das im Strukturbaum ausgewählte Medienelement
|
||||
erlaubt keine untergeordneten Elemente.
|
||||
</key>
|
||||
<key alias="noMediaTypesEditPermissions">Bearbeitungsrechte für diesen Medientyp</key>
|
||||
@@ -444,15 +444,15 @@
|
||||
<key alias="stay">Bleiben</key>
|
||||
<key alias="discardChanges">Änderungen verwerfen</key>
|
||||
<key alias="unsavedChanges">Es gibt ungesicherte Änderungen</key>
|
||||
<key alias="unsavedChangesWarning">Wollen Sie diese Seite wirklich verlassen?
|
||||
<key alias="unsavedChangesWarning">Wollen Sie diese Seite wirklich verlassen?
|
||||
- es gibt ungesicherte Änderungen
|
||||
</key>
|
||||
<key alias="confirmListViewPublish">Veröffentlichen macht die ausgewählten Elemente auf der Website sichtbar.</key>
|
||||
<key alias="confirmListViewUnpublish">Aufheben der Veröffentlichung entfernt die ausgewählten Elemente
|
||||
<key alias="confirmListViewUnpublish">Aufheben der Veröffentlichung entfernt die ausgewählten Elemente
|
||||
und ihre Unterknoten von der Website.
|
||||
</key>
|
||||
<key alias="confirmUnpublish">Aufheben der Veröffentlichung entfernt diese Seite und ihre Unterseiten von der Website.</key>
|
||||
<key alias="doctypeChangeWarning">Es gibt ungesicherte Änderungen.
|
||||
<key alias="doctypeChangeWarning">Es gibt ungesicherte Änderungen.
|
||||
Ändern des Dokumenttyps macht diese rückgängig.
|
||||
</key>
|
||||
</area>
|
||||
@@ -518,7 +518,7 @@
|
||||
<key alias="permissionsSet">Berechtigungen vergeben für</key>
|
||||
<key alias="permissionsSetForGroup">Berechtigungen vergeben für %0% für Benutzer-Gruppe %1%</key>
|
||||
<key alias="permissionsHelp">Wählen Sie die Benutzer-Gruppe, deren Berechtigungen Sie setzen möchten</key>
|
||||
<key alias="recycleBinDeleting">Der Papierkorb wird geleert.
|
||||
<key alias="recycleBinDeleting">Der Papierkorb wird geleert.
|
||||
Bitte warten Sie und schließen Sie das Fenster erst, wenn der Vorgang abgeschlossen ist.
|
||||
</key>
|
||||
<key alias="recycleBinIsEmpty">Der Papierkorb ist leer</key>
|
||||
@@ -531,10 +531,10 @@
|
||||
<key alias="removeMacro">Macro entfernen</key>
|
||||
<key alias="requiredField">Pflichtfeld</key>
|
||||
<key alias="sitereindexed">Die Website-Index wurd neu erstellt</key>
|
||||
<key alias="siterepublished">Der Zwischenspeicher der Website wurde aktualisiert und alle veröffentlichten Inhalte sind jetzt auf dem neuesten Stand.
|
||||
<key alias="siterepublished">Der Zwischenspeicher der Website wurde aktualisiert und alle veröffentlichten Inhalte sind jetzt auf dem neuesten Stand.
|
||||
Bisher unveröffentliche Inhalte wurden dabei nicht veröffentlicht.
|
||||
</key>
|
||||
<key alias="siterepublishHelp">Der Zwischenspeicher der Website wird aktualisiert und der veröffentlichte Inhalt auf den neuesten Stand gebracht.
|
||||
<key alias="siterepublishHelp">Der Zwischenspeicher der Website wird aktualisiert und der veröffentlichte Inhalt auf den neuesten Stand gebracht.
|
||||
Unveröffentlichte Inhalte bleiben dabei weiterhin unveröffentlicht.
|
||||
</key>
|
||||
<key alias="tableColumns">Anzahl der Spalten</key>
|
||||
@@ -696,21 +696,21 @@
|
||||
<key alias="wasMoved">wurde verschoben in</key>
|
||||
<key alias="hasReferencesDeleteConsequence">
|
||||
<![CDATA[Löschen von <strong>%0%</strong> wird die Eigenschaften und Daten von folgenden Element löschen]]></key>
|
||||
<key alias="acceptDeleteConsequence">Ich verstehe das diese Aktion Eigenschaften und Daten basierend auf diesem
|
||||
<key alias="acceptDeleteConsequence">Ich verstehe das diese Aktion Eigenschaften und Daten basierend auf diesem
|
||||
DataTyps löschen wird.
|
||||
</key>
|
||||
</area>
|
||||
<area alias="errorHandling">
|
||||
<key alias="errorButDataWasSaved">Ihre Daten wurden gespeichert.
|
||||
<key alias="errorButDataWasSaved">Ihre Daten wurden gespeichert.
|
||||
Bevor Sie diese Seite jedoch veröffentlichen können, müssen Sie die folgenden Korrekturen vornehmen:
|
||||
</key>
|
||||
<key alias="errorChangingProviderPassword">Der aktuelle Mitgliedschaftsanbieter erlaubt keine Kennwortänderung
|
||||
<key alias="errorChangingProviderPassword">Der aktuelle Mitgliedschaftsanbieter erlaubt keine Kennwortänderung
|
||||
(EnablePasswordRetrieval muss auf "true" gesetzt sein)
|
||||
</key>
|
||||
<key alias="errorExistsWithoutTab">'%0%' ist bereits vorhanden</key>
|
||||
<key alias="errorHeader">Bitte prüfen und korrigieren:</key>
|
||||
<key alias="errorHeaderWithoutTab">Bitte prüfen und korrigieren:</key>
|
||||
<key alias="errorInPasswordFormat">Für das Kennwort ist eine Mindestlänge von %0% Zeichen vorgesehen,
|
||||
<key alias="errorInPasswordFormat">Für das Kennwort ist eine Mindestlänge von %0% Zeichen vorgesehen,
|
||||
wovon mindestens %1% Sonderzeichen (nicht alphanumerisch) sein müssen
|
||||
</key>
|
||||
<key alias="errorIntegerWithoutTab">'%0%' muss eine Zahl sein</key>
|
||||
@@ -724,7 +724,7 @@
|
||||
<key alias="concurrencyError">Optimistic concurrency Fehler, Objekte wurde geändert.</key>
|
||||
<key alias="receivedErrorFromServer">Der Server hat einen Fehler gemeldet</key>
|
||||
<key alias="dissallowedMediaType">Dieser Dateityp wird durch die Systemeinstellungen blockiert</key>
|
||||
<key alias="codemirroriewarning">ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist,
|
||||
<key alias="codemirroriewarning">ACHTUNG! Obwohl CodeMirror in den Einstellungen aktiviert ist,
|
||||
bleibt das Modul wegen mangelnder Stabilität in Internet Explorer deaktiviert.
|
||||
</key>
|
||||
<key alias="contentTypeAliasAndNameNotNull">Bitte geben Sie die Bezeichnung und den Alias des neuen Dokumenttyps ein.</key>
|
||||
@@ -732,7 +732,7 @@
|
||||
<key alias="macroErrorLoadingPartialView">Fehler beim Laden einer "Partial View Kodedatei" (Datei: %0%)</key>
|
||||
<key alias="missingTitle">Bitte geben Sie einen Titel ein</key>
|
||||
<key alias="missingType">Bitte wählen Sie einen Typ</key>
|
||||
<key alias="pictureResizeBiggerThanOrg">Soll die Abbildung wirklich über die
|
||||
<key alias="pictureResizeBiggerThanOrg">Soll die Abbildung wirklich über die
|
||||
Originalgröße hinaus vergrößert werden?
|
||||
</key>
|
||||
<key alias="startNodeDoesNotExists">Startelement gelöscht, bitte kontaktieren Sie den System-Administrator.</key>
|
||||
@@ -957,7 +957,7 @@
|
||||
<![CDATA[
|
||||
Klicken Sie auf <strong>Installieren</strong>, um die Datenbank für Umbraco %0% einzurichten.
|
||||
]]></key>
|
||||
<key alias="databaseInstallDone">Die Datenbank wurde für Umbraco %0% konfiguriert.
|
||||
<key alias="databaseInstallDone">Die Datenbank wurde für Umbraco %0% konfiguriert.
|
||||
Klicken Sie auf <strong>weiter</strong>, um fortzufahren.</key>
|
||||
<key alias="databaseText">Um diesen Schritt abzuschließen, müssen Sie die notwendigen Informationen zur Datenbankverbindung angeben.<br />Bitte kontaktieren Sie Ihren Provider bzw. Server-Administrator für weitere Informationen.</key>
|
||||
<key alias="databaseUpgrade">
|
||||
@@ -2059,9 +2059,8 @@
|
||||
<key alias="usergroup">Benutzergruppe</key>
|
||||
<key alias="userInvited">wurde eingeladen</key>
|
||||
<key alias="userInvitedSuccessHelp">Eine Einladung mit Anweisungen zur Anmeldung im Umbraco-Back-Office wurde dem neuen Benutzer zugeschickt.</key>
|
||||
<key alias="userinviteWelcomeMessage">Hallo und Willkommen bei Umbraco! In nur einer Minute sind Sie bereit loszulegen, Sie müssen nur ein Kennwort festlegen und optinal Ihrem Avatar ein Bild hinzufügen.</key>
|
||||
<key alias="userinviteWelcomeMessage">Hallo und Willkommen bei Umbraco! In nur einer Minute sind Sie bereit loszulegen, Sie müssen nur ein Kennwort festlegen.</key>
|
||||
<key alias="userinviteExpiredMessage">Willkommen bei Umbraco! Bedauerlicherweise ist Ihre Einladung verfallen. Bitte kontaktieren Sie Ihren Administrator und bitten Sie ihn, diese erneut zu schicken.</key>
|
||||
<key alias="userinviteAvatarMessage">Laden Sie ein Foto von sich hoch, um es anderen Benutzern zu erleichtern, sie zu erkennen. Klicken Sie auf den Kreis oben, um Ihr Foto hochzuladen.</key>
|
||||
<key alias="writer">Autor</key>
|
||||
<key alias="change">Änderung</key>
|
||||
<!--???-->
|
||||
@@ -2179,8 +2178,8 @@
|
||||
<key alias="stateInactive">Nicht aktiv</key>
|
||||
<key alias="sortNameAscending">Name (A-Z)</key>
|
||||
<key alias="sortNameDescending">Name (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Newest</key>
|
||||
<key alias="sortCreateDateDescending">Oldest</key>
|
||||
<key alias="sortCreateDateAscending">Oldest</key>
|
||||
<key alias="sortCreateDateDescending">Newest</key>
|
||||
<key alias="sortLastLoginDateDescending">Last login</key>
|
||||
</area>
|
||||
<area alias="validation">
|
||||
@@ -2339,7 +2338,7 @@
|
||||
</area>
|
||||
<area alias="contentTemplatesDashboard">
|
||||
<key alias="whatHeadline">Was sind Inhaltsvorlagen?</key>
|
||||
<key alias="whatDescription">Inhaltsvorlagen sind vordefinierte Inhalte die ausgewählt werden können
|
||||
<key alias="whatDescription">Inhaltsvorlagen sind vordefinierte Inhalte die ausgewählt werden können
|
||||
wenn Sie einen neuen Inhaltsknoten anlegen wollen.
|
||||
</key>
|
||||
<key alias="createHeadline">Wie erstelle ich eine Inhaltsvorlage?</key>
|
||||
@@ -2381,7 +2380,7 @@
|
||||
<key alias="FileWritingForPackages">Dateien durch Packages erstellen lassen</key>
|
||||
<key alias="FileWriting">Dateien schreiben</key>
|
||||
<key alias="MediaFolderCreation">Medien Ordner stellen</key>
|
||||
</area>
|
||||
</area>
|
||||
<area alias="treeSearch">
|
||||
<key alias="searchResult">Element zurückgegeben</key>
|
||||
<key alias="searchResults">Elemente zurückgegeben</key>
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
<key alias="createFolderFailed">Failed to create a folder under parent id %0%</key>
|
||||
<key alias="renameFolderFailed">Failed to rename the folder with id %0%</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</key>
|
||||
<key alias="fileSecurityValidationFailure">One or more file security validations have failed</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Create a new member</key>
|
||||
@@ -488,7 +489,7 @@
|
||||
<key alias="insertlink">Insert link</key>
|
||||
<key alias="insertMacro">Click to add a Macro</key>
|
||||
<key alias="inserttable">Insert table</key>
|
||||
<key alias="languagedeletewarning">This will delete the language</key>
|
||||
<key alias="languagedeletewarning">This will delete the language and all content related to the language</key>
|
||||
<key alias="languageChangeWarning">Changing the culture for a language may be an expensive operation and will result
|
||||
in the content cache and indexes being rebuilt
|
||||
</key>
|
||||
@@ -2124,14 +2125,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
Umbraco.
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we
|
||||
just need you to setup a password and add a picture for your avatar.
|
||||
just need you to setup a password.
|
||||
</key>
|
||||
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your
|
||||
administrator and ask them to resend it.
|
||||
</key>
|
||||
<key alias="userinviteAvatarMessage">Uploading a photo of yourself will make it easy for other users to recognize
|
||||
you. Click the circle above to upload your photo.
|
||||
</key>
|
||||
<key alias="writer">Writer</key>
|
||||
<key alias="change">Change</key>
|
||||
<key alias="yourProfile" version="7.0">Your profile</key>
|
||||
@@ -2245,8 +2243,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="stateInactive">Inactive</key>
|
||||
<key alias="sortNameAscending">Name (A-Z)</key>
|
||||
<key alias="sortNameDescending">Name (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Newest</key>
|
||||
<key alias="sortCreateDateDescending">Oldest</key>
|
||||
<key alias="sortCreateDateAscending">Oldest</key>
|
||||
<key alias="sortCreateDateDescending">Newest</key>
|
||||
<key alias="sortLastLoginDateDescending">Last login</key>
|
||||
<key alias="noUserGroupsAdded">No user groups have been added</key>
|
||||
<key alias="2faDisableText">If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device:</key>
|
||||
|
||||
@@ -352,6 +352,7 @@
|
||||
<key alias="renameFolderFailed">Failed to rename the folder with id %0%</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</key>
|
||||
<key alias="uploadNotAllowed">Upload is not allowed in this location.</key>
|
||||
<key alias="fileSecurityValidationFailure">One or more file security validations have failed</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Create a new member</key>
|
||||
@@ -503,7 +504,7 @@
|
||||
<key alias="insertlink">Insert link</key>
|
||||
<key alias="insertMacro">Click to add a Macro</key>
|
||||
<key alias="inserttable">Insert table</key>
|
||||
<key alias="languagedeletewarning">This will delete the language</key>
|
||||
<key alias="languagedeletewarning">This will delete the language and all content related to the language</key>
|
||||
<key alias="languageChangeWarning">Changing the culture for a language may be an expensive operation and will result
|
||||
in the content cache and indexes being rebuilt
|
||||
</key>
|
||||
@@ -757,6 +758,7 @@
|
||||
<key alias="by">by</key>
|
||||
<key alias="cancel">Cancel</key>
|
||||
<key alias="cellMargin">Cell margin</key>
|
||||
<key alias="change">Change</key>
|
||||
<key alias="choose">Choose</key>
|
||||
<key alias="clear">Clear</key>
|
||||
<key alias="close">Close</key>
|
||||
@@ -2218,14 +2220,11 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
Umbraco.
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we
|
||||
just need you to setup a password and add a picture for your avatar.
|
||||
just need you to setup a password.
|
||||
</key>
|
||||
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your
|
||||
administrator and ask them to resend it.
|
||||
</key>
|
||||
<key alias="userinviteAvatarMessage">Uploading a photo of yourself will make it easy for other users to recognize
|
||||
you. Click the circle above to upload your photo.
|
||||
</key>
|
||||
<key alias="writer">Writer</key>
|
||||
<key alias="configureTwoFactor">Configure Two-Factor</key>
|
||||
<key alias="change">Change</key>
|
||||
@@ -2340,8 +2339,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="stateInactive">Inactive</key>
|
||||
<key alias="sortNameAscending">Name (A-Z)</key>
|
||||
<key alias="sortNameDescending">Name (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Newest</key>
|
||||
<key alias="sortCreateDateDescending">Oldest</key>
|
||||
<key alias="sortCreateDateAscending">Oldest</key>
|
||||
<key alias="sortCreateDateDescending">Newest</key>
|
||||
<key alias="sortLastLoginDateDescending">Last login</key>
|
||||
<key alias="noUserGroupsAdded">No user groups have been added</key>
|
||||
<key alias="2faDisableText">If you wish to disable this two-factor provider, then you must enter the code shown on your authentication device:</key>
|
||||
@@ -3005,7 +3004,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
We will send:
|
||||
<ul>
|
||||
<li>Anonymized site ID, Umbraco version, and packages installed.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li>
|
||||
<li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>
|
||||
<li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>
|
||||
<li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li>
|
||||
</ul>
|
||||
|
||||
@@ -1394,8 +1394,7 @@
|
||||
<key alias="usergroup">Grupo de usuario</key>
|
||||
<key alias="userInvited">ha sido invitado</key>
|
||||
<key alias="userInvitedSuccessHelp">Se ha enviado una invitación al nuevo usuario con detalles sobre cómo acceder a Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">¡Hola y bienvenido a Umbraco!. En un minuto todo estará listo para empezar, sólo necesitamos que configures tu contraseña y una imagen para tu avatar.</key>
|
||||
<key alias="userinviteAvatarMessage">Sube una foto para que otros usuarios te reconozcan más fácilmente.</key>
|
||||
<key alias="userinviteWelcomeMessage">¡Hola y bienvenido a Umbraco!. En un minuto todo estará listo para empezar, sólo necesitamos que configures tu contraseña.</key>
|
||||
<key alias="writer">Redactor</key>
|
||||
<key alias="change">Cambiar</key>
|
||||
<key alias="yourProfile" version="7.0">Tu perfil</key>
|
||||
|
||||
@@ -1795,9 +1795,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="usergroup">Groupe d'utilisateurs</key>
|
||||
<key alias="userInvited">a été invité</key>
|
||||
<key alias="userInvitedSuccessHelp">Une invitation a été envoyée au nouvel utilisateur avec les détails concernant la connexion à Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Bien le bonjour et bienvenue dans Umbraco! Vous serez prêt.e dans moins d'1 minute, vous devez encore simplement configurer votre mot de passe et ajouter une photo pour votre avatar.</key>
|
||||
<key alias="userinviteWelcomeMessage">Bien le bonjour et bienvenue dans Umbraco! Vous serez prêt.e dans moins d'1 minute, vous devez encore simplement configurer votre mot de passe.</key>
|
||||
<key alias="userinviteExpiredMessage">Bienvenue dans Umbraco! Malheureusement, votre invitation a expiré. Veuillez contacter votre administrateur et demandez-lui de vous l'envoyer à nouveau.</key>
|
||||
<key alias="userinviteAvatarMessage">Chargez une photo afin que les autres utilisateurs puissent vous reconnaître facilement. Cliquez sur le cercle ci-dessus pour charger votre photo.</key>
|
||||
<key alias="writer">Rédacteur</key>
|
||||
<key alias="change">Modifier</key>
|
||||
<key alias="yourProfile" version="7.0">Votre profil</key>
|
||||
@@ -1910,8 +1909,8 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à
|
||||
<key alias="stateInactive">Inactif</key>
|
||||
<key alias="sortNameAscending">Nom (A-Z)</key>
|
||||
<key alias="sortNameDescending">Nom (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Plus récent</key>
|
||||
<key alias="sortCreateDateDescending">Plus ancien</key>
|
||||
<key alias="sortCreateDateAscending">Plus ancien</key>
|
||||
<key alias="sortCreateDateDescending">Plus récent</key>
|
||||
<key alias="sortLastLoginDateDescending">Dernière connexion</key>
|
||||
</area>
|
||||
<area alias="validation">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2197,13 +2197,10 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in
|
||||
<key alias="userInvitedSuccessHelp">
|
||||
<![CDATA[Un invito è stato invitato al nuovo utente con le istruzioni su come effettuare il login in Umbraco.]]></key>
|
||||
<key alias="userinviteWelcomeMessage">Ciao e benvenuto su Umbraco! In solo 1 minuto sarai pronto a partire, dovrai
|
||||
solamente impostare una password e aggiungere una foto profilo.
|
||||
solamente impostare una password.
|
||||
</key>
|
||||
<key alias="userinviteExpiredMessage">
|
||||
<![CDATA[Benvenuto su Umbraco! Purtroppo il tuo invito è scaduto. Per favore contatta l'amministratore e chiedigli di rispedirlo.]]></key>
|
||||
<key alias="userinviteAvatarMessage">Caricando una tua foto, gli altri utenti potranno riconoscerti facilmente. Fai
|
||||
clic sul cerchio sopra per caricare la tua foto.
|
||||
</key>
|
||||
<key alias="writer">Autore</key>
|
||||
<key alias="change">Modifica</key>
|
||||
<key alias="yourProfile" version="7.0">Il tuo profilo</key>
|
||||
@@ -2319,8 +2316,8 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in
|
||||
<key alias="stateInactive">Inattivi</key>
|
||||
<key alias="sortNameAscending">Nome (A-Z)</key>
|
||||
<key alias="sortNameDescending">Nome (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending"><![CDATA[Più nuovi]]></key>
|
||||
<key alias="sortCreateDateDescending"><![CDATA[Più vecchi]]></key>
|
||||
<key alias="sortCreateDateAscending"><![CDATA[Più vecchi]]></key>
|
||||
<key alias="sortCreateDateDescending"><![CDATA[Più nuovi]]></key>
|
||||
<key alias="sortLastLoginDateDescending">Ultimo login</key>
|
||||
<key alias="noUserGroupsAdded">Non sono stati aggiunti gruppi di utenti</key>
|
||||
</area>
|
||||
|
||||
@@ -340,6 +340,7 @@
|
||||
<key alias="renameFolderFailed">Kan de map met id %0% niet hernoemen</key>
|
||||
<key alias="dragAndDropYourFilesIntoTheArea">Sleep en zet je bestand(en) neer in dit gebied</key>
|
||||
<key alias="uploadNotAllowed">Upload is niet toegelaten in deze locatie.</key>
|
||||
<key alias="fileSecurityValidationFailure">Een of meerdere veiligheid validaties zijn gefaald voor het bestand</key>
|
||||
</area>
|
||||
<area alias="member">
|
||||
<key alias="createNewMember">Maak nieuw lid aan</key>
|
||||
@@ -1937,12 +1938,11 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
te loggen in Umbraco
|
||||
</key>
|
||||
<key alias="userinviteWelcomeMessage">Hallo en welkom in Umbraco! Binnen ongeveer één minuut kan je aan de slag. Je
|
||||
moet enkel je wachtwoord instellen en een foto toevoegen.
|
||||
moet enkel je wachtwoord instellen.
|
||||
</key>
|
||||
<key alias="userinviteExpiredMessage">Welkom bij Umbraco! Helaas is je uitnodiging vervallen. Vraag aan je
|
||||
administrator om de uitnodiging opnieuw te versturen.
|
||||
</key>
|
||||
<key alias="userinviteAvatarMessage">Wijzig je foto zodat andere gebruikers je makkelijk kunnen herkennen.</key>
|
||||
<key alias="writer">Auteur</key>
|
||||
<key alias="configureTwoFactor">Configureer tweestapsverificatie</key>
|
||||
<key alias="change">Wijzig</key>
|
||||
@@ -2056,8 +2056,8 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
|
||||
<key alias="stateInactive">Inactief</key>
|
||||
<key alias="sortNameAscending">Naam (A-Z)</key>
|
||||
<key alias="sortNameDescending">Naam (Z-A)</key>
|
||||
<key alias="sortCreateDateAscending">Nieuwste</key>
|
||||
<key alias="sortCreateDateDescending">Oudste</key>
|
||||
<key alias="sortCreateDateAscending">Oudste</key>
|
||||
<key alias="sortCreateDateDescending">Nieuwste</key>
|
||||
<key alias="sortLastLoginDateDescending">Laatste login</key>
|
||||
<key alias="noUserGroupsAdded">Er zijn geen gebruikersgroepen toegevoegd</key>
|
||||
</area>
|
||||
|
||||
@@ -822,6 +822,8 @@ Você pode publicar esta página e todas suas sub-páginas ao selecionar <em>pub
|
||||
<key alias="usertype">Tipo de usuário</key>
|
||||
<key alias="userTypes">Tipos de usuários</key>
|
||||
<key alias="writer">Escrevente</key>
|
||||
<key alias="sortCreateDateAscending">Mais antigo</key>
|
||||
<key alias="sortCreateDateDescending">Mais recente</key>
|
||||
</area>
|
||||
<area alias="logViewer">
|
||||
<key alias="selectAllLogLevelFilters">Selecionar tudo</key>
|
||||
|
||||
@@ -1703,8 +1703,7 @@
|
||||
<key alias="usergroup">Группа пользователей</key>
|
||||
<key alias="userInvited"> был приглашен</key>
|
||||
<key alias="userInvitedSuccessHelp">Новому пользователю было отправлено приглашение, в котором содержатся инструкции для входа в панель Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа и добавить аватар.</key>
|
||||
<key alias="userinviteAvatarMessage">Загрузите изображение, это поможет другим пользователям идентифицировать Вас.</key>
|
||||
<key alias="userinviteWelcomeMessage">Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа.</key>
|
||||
<key alias="userManagement">Управление пользователями</key>
|
||||
<key alias="userPermissions">Разрешения для пользователя</key>
|
||||
<key alias="writer">Автор</key>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
<key alias="saveToPublish">Spara och skicka för godkännande</key>
|
||||
<key alias="schedulePublish">Schemaläggning</key>
|
||||
<key alias="select">Välj</key>
|
||||
<key alias="saveAndPreview">Förhandsgranska</key>
|
||||
<key alias="saveAndPreview">Spara och förhandsgranska</key>
|
||||
<key alias="showPageDisabled">Förhandsgranskning är avstängt på grund av att det inte finns någon mall tilldelad</key>
|
||||
<key alias="somethingElse">Gör något annat</key>
|
||||
<key alias="styleChoose">Välj stil</key>
|
||||
@@ -1001,8 +1001,8 @@
|
||||
<key alias="writer">Skribent</key>
|
||||
<key alias="yourHistory">Din nuvarande historik</key>
|
||||
<key alias="yourProfile">Din profil</key>
|
||||
<key alias="sortCreateDateAscending">Nyast</key>
|
||||
<key alias="sortCreateDateDescending">Äldst</key>
|
||||
<key alias="sortCreateDateAscending">Äldst</key>
|
||||
<key alias="sortCreateDateDescending">Nyast</key>
|
||||
<key alias="sortLastLoginDateDescending">Senaste login</key>
|
||||
</area>
|
||||
<area alias="logViewer">
|
||||
|
||||
@@ -1879,9 +1879,8 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<key alias="usergroup">Kullanıcı grubu</key>
|
||||
<key alias="userInvited">davet edildi</key>
|
||||
<key alias="userInvitedSuccessHelp">Yeni kullanıcıya, Umbraco'da nasıl oturum açılacağına ilişkin ayrıntıları içeren bir davetiye gönderildi.</key>
|
||||
<key alias="userinviteWelcomeMessage">Merhabalar, Umbraco'ya hoş geldiniz! Sadece 1 dakika içinde hazır olacaksınız, sadece bir şifre belirlemeniz ve avatarınız için bir resim eklemeniz gerekiyor.</key>
|
||||
<key alias="userinviteWelcomeMessage">Merhabalar, Umbraco'ya hoş geldiniz! Sadece 1 dakika içinde hazır olacaksınız, sadece bir şifre belirlemeniz.</key>
|
||||
<key alias="userinviteExpiredMessage">Umbraco'ya hoş geldiniz! Maalesef davetinizin süresi doldu. Lütfen yöneticinizle iletişime geçin ve yeniden göndermesini isteyin.</key>
|
||||
<key alias="userinviteAvatarMessage">Kendi fotoğrafınızı yüklemek, diğer kullanıcıların sizi tanımasını kolaylaştıracaktır. Fotoğrafınızı yüklemek için yukarıdaki daireyi tıklayın.</key>
|
||||
<key alias="writer">Yazar</key>
|
||||
<key alias="change">Değiştir</key>
|
||||
<key alias="yourProfile" version="7.0">Profiliniz</key>
|
||||
@@ -1996,8 +1995,8 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey
|
||||
<key alias="stateInactive">Etkin Değil</key>
|
||||
<key alias="sortNameAscending">Ad (AZ)</key>
|
||||
<key alias="sortNameDescending">Ad (ZA)</key>
|
||||
<key alias="sortCreateDateAscending">En yeni</key>
|
||||
<key alias="sortCreateDateDescending">En eski</key>
|
||||
<key alias="sortCreateDateAscending">En eski</key>
|
||||
<key alias="sortCreateDateDescending">En yeni</key>
|
||||
<key alias="sortLastLoginDateDescending">Son giriş</key>
|
||||
<key alias="noUserGroupsAdded">Hiçbir kullanıcı grubu eklenmedi</key>
|
||||
</area>
|
||||
|
||||
@@ -1703,8 +1703,7 @@
|
||||
<key alias="usergroup">Група користувачів</key>
|
||||
<key alias="userInvited"> був запрошений</key>
|
||||
<key alias="userInvitedSuccessHelp">Новому користувачеві було надіслано запрошення, яке містить інструкції для входу в панель Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Привіт і ласкаво просимо до Umbraco! Все буде готове протягом декількох хвилин, нам потрібно задати пароль для входу і додати аватар.</key>
|
||||
<key alias="userinviteAvatarMessage">Завантажте зображення, що допоможе іншим користувачам ідентифікувати Вас.</key>
|
||||
<key alias="userinviteWelcomeMessage">Привіт і ласкаво просимо до Umbraco! Все буде готове протягом декількох хвилин, нам потрібно задати пароль для входу.</key>
|
||||
<key alias="userManagement">Управління користувачами</key>
|
||||
<key alias="userPermissions">Дозволи для користувача</key>
|
||||
<key alias="writer">Автор</key>
|
||||
|
||||
@@ -137,7 +137,7 @@ internal class NotificationAsyncHandlerWrapperImpl<TNotification> : Notification
|
||||
/// confusion.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public override Task HandleAsync(
|
||||
public override async Task HandleAsync(
|
||||
INotification notification,
|
||||
CancellationToken cancellationToken,
|
||||
ServiceFactory serviceFactory,
|
||||
@@ -155,7 +155,7 @@ internal class NotificationAsyncHandlerWrapperImpl<TNotification> : Notification
|
||||
(theNotification, theToken) =>
|
||||
x.HandleAsync((TNotification)theNotification, theToken)));
|
||||
|
||||
return publish(handlers, notification, cancellationToken);
|
||||
await publish(handlers, notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public partial class EventAggregator : IEventAggregator
|
||||
=> _serviceFactory = serviceFactory;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
public async Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification
|
||||
{
|
||||
// TODO: Introduce codegen efficient Guard classes to reduce noise.
|
||||
@@ -60,7 +60,7 @@ public partial class EventAggregator : IEventAggregator
|
||||
}
|
||||
|
||||
PublishNotification(notification);
|
||||
return PublishNotificationAsync(notification, cancellationToken);
|
||||
await PublishNotificationAsync(notification, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -219,96 +219,52 @@ public static class TypeExtensions
|
||||
/// <returns></returns>
|
||||
public static PropertyInfo[] GetAllProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
PropertyInfo[] typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance);
|
||||
|
||||
IEnumerable<PropertyInfo> newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all public properties including inherited properties even for interfaces
|
||||
/// Returns public properties including inherited properties even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// taken from
|
||||
/// http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
public static PropertyInfo[] GetPublicProperties(this Type type)
|
||||
{
|
||||
if (type.IsInterface)
|
||||
{
|
||||
var propertyInfos = new List<PropertyInfo>();
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
|
||||
}
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns public methods including inherited methods even for interfaces
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo[] GetPublicMethods(this Type type)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetMethods(bindingFlags));
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
PropertyInfo[] typeProperties = subType.GetProperties(
|
||||
BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.Instance);
|
||||
|
||||
IEnumerable<PropertyInfo> newPropertyInfos = typeProperties
|
||||
.Where(x => !propertyInfos.Contains(x));
|
||||
|
||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray();
|
||||
}
|
||||
|
||||
return type.GetProperties(BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public | BindingFlags.Instance);
|
||||
/// <summary>
|
||||
/// Returns all methods including inherited methods even for interfaces
|
||||
/// </summary>
|
||||
/// <remarks>Includes both Public and Non-Public methods</remarks>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo[] GetAllMethods(this Type type)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
|
||||
| BindingFlags.Public
|
||||
| BindingFlags.NonPublic
|
||||
| BindingFlags.Instance;
|
||||
return type.GetAllMemberInfos(t => t.GetMethods(bindingFlags));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -512,4 +468,47 @@ public static class TypeExtensions
|
||||
|
||||
return attempt;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// taken from
|
||||
/// http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy
|
||||
/// </remarks>
|
||||
private static T[] GetAllMemberInfos<T>(this Type type, Func<Type, T[]> getMemberInfos)
|
||||
where T : MemberInfo
|
||||
{
|
||||
if (type.IsInterface is false)
|
||||
{
|
||||
return getMemberInfos(type);
|
||||
}
|
||||
|
||||
var memberInfos = new List<T>();
|
||||
|
||||
var considered = new List<Type>();
|
||||
var queue = new Queue<Type>();
|
||||
considered.Add(type);
|
||||
queue.Enqueue(type);
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
Type subType = queue.Dequeue();
|
||||
foreach (Type subInterface in subType.GetInterfaces())
|
||||
{
|
||||
if (considered.Contains(subInterface))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
considered.Add(subInterface);
|
||||
queue.Enqueue(subInterface);
|
||||
}
|
||||
|
||||
T[] typeMethodInfos = getMemberInfos(subType);
|
||||
|
||||
IEnumerable<T> newMethodInfos = typeMethodInfos
|
||||
.Where(x => !memberInfos.Contains(x));
|
||||
|
||||
memberInfos.InsertRange(0, newMethodInfos);
|
||||
}
|
||||
|
||||
return memberInfos.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,7 +7,15 @@ namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
/// <para>This is for tests etc - does not implement fallback at all.</para>
|
||||
/// </remarks>
|
||||
public class NoopPublishedValueFallback : IPublishedValueFallback
|
||||
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IVariationContextAccessor VariationContextAccessor
|
||||
{
|
||||
get => new ThreadCultureVariationContextAccessor();
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value)
|
||||
{
|
||||
|
||||
@@ -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,11 +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)
|
||||
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());
|
||||
}
|
||||
|
||||
/// <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>(), 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,5 +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 non-obsolete overload, scheduled for removal in v14")]
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -11,21 +16,37 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IPropertyIndexValueFactory
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private IndexingSettings _indexingSettings;
|
||||
|
||||
protected bool ForceExplicitlyIndexEachNestedProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the JsonPropertyIndexValueFactoryBase.
|
||||
/// </summary>
|
||||
protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer)
|
||||
protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer, IOptionsMonitor<IndexingSettings> indexingSettings)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_indexingSettings = indexingSettings.CurrentValue;
|
||||
indexingSettings.OnChange(newValue => _indexingSettings = newValue);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the JsonPropertyIndexValueFactoryBase.
|
||||
/// </summary>
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 14.")]
|
||||
protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer): this(jsonSerializer, StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<IndexingSettings>>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published)
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures,
|
||||
IDictionary<Guid, IContentType> contentTypeDictionary)
|
||||
{
|
||||
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
|
||||
@@ -43,7 +64,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
return result;
|
||||
}
|
||||
|
||||
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published));
|
||||
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures, contentTypeDictionary));
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
@@ -57,13 +78,44 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
}
|
||||
}
|
||||
|
||||
result.AddRange(HandleResume(result, property, culture, segment, published));
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> summary = HandleResume(result, property, culture, segment, published);
|
||||
if (_indexingSettings.ExplicitlyIndexEachNestedProperty || ForceExplicitlyIndexEachNestedProperty)
|
||||
{
|
||||
result.AddRange(summary);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
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>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
/// <summary>
|
||||
/// Method to return a list of resume of the content. By default this returns an empty list
|
||||
/// Method to return a list of summary of the content. By default this returns an empty list
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<KeyValuePair<string, IEnumerable<object?>>> HandleResume(
|
||||
List<KeyValuePair<string, IEnumerable<object?>>> result,
|
||||
@@ -75,10 +127,33 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
|
||||
/// <summary>
|
||||
/// Method that handle the deserialized object.
|
||||
/// </summary>
|
||||
[Obsolete("Use the non-obsolete overload instead, scheduled for removal in v14")]
|
||||
protected abstract IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
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>
|
||||
protected virtual IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
TSerialized deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool 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,5 +8,15 @@ 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) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
|
||||
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")]
|
||||
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
|
||||
=> GetIndexValues(property, culture, segment, published);
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ public class SliderConfiguration
|
||||
[ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")]
|
||||
public decimal InitialValue2 { get; set; }
|
||||
|
||||
[ConfigurationField("minVal", "Minimum value", "number")]
|
||||
[ConfigurationField("minVal", "Minimum value", "number", Description = "Must be smaller than the Maximum value")]
|
||||
public decimal MinimumValue { get; set; }
|
||||
|
||||
[ConfigurationField("maxVal", "Maximum value", "number")]
|
||||
[ConfigurationField("maxVal", "Maximum value", "number", Description = "Must be larger than the Minimum value")]
|
||||
public decimal MaximumValue { get; set; }
|
||||
|
||||
[ConfigurationField("step", "Step increments", "number")]
|
||||
[ConfigurationField("step", "Step increments", "number", Description = "Must be a positive value")]
|
||||
public decimal StepIncrements { get; set; }
|
||||
}
|
||||
|
||||
@@ -25,4 +25,19 @@ public class SliderConfigurationEditor : ConfigurationEditor<SliderConfiguration
|
||||
ioHelper, editorConfigurationParser)
|
||||
{
|
||||
}
|
||||
|
||||
public override Dictionary<string, object> ToConfigurationEditor(SliderConfiguration? configuration)
|
||||
{
|
||||
// negative step increments can be configured in the back-office. they will cause the slider to
|
||||
// crash the entire back-office. as we can't configure min and max values for the number prevalue
|
||||
// editor, we have to this instead to limit the damage.
|
||||
// logically, the step increments should be inverted instead of hardcoding them to 1, but the
|
||||
// latter might point people in the direction of their misconfiguration.
|
||||
if (configuration?.StepIncrements <= 0)
|
||||
{
|
||||
configuration.StepIncrements = 1;
|
||||
}
|
||||
|
||||
return base.ToConfigurationEditor(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
public class TagPropertyIndexValueFactory : JsonPropertyIndexValueFactoryBase<string[]>, ITagPropertyIndexValueFactory
|
||||
{
|
||||
public TagPropertyIndexValueFactory(IJsonSerializer jsonSerializer) : base(jsonSerializer)
|
||||
public TagPropertyIndexValueFactory(
|
||||
IJsonSerializer jsonSerializer,
|
||||
IOptionsMonitor<IndexingSettings> indexingSettings)
|
||||
: base(jsonSerializer, indexingSettings)
|
||||
{
|
||||
ForceExplicitlyIndexEachNestedProperty = true;
|
||||
}
|
||||
|
||||
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 14.")]
|
||||
public TagPropertyIndexValueFactory(IJsonSerializer jsonSerializer)
|
||||
: this(jsonSerializer, StaticServiceProvider.Instance.GetRequiredService<IOptionsMonitor<IndexingSettings>>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
string[] deserializedPropertyValue,
|
||||
IProperty property,
|
||||
string? culture,
|
||||
string? segment,
|
||||
bool published,
|
||||
IEnumerable<string> availableCultures)
|
||||
{
|
||||
yield return new KeyValuePair<string, IEnumerable<object?>>(property.Alias, deserializedPropertyValue);
|
||||
}
|
||||
|
||||
[Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")]
|
||||
protected override IEnumerable<KeyValuePair<string, IEnumerable<object?>>> Handle(
|
||||
string[] deserializedPropertyValue,
|
||||
IProperty property,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -6,74 +6,123 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
/// <summary>
|
||||
/// The slider property value converter.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase" />
|
||||
[DefaultPropertyValueConverter]
|
||||
public class SliderValueConverter : PropertyValueConverterBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, bool> Storages = new();
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SliderValueConverter" /> class.
|
||||
/// </summary>
|
||||
public SliderValueConverter()
|
||||
{ }
|
||||
|
||||
public SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService =
|
||||
dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SliderValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dataTypeService">The data type service.</param>
|
||||
[Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")]
|
||||
public SliderValueConverter(IDataTypeService dataTypeService)
|
||||
{ }
|
||||
|
||||
public static void ClearCaches() => Storages.Clear();
|
||||
/// <summary>
|
||||
/// Clears the data type configuration caches.
|
||||
/// </summary>
|
||||
[Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")]
|
||||
public static void ClearCaches()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> IsRangeDataType(propertyType.DataType.Id) ? typeof(Range<decimal>) : typeof(decimal);
|
||||
=> IsRange(propertyType) ? typeof(Range<decimal>) : typeof(decimal);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview)
|
||||
{
|
||||
if (source == null)
|
||||
bool isRange = IsRange(propertyType);
|
||||
|
||||
var sourceString = source?.ToString();
|
||||
|
||||
return isRange
|
||||
? HandleRange(sourceString)
|
||||
: HandleDecimal(sourceString);
|
||||
}
|
||||
|
||||
private static Range<decimal> HandleRange(string? sourceString)
|
||||
{
|
||||
if (sourceString is null)
|
||||
{
|
||||
return null;
|
||||
return new Range<decimal>();
|
||||
}
|
||||
|
||||
if (IsRangeDataType(propertyType.DataType.Id))
|
||||
{
|
||||
var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma);
|
||||
Attempt<decimal> minimumAttempt = rangeRawValues[0].TryConvertTo<decimal>();
|
||||
Attempt<decimal> maximumAttempt = rangeRawValues[1].TryConvertTo<decimal>();
|
||||
string[] rangeRawValues = sourceString.Split(Constants.CharArrays.Comma);
|
||||
|
||||
if (minimumAttempt.Success && maximumAttempt.Success)
|
||||
if (TryParseDecimal(rangeRawValues[0], out var minimum))
|
||||
{
|
||||
if (rangeRawValues.Length == 1)
|
||||
{
|
||||
return new Range<decimal> { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result };
|
||||
// Configuration is probably changed from single to range, return range with same min/max
|
||||
return new Range<decimal>
|
||||
{
|
||||
Minimum = minimum,
|
||||
Maximum = minimum
|
||||
};
|
||||
}
|
||||
|
||||
if (rangeRawValues.Length == 2 && TryParseDecimal(rangeRawValues[1], out var maximum))
|
||||
{
|
||||
return new Range<decimal>
|
||||
{
|
||||
Minimum = minimum,
|
||||
Maximum = maximum
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Attempt<decimal> valueAttempt = source.ToString().TryConvertTo<decimal>();
|
||||
if (valueAttempt.Success)
|
||||
return new Range<decimal>();
|
||||
}
|
||||
|
||||
private static decimal HandleDecimal(string? sourceString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourceString))
|
||||
{
|
||||
return valueAttempt.Result;
|
||||
return default;
|
||||
}
|
||||
|
||||
// Something failed in the conversion of the strings to decimals
|
||||
return null;
|
||||
// This used to be a range slider, so we'll assign the minimum value as the new value
|
||||
if (sourceString.Contains(','))
|
||||
{
|
||||
var minimumValueRepresentation = sourceString.Split(Constants.CharArrays.Comma)[0];
|
||||
|
||||
if (TryParseDecimal(minimumValueRepresentation, out var minimum))
|
||||
{
|
||||
return minimum;
|
||||
}
|
||||
}
|
||||
else if (TryParseDecimal(sourceString, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discovers if the slider is set to range mode.
|
||||
/// Helper method for parsing a double consistently
|
||||
/// </summary>
|
||||
/// <param name="dataTypeId">
|
||||
/// The data type id.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool" />.
|
||||
/// </returns>
|
||||
private bool IsRangeDataType(int dataTypeId) =>
|
||||
private static bool TryParseDecimal(string? representation, out decimal value)
|
||||
=> decimal.TryParse(representation, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
||||
|
||||
// GetPreValuesCollectionByDataTypeId is cached at repository level;
|
||||
// still, the collection is deep-cloned so this is kinda expensive,
|
||||
// better to cache here + trigger refresh in DataTypeCacheRefresher
|
||||
// TODO: this is cheap now, remove the caching
|
||||
Storages.GetOrAdd(dataTypeId, id =>
|
||||
{
|
||||
IDataType? dataType = _dataTypeService.GetDataType(id);
|
||||
SliderConfiguration? configuration = dataType?.ConfigurationAs<SliderConfiguration>();
|
||||
return configuration?.EnableRange ?? false;
|
||||
});
|
||||
private static bool IsRange(IPublishedPropertyType propertyType)
|
||||
=> propertyType.DataType.ConfigurationAs<SliderConfiguration>()?.EnableRange == true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -7,69 +6,66 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
/// <summary>
|
||||
/// The tags property value converter.
|
||||
/// </summary>
|
||||
/// <seealso cref="Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase" />
|
||||
[DefaultPropertyValueConverter]
|
||||
public class TagsValueConverter : PropertyValueConverterBase
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, bool> Storages = new();
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TagsValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
||||
public TagsValueConverter(IJsonSerializer jsonSerializer)
|
||||
=> _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TagsValueConverter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dataTypeService">The data type service.</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
[Obsolete("The IDataTypeService is not used anymore. This constructor will be removed in a future version.")]
|
||||
public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
_jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
|
||||
}
|
||||
: this(jsonSerializer)
|
||||
{ }
|
||||
|
||||
public static void ClearCaches() => Storages.Clear();
|
||||
/// <summary>
|
||||
/// Clears the data type configuration caches.
|
||||
/// </summary>
|
||||
[Obsolete("Caching of data type configuration is not done anymore. This method will be removed in a future version.")]
|
||||
public static void ClearCaches()
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> typeof(IEnumerable<string>);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> PropertyCacheLevel.Element;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)
|
||||
{
|
||||
if (source == null)
|
||||
string? sourceString = source?.ToString();
|
||||
if (string.IsNullOrEmpty(sourceString))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// if Json storage type deserialize and return as string array
|
||||
if (JsonStorageType(propertyType.DataType.Id))
|
||||
{
|
||||
var array = source.ToString() is not null
|
||||
? _jsonSerializer.Deserialize<string[]>(source.ToString()!)
|
||||
: null;
|
||||
return array ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
// Otherwise assume CSV storage type and return as string array
|
||||
return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
|
||||
return IsJson(propertyType)
|
||||
? _jsonSerializer.Deserialize<string[]>(sourceString) ?? Array.Empty<string>()
|
||||
: sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers if the tags data type is storing its data in a Json format
|
||||
/// </summary>
|
||||
/// <param name="dataTypeId">
|
||||
/// The data type id.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool" />.
|
||||
/// </returns>
|
||||
private bool JsonStorageType(int dataTypeId) =>
|
||||
|
||||
// GetDataType(id) is cached at repository level; still, there is some
|
||||
// deep-cloning involved (expensive) - better cache here + trigger
|
||||
// refresh in DataTypeCacheRefresher
|
||||
Storages.GetOrAdd(dataTypeId, id =>
|
||||
{
|
||||
TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs<TagConfiguration>();
|
||||
return configuration?.StorageType == TagsStorageType.Json;
|
||||
});
|
||||
private static bool IsJson(IPublishedPropertyType propertyType)
|
||||
=> propertyType.DataType.ConfigurationAs<TagConfiguration>()?.StorageType == TagsStorageType.Json;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,56 @@ using System.Security.Principal;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IIdentity" />.
|
||||
/// </summary>
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the thread culture is set based on the back office user's culture
|
||||
/// Ensures that the thread culture is set based on the back office user's culture.
|
||||
/// </summary>
|
||||
/// <param name="identity">The identity.</param>
|
||||
public static void EnsureCulture(this IIdentity identity)
|
||||
{
|
||||
CultureInfo? culture = GetCulture(identity);
|
||||
if (!(culture is null))
|
||||
if (culture is not null)
|
||||
{
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture string from the back office user.
|
||||
/// </summary>
|
||||
/// <param name="identity">The identity.</param>
|
||||
/// <returns>
|
||||
/// The culture string.
|
||||
/// </returns>
|
||||
public static string? GetCultureString(this IIdentity identity)
|
||||
{
|
||||
if (identity is ClaimsIdentity umbIdentity &&
|
||||
umbIdentity.VerifyBackOfficeIdentity(out _) &&
|
||||
umbIdentity.IsAuthenticated)
|
||||
{
|
||||
return umbIdentity.GetCultureString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture from the back office user.
|
||||
/// </summary>
|
||||
/// <param name="identity">The identity.</param>
|
||||
/// <returns>
|
||||
/// The culture.
|
||||
/// </returns>
|
||||
public static CultureInfo? GetCulture(this IIdentity identity)
|
||||
{
|
||||
if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) &&
|
||||
umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null)
|
||||
string? culture = identity.GetCultureString();
|
||||
if (!string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!);
|
||||
return CultureInfo.GetCultureInfo(culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -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,38 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public class FileStreamSecurityValidator : IFileStreamSecurityValidator
|
||||
{
|
||||
private readonly IEnumerable<IFileStreamSecurityAnalyzer> _fileAnalyzers;
|
||||
|
||||
public FileStreamSecurityValidator(IEnumerable<IFileStreamSecurityAnalyzer> fileAnalyzers)
|
||||
{
|
||||
_fileAnalyzers = fileAnalyzers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes whether the file content is considered safe with registered IFileStreamSecurityAnalyzers
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe after running the necessary analyzers</returns>
|
||||
public bool IsConsideredSafe(Stream fileStream)
|
||||
{
|
||||
foreach (var fileAnalyzer in _fileAnalyzers)
|
||||
{
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
if (!fileAnalyzer.ShouldHandle(fileStream))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
if (fileAnalyzer.IsConsideredSafe(fileStream) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
// If no analyzer we consider the file to be safe as the implementer has the possibility to add additional analyzers
|
||||
// Or all analyzers deem te file to be safe
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public interface IFileStreamSecurityAnalyzer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the analyzer should process the file
|
||||
/// The implementation should be considerably faster than IsConsideredSafe
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <returns></returns>
|
||||
bool ShouldHandle(Stream fileStream);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes whether the file content is considered safe
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read/Write seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe</returns>
|
||||
bool IsConsideredSafe(Stream fileStream);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Umbraco.Cms.Core.Security;
|
||||
|
||||
public interface IFileStreamSecurityValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes wether the file content is considered safe with registered IFileStreamSecurityAnalyzers
|
||||
/// </summary>
|
||||
/// <param name="fileStream">Needs to be a Read seekable stream</param>
|
||||
/// <returns>Whether the file is considered safe after running the necessary analyzers</returns>
|
||||
bool IsConsideredSafe(Stream fileStream);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -36,20 +36,20 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ContentService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IDocumentRepository documentRepository,
|
||||
IEntityRepository entityRepository,
|
||||
IAuditRepository auditRepository,
|
||||
IContentTypeRepository contentTypeRepository,
|
||||
IDocumentBlueprintRepository documentBlueprintRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
Lazy<IPropertyValidationService> propertyValidationService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
ICultureImpactFactory cultureImpactFactory)
|
||||
: base(provider, loggerFactory, eventMessagesFactory)
|
||||
public ContentService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IDocumentRepository documentRepository,
|
||||
IEntityRepository entityRepository,
|
||||
IAuditRepository auditRepository,
|
||||
IContentTypeRepository contentTypeRepository,
|
||||
IDocumentBlueprintRepository documentBlueprintRepository,
|
||||
ILanguageRepository languageRepository,
|
||||
Lazy<IPropertyValidationService> propertyValidationService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
ICultureImpactFactory cultureImpactFactory)
|
||||
: base(provider, loggerFactory, eventMessagesFactory)
|
||||
{
|
||||
_documentRepository = documentRepository;
|
||||
_entityRepository = entityRepository;
|
||||
@@ -59,7 +59,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
_languageRepository = languageRepository;
|
||||
_propertyValidationService = propertyValidationService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_cultureImpactFactory = cultureImpactFactory;
|
||||
_cultureImpactFactory = cultureImpactFactory;
|
||||
_logger = loggerFactory.CreateLogger<ContentService>();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1146,6 +1150,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
var allLangs = _languageRepository.GetMany().ToList();
|
||||
|
||||
// Change state to publishing
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
var savingNotification = new ContentSavingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
@@ -1156,7 +1162,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// if culture is '*', then publish them all (including variants)
|
||||
|
||||
// this will create the correct culture impact even if culture is * or null
|
||||
var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
|
||||
// publish the culture(s)
|
||||
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
|
||||
@@ -1540,7 +1546,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// handling events, business rules, etc
|
||||
// note: StrategyUnpublish flips the PublishedState to Unpublishing!
|
||||
// note: This unpublishes the entire document (not different variants)
|
||||
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages);
|
||||
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages, notificationState);
|
||||
if (unpublishResult.Success)
|
||||
{
|
||||
unpublishResult = StrategyUnpublish(content, eventMessages);
|
||||
@@ -1843,7 +1849,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
// publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
|
||||
IProperty[]? invalidProperties = null;
|
||||
var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture));
|
||||
var impact = _cultureImpactFactory.ImpactExplicit(culture, IsDefaultCulture(allLangs.Value, culture));
|
||||
var tryPublish = d.PublishCulture(impact) &&
|
||||
_propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
|
||||
if (invalidProperties != null && invalidProperties.Length > 0)
|
||||
@@ -1927,14 +1933,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
{
|
||||
return culturesToPublish.All(culture =>
|
||||
{
|
||||
var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
var impact = _cultureImpactFactory.Create(culture, IsDefaultCulture(allLangs, culture), content);
|
||||
return content.PublishCulture(impact) &&
|
||||
_propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
|
||||
});
|
||||
}
|
||||
|
||||
return content.PublishCulture(_cultureImpactFactory.ImpactInvariant())
|
||||
&& _propertyValidationService.Value.IsPropertyDataValid(content, out _, _cultureImpactFactory.ImpactInvariant());
|
||||
return content.PublishCulture(_cultureImpactFactory.ImpactInvariant())
|
||||
&& _propertyValidationService.Value.IsPropertyDataValid(content, out _, _cultureImpactFactory.ImpactInvariant());
|
||||
}
|
||||
|
||||
// utility 'ShouldPublish' func used by SaveAndPublishBranch
|
||||
@@ -2103,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);
|
||||
@@ -2138,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);
|
||||
@@ -2162,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();
|
||||
}
|
||||
@@ -2183,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
|
||||
@@ -2216,6 +2224,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (result.Success)
|
||||
{
|
||||
publishedDocuments.Add(document);
|
||||
notificationState = savingNotification.State;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -2432,7 +2441,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="userId">Optional Id of the User moving the Content</param>
|
||||
public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
if(content.ParentId == parentId)
|
||||
if (content.ParentId == parentId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -2583,7 +2592,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
IContent[] contents = _documentRepository.Get(query).ToArray();
|
||||
|
||||
var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
|
||||
var deletingContentNotification = new ContentDeletingNotification(contents, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification) || scope.Notifications.PublishCancelable(deletingContentNotification))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Cancel(eventMessages);
|
||||
@@ -2948,13 +2958,13 @@ public class ContentService : RepositoryService, IContentService
|
||||
{
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(published, eventMessages));
|
||||
}
|
||||
|
||||
|
||||
return OperationResult.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
@@ -2967,6 +2977,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
@@ -3293,10 +3305,10 @@ public class ContentService : RepositoryService, IContentService
|
||||
/// <param name="content"></param>
|
||||
/// <param name="evtMsgs"></param>
|
||||
/// <returns></returns>
|
||||
private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs)
|
||||
private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs, IDictionary<string, object?>? notificationState)
|
||||
{
|
||||
// raise Unpublishing notification
|
||||
if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs)))
|
||||
if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState)))
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
|
||||
@@ -3562,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();
|
||||
}
|
||||
@@ -3576,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();
|
||||
}
|
||||
}
|
||||
@@ -3676,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -16,7 +16,8 @@ public static class ContentServiceExtensions
|
||||
{
|
||||
#region RTE Anchor values
|
||||
|
||||
private static readonly Regex AnchorRegex = new("<a id=\"(.*?)\">", RegexOptions.Compiled);
|
||||
private static readonly Regex AnchorRegex = new(@"<a id=\\*""(.*?)\\*"">", RegexOptions.Compiled);
|
||||
private static readonly string[] _propertyTypesWithRte = new[] { Constants.PropertyEditors.Aliases.TinyMce, Constants.PropertyEditors.Aliases.BlockList, Constants.PropertyEditors.Aliases.BlockGrid };
|
||||
|
||||
public static IEnumerable<IContent>? GetByIds(this IContentService contentService, IEnumerable<Udi> ids)
|
||||
{
|
||||
@@ -67,21 +68,22 @@ public static class ContentServiceExtensions
|
||||
public static IEnumerable<string> GetAnchorValuesFromRTEs(this IContentService contentService, int id, string? culture = "*")
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
culture = culture is not "*" ? culture : null;
|
||||
|
||||
IContent? content = contentService.GetById(id);
|
||||
|
||||
if (content is not null)
|
||||
if (content is null)
|
||||
{
|
||||
foreach (IProperty contentProperty in content.Properties)
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (IProperty contentProperty in content.Properties.Where(s => _propertyTypesWithRte.Contains(s.PropertyType.PropertyEditorAlias)))
|
||||
{
|
||||
var value = contentProperty.GetValue(culture)?.ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases
|
||||
.TinyMce))
|
||||
{
|
||||
var value = contentProperty.GetValue(culture)?.ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));
|
||||
}
|
||||
}
|
||||
result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ public static class ContentServiceExtensions
|
||||
MatchCollection matches = AnchorRegex.Matches(rteContent);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);
|
||||
result.Add(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,13 +8,23 @@ namespace Umbraco.Cms.Core.Xml;
|
||||
/// </summary>
|
||||
public class UmbracoXPathPathSyntaxParser
|
||||
{
|
||||
[Obsolete("This will be removed in Umbraco 13. Use ParseXPathQuery which accepts a parentId instead")]
|
||||
public static string ParseXPathQuery(
|
||||
string xpathExpression,
|
||||
int? nodeContextId,
|
||||
Func<int, IEnumerable<string>?> getPath,
|
||||
Func<int, bool> publishedContentExists) => ParseXPathQuery(xpathExpression, nodeContextId, null, getPath, publishedContentExists);
|
||||
|
||||
/// <summary>
|
||||
/// Parses custom umbraco xpath expression
|
||||
/// </summary>
|
||||
/// <param name="xpathExpression">The Xpath expression</param>
|
||||
/// <param name="nodeContextId">
|
||||
/// The current node id context of executing the query - null if there is no current node, in which case
|
||||
/// some of the parameters like $current, $parent, $site will be disabled
|
||||
/// The current node id context of executing the query - null if there is no current node.
|
||||
/// </param>
|
||||
/// <param name="parentId">
|
||||
/// The parent node id of the current node id context of executing the query. With this we can determine the
|
||||
/// $parent and $site parameters even if the current node is not yet published.
|
||||
/// </param>
|
||||
/// <param name="getPath">The callback to create the nodeId path, given a node Id</param>
|
||||
/// <param name="publishedContentExists">The callback to return whether a published node exists based on Id</param>
|
||||
@@ -22,6 +32,7 @@ public class UmbracoXPathPathSyntaxParser
|
||||
public static string ParseXPathQuery(
|
||||
string xpathExpression,
|
||||
int? nodeContextId,
|
||||
int? parentId,
|
||||
Func<int, IEnumerable<string>?> getPath,
|
||||
Func<int, bool> publishedContentExists)
|
||||
{
|
||||
@@ -84,19 +95,27 @@ public class UmbracoXPathPathSyntaxParser
|
||||
// parseable items:
|
||||
var vars = new Dictionary<string, Func<string, string>>();
|
||||
|
||||
// These parameters must have a node id context
|
||||
if (nodeContextId.HasValue)
|
||||
if (parentId.HasValue)
|
||||
{
|
||||
vars.Add("$parent", q =>
|
||||
{
|
||||
var path = getPath(parentId.Value)?.ToArray();
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(path);
|
||||
return q.Replace("$parent", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
|
||||
vars.Add("$site", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(parentId.Value));
|
||||
return q.Replace(
|
||||
"$site",
|
||||
string.Format(rootXpath, closestPublishedAncestorId) + "/ancestor-or-self::*[@level = 1]");
|
||||
});
|
||||
}
|
||||
else if (nodeContextId.HasValue)
|
||||
{
|
||||
vars.Add("$current", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value));
|
||||
return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
|
||||
vars.Add("$parent", q =>
|
||||
{
|
||||
// remove the first item in the array if its the current node
|
||||
// this happens when current is published, but we are looking for its parent specifically
|
||||
var path = getPath(nodeContextId.Value)?.ToArray();
|
||||
if (path?[0] == nodeContextId.ToString())
|
||||
{
|
||||
@@ -116,6 +135,16 @@ public class UmbracoXPathPathSyntaxParser
|
||||
});
|
||||
}
|
||||
|
||||
// These parameters must have a node id context
|
||||
if (nodeContextId.HasValue)
|
||||
{
|
||||
vars.Add("$current", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value));
|
||||
return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: This used to just replace $root with string.Empty BUT, that would never work
|
||||
// the root is always "/root . Need to confirm with Per why this was string.Empty before!
|
||||
vars.Add("$root", q => q.Replace("$root", "/root"));
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -37,7 +37,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
factory.GetRequiredService<IUserService>(),
|
||||
factory.GetRequiredService<IShortStringHelper>(),
|
||||
factory.GetRequiredService<IScopeProvider>(),
|
||||
true));
|
||||
true,
|
||||
factory.GetRequiredService<ILocalizationService>()));
|
||||
builder.Services.AddUnique<IContentValueSetBuilder>(factory =>
|
||||
new ContentValueSetBuilder(
|
||||
factory.GetRequiredService<PropertyEditorCollection>(),
|
||||
@@ -45,7 +46,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
factory.GetRequiredService<IUserService>(),
|
||||
factory.GetRequiredService<IShortStringHelper>(),
|
||||
factory.GetRequiredService<IScopeProvider>(),
|
||||
false));
|
||||
false,
|
||||
factory.GetRequiredService<ILocalizationService>()));
|
||||
builder.Services.AddUnique<IValueSetBuilder<IMedia>, MediaValueSetBuilder>();
|
||||
builder.Services.AddUnique<IValueSetBuilder<IMember>, MemberValueSetBuilder>();
|
||||
builder.Services.AddSingleton<ExamineIndexRebuilder>();
|
||||
|
||||
@@ -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;
|
||||
@@ -22,7 +25,28 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
|
||||
/// <inheritdoc />
|
||||
public abstract IEnumerable<ValueSet> GetValueSets(params TContent[] content);
|
||||
|
||||
[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>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>().GetAll().ToDictionary(x=>x.Key));
|
||||
|
||||
[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)
|
||||
@@ -31,7 +55,7 @@ public abstract class BaseValueSetBuilder<TContent> : IValueSetBuilder<TContent>
|
||||
}
|
||||
|
||||
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> indexVals =
|
||||
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly);
|
||||
editor.PropertyIndexValueFactory.GetIndexValues(property, culture, segment, PublishedValuesOnly, availableCultures, contentTypeDictionary);
|
||||
foreach (KeyValuePair<string, IEnumerable<object?>> keyVal in indexVals)
|
||||
{
|
||||
if (keyVal.Key.IsNullOrWhiteSpace())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user