Compare commits

..
Author SHA1 Message Date
Nikolaj 6afa6d2fe3 Bump version 2023-04-27 09:44:09 +02:00
Mole 531ab3cbc5 Set culture variation when getting property types for indexing (#14167) 2023-04-27 09:43:31 +02:00
404 changed files with 4525 additions and 19988 deletions
+7 -2
View File
@@ -11,10 +11,15 @@ 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({
@@ -41,13 +46,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).")
}
-1
View File
@@ -49,5 +49,4 @@
<PropertyGroup>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>
</Project>
+56 -104
View File
@@ -21,33 +21,9 @@ 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: 20
nodeVersion: 14.18.1
dotnetVersion: 6.x
dotnetIncludePreviewVersions: false
solution: umbraco.sln
@@ -73,7 +49,6 @@ stages:
steps:
- task: NodeTool@0
displayName: Use Node.js $(nodeVersion)
retryCountOnTaskFailure: 3
inputs:
versionSpec: $(nodeVersion)
- task: Cache@2
@@ -86,9 +61,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 (Backoffice)
displayName: Run npm ci
- task: gulp@0
displayName: Run gulp build (Backoffice)
displayName: Run gulp build
inputs:
gulpFile: src/Umbraco.Web.UI.Client/gulpfile.js
targets: coreBuild
@@ -105,12 +80,44 @@ stages:
command: restore
projects: $(solution)
- task: DotNetCoreCLI@2
name: build
displayName: Run dotnet build and generate NuGet packages
displayName: Run dotnet build
inputs:
command: build
projects: $(solution)
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
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
- task: PublishPipelineArtifact@1
displayName: Publish nupkg
inputs:
@@ -123,11 +130,11 @@ stages:
artifactName: build_output
- stage: Build_Docs
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.buildApiDocs}}))
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.buildApiDocs}}))
displayName: Prepare API Documentation
dependsOn: Build
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
jobs:
# C# API Reference
- job:
@@ -181,10 +188,9 @@ stages:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: Use Node.js 10.15.x
retryCountOnTaskFailure: 3
displayName: Use Node.js 10.15.0
inputs:
versionSpec: 10.15.x # Won't work with higher versions
versionSpec: 10.15.0 # Won't work with higher versions
- script: |
npm ci --no-fund --no-audit --prefer-offline
npx gulp docs
@@ -253,8 +259,6 @@ stages:
- stage: Integration
displayName: Integration Tests
dependsOn: Build
variables:
releaseTestFilter: eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True')
jobs:
# Integration Tests (SQLite)
- job:
@@ -282,30 +286,12 @@ stages:
performMultiLevelLookup: true
includePreviewVersions: $(dotnetIncludePreviewVersions)
- task: DotNetCoreCLI@2
displayName: Run dotnet test Windows
condition: eq(variables['Agent.OS'],'Windows_NT')
displayName: Run dotnet test
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'
@@ -313,7 +299,7 @@ stages:
# Integration Tests (SQL Server)
- job:
timeoutInMinutes: 120
condition: or(eq(stageDependencies.Build.A.outputs['build.NBGV_PublicRelease'], 'True'), ${{parameters.sqlServerIntegrationTests}})
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.sqlServerIntegrationTests}})
displayName: Integration Tests (SQL Server)
strategy:
matrix:
@@ -324,7 +310,7 @@ stages:
Linux:
vmImage: 'ubuntu-latest'
testDb: SqlServer
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=true'
connectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
pool:
vmImage: $(vmImage)
variables:
@@ -335,11 +321,6 @@ 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'))
@@ -347,31 +328,12 @@ stages:
displayName: Start SQL Server (Linux only)
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- task: DotNetCoreCLI@2
displayName: Run dotnet test Windows
condition: eq(variables['Agent.OS'],'Windows_NT')
displayName: Run dotnet test
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)
@@ -399,7 +361,6 @@ 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
@@ -407,7 +368,7 @@ stages:
Umbraco__CMS__Unattended__InstallUnattended: true
Umbraco__CMS__Global__InstallMissingDatabase: true
UmbracoDatabaseServer: (LocalDB)\MSSQLLocalDB
UmbracoDatabaseName: AcceptanceTestDB
UmbracoDatabaseName: Playwright
ConnectionStrings__umbracoDbDSN: Server=$(UmbracoDatabaseServer);Database=$(UmbracoDatabaseName);Integrated Security=true;
# Custom Umbraco settings
Umbraco__CMS__Global__VersionCheckPeriod: 0
@@ -424,7 +385,6 @@ 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
@@ -469,7 +429,7 @@ stages:
workingDirectory: tests/Umbraco.Tests.AcceptanceTest/misc
- pwsh: |
dotnet new --install ./nupkg/Umbraco.Templates.*.nupkg
dotnet new umbraco --name AcceptanceTestProject --no-restore --output .
dotnet new umbraco --name Playwright --no-restore --output .
dotnet restore --configfile ./nuget.config
dotnet build --configuration $(buildConfiguration) --no-restore
dotnet dev-certs https
@@ -490,8 +450,6 @@ 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)
@@ -530,11 +488,9 @@ stages:
- Unit
- Integration
# - E2E # TODO: Enable when stable.
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{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
@@ -549,20 +505,16 @@ stages:
command: 'push'
packagesToPush: $(Build.ArtifactStagingDirectory)/**/*.nupkg
nuGetFeedType: 'external'
${{ if eq(parameters.isNightly, true) }}:
publishFeedCredentials: 'MyGet - Umbraco Nightly'
${{ else }}:
publishFeedCredentials: 'MyGet - Pre-releases'
publishFeedCredentials: 'MyGet - Pre-releases'
- stage: Deploy_NuGet
displayName: NuGet release
dependsOn:
- Deploy_MyGet
- Build_Docs
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{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
@@ -583,12 +535,12 @@ stages:
pool:
vmImage: 'windows-latest' # Apparently AzureFileCopy is windows only :(
variables:
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['build.NBGV_VersionMajor'] ]
umbracoMajorVersion: $[ stageDependencies.Build.A.outputs['determineMajorVersion.majorVersion'] ]
displayName: Upload API Documention
dependsOn:
- Build
- Deploy_NuGet
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.uploadApiDocs}}))
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}}))
jobs:
- job:
displayName: Upload C# Docs
-35
View File
@@ -1,35 +0,0 @@
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
-6
View File
@@ -1,6 +0,0 @@
{
"sdk": {
"version": "6.0.300",
"rollForward": "latestFeature"
}
}
-67
View File
@@ -1,67 +0,0 @@
<?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>
+1 -16
View File
@@ -51,7 +51,6 @@ namespace JsonSchema
public ImagingSettings? Imaging { get; set; }
public IndexCreatorSettings? Examine { get; set; }
public IndexingSettings? Indexing { get; set; }
public KeepAliveSettings? KeepAlive { get; set; }
@@ -89,27 +88,13 @@ namespace JsonSchema
public HelpPageSettings? HelpPage { get; set; }
public InstallDefaultData? InstallDefaultData { get; set; }
public InstallDefaultDataSettings? DefaultDataCreation { 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 -4
View File
@@ -3,17 +3,16 @@
<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.9.0" />
<PackageReference Include="NJsonSchema" Version="10.7.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<PackageReference Include="Umbraco.Deploy.Core" Version="10.4.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="10.5.4" />
<PackageReference Include="Umbraco.Deploy.Core" Version="10.1.3" />
<PackageReference Include="Umbraco.Forms.Core" Version="10.3.0" />
</ItemGroup>
</Project>
@@ -4,7 +4,6 @@
<Description>Contains the presentation layer for the Umbraco CMS Management API.</Description>
<IsPackable>false</IsPackable>
<EnablePackageValidation>false</EnablePackageValidation>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
@@ -13,7 +12,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" />
@@ -21,7 +20,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>
@@ -134,10 +134,9 @@ public class SqlServerDistributedLockingMechanism : IDistributedLockingMechanism
const string query = "SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id";
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
// execute the lock timeout query and the actual query in a single server roundtrip
var i = db.ExecuteScalar<int?>($"{lockTimeoutQuery};{query}", new { id = LockId });
var i = db.ExecuteScalar<int?>(query, new { id = LockId });
if (i == null)
{
@@ -170,10 +169,9 @@ 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";
var lockTimeoutQuery = $"SET LOCK_TIMEOUT {_timeout.TotalMilliseconds}";
db.Execute("SET LOCK_TIMEOUT " + _timeout.TotalMilliseconds + ";");
// execute the lock timeout query and the actual query in a single server roundtrip
var i = db.Execute($"{lockTimeoutQuery};{query}", new { id = LockId });
var i = db.Execute(query, new { id = LockId });
if (i == 0)
{
@@ -1,11 +0,0 @@
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Persistence.SqlServer.Services;
public class SqlServerSpecificMapperFactory : IProviderSpecificMapperFactory
{
public string ProviderName => Constants.ProviderName;
public NPocoMapperCollection Mappers => new(() => new[] { new UmbracoDefaultMapper() });
}
@@ -22,8 +22,6 @@ public static class UmbracoBuilderExtensions
/// </summary>
public static IUmbracoBuilder AddUmbracoSqlServerSupport(this IUmbracoBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor
.Singleton<IProviderSpecificMapperFactory, SqlServerSpecificMapperFactory>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ISqlSyntaxProvider, SqlServerSyntaxProvider>());
builder.Services.TryAddEnumerable(ServiceDescriptor
.Singleton<IBulkSqlInsertProvider, SqlServerBulkSqlInsertProvider>());
@@ -1,4 +1,3 @@
using System.Globalization;
using NPoco;
namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
@@ -29,24 +28,6 @@ public class SqlitePocoGuidMapper : DefaultMapper
};
}
if (destType == typeof(decimal))
{
return value =>
{
var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
return result;
};
}
if (destType == typeof(decimal?))
{
return value =>
{
var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
return result;
};
}
return base.GetFromDbConverter(destType, sourceType);
}
}
@@ -154,7 +154,7 @@ public class SqliteDistributedLockingMechanism : IDistributedLockingMechanism
try
{
var i = db.ExecuteNonQuery(command);
var i = command.ExecuteNonQuery();
if (i == 0)
{
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.5" />
</ItemGroup>
<ItemGroup>
-1
View File
@@ -4,7 +4,6 @@
<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 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))
// 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))
{
idsRemoved.Add(payload.Id);
}
@@ -120,11 +120,7 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
// should rename it, and then, this is only for Deploy, and then, ???
// if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
// ...
if (payloads.Any(x => x.Blueprint is false))
{
// Only notify if the payload contains actual (non-blueprint) contents
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
}
NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads);
base.Refresh(payloads);
}
@@ -161,13 +157,8 @@ 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;
@@ -175,13 +166,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
ChangeTypes = changeTypes;
}
public int Id { get; init; }
public int Id { get; }
public Guid? Key { get; init; }
public Guid? Key { get; }
public TreeChangeTypes ChangeTypes { get; init; }
public bool Blueprint { get; init; }
public TreeChangeTypes ChangeTypes { get; }
}
#endregion
@@ -88,6 +88,10 @@ 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,14 +134,8 @@ 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()
{
ChangeTypes = TreeChangeTypes.RefreshAll
}
};
ContentCacheRefresher.JsonPayload[] clearContentPayload =
new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload);
}
+8 -31
View File
@@ -9,9 +9,6 @@ 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;
@@ -36,10 +33,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
Lazy<object?>? result;
try
{
if (_locker.TryEnterReadLock(_readLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when getting item");
}
_locker.EnterReadLock();
result = MemoryCache.Get(key) as Lazy<object?>; // null if key not found
}
finally
@@ -201,10 +195,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing item");
}
_locker.EnterWriteLock();
if (MemoryCache[key] == null)
{
return;
@@ -232,10 +223,8 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
var isInterface = type.IsInterface;
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by type");
}
_locker.EnterWriteLock();
// ToArray required to remove
foreach (var key in MemoryCache
.Where(x =>
@@ -270,10 +259,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing by generic type");
}
_locker.EnterWriteLock();
Type typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -310,10 +296,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing generic type with predicate");
}
_locker.EnterWriteLock();
Type typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -355,10 +338,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
{
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cache when clearing with prefix");
}
_locker.EnterWriteLock();
// ToArray required to remove
foreach (var key in MemoryCache
@@ -385,10 +365,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable
try
{
if (_locker.TryEnterWriteLock(_writeLockTimeout) is false)
{
throw new TimeoutException("Timeout exceeded to the memory cach when clearing by regex");
}
_locker.EnterWriteLock();
// ToArray required to remove
foreach (var key in MemoryCache
@@ -3,26 +3,30 @@ 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;
/// <summary>
/// Initializes a new instance of the <see cref="BuilderCollectionBase{TItem}" /> with items.
/// 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;
/// <inheritdoc />
/// <summary>
/// Gets an enumerator.
/// </summary>
public IEnumerator<TItem> GetEnumerator() => _items.GetEnumerator();
/// <inheritdoc />
/// <summary>
/// Gets an enumerator.
/// </summary>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
@@ -1,16 +1,13 @@
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; }
}
+1 -10
View File
@@ -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 string[] KnownAssemblyExclusionFilter =
internal static readonly string[] KnownAssemblyExclusionFilter =
{
"mscorlib,", "netstandard,", "System,", "Antlr3.", "AutoMapper,", "AutoMapper.", "Autofac,", // DI
"Autofac.", "AzureDirectory,", "Castle.", // DI, tests
@@ -49,20 +49,11 @@ 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 />
@@ -1,18 +0,0 @@
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,7 +14,6 @@ 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.
@@ -41,7 +40,4 @@ 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,7 +2,6 @@
// See LICENSE for more details.
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Umbraco.Cms.Core.Configuration.Models;
@@ -25,8 +24,6 @@ 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.
@@ -112,26 +109,4 @@ 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,9 +22,4 @@ 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,7 +38,6 @@ 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";
-1
View File
@@ -28,6 +28,5 @@ 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,7 +50,6 @@ public static partial class UmbracoBuilderExtensions
builder
.AddUmbracoOptions<ModelsBuilderSettings>()
.AddUmbracoOptions<ActiveDirectorySettings>()
.AddUmbracoOptions<IndexCreatorSettings>()
.AddUmbracoOptions<MarketplaceSettings>()
.AddUmbracoOptions<ContentSettings>()
.AddUmbracoOptions<CoreDebugSettings>()
@@ -65,7 +64,7 @@ public static partial class UmbracoBuilderExtensions
.AddUmbracoOptions<HealthChecksSettings>()
.AddUmbracoOptions<HostingSettings>()
.AddUmbracoOptions<ImagingSettings>()
.AddUmbracoOptions<IndexingSettings>()
.AddUmbracoOptions<IndexCreatorSettings>()
.AddUmbracoOptions<KeepAliveSettings>()
.AddUmbracoOptions<LoggingSettings>()
.AddUmbracoOptions<MemberPasswordConfigurationSettings>()
@@ -318,9 +318,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<ConflictingPackageData>();
Services.AddSingleton<CompiledPackageXmlParser>();
// Register a noop IHtmlSanitizer & IMarkdownSanitizer to be replaced
// Register a noop IHtmlSanitizer to be replaced
Services.AddUnique<IHtmlSanitizer, NoopHtmlSanitizer>();
Services.AddUnique<IMarkdownSanitizer, NoopMarkdownSanitizer>();
Services.AddUnique<IPropertyTypeUsageService, PropertyTypeUsageService>();
Services.AddUnique<IDataTypeUsageService, DataTypeUsageService>();
@@ -328,9 +327,6 @@ 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>();
}
}
}
+10 -5
View File
@@ -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,10 +2116,15 @@ 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.</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="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>
@@ -2233,8 +2238,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">Najstarije</key>
<key alias="sortCreateDateDescending">Najnovije</key>
<key alias="sortCreateDateAscending">Najnovije</key>
<key alias="sortCreateDateDescending">Najstarije</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>
@@ -2291,7 +2296,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,8 +1754,9 @@
<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.</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="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>
@@ -1868,8 +1869,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">Nejstarší</key>
<key alias="sortCreateDateDescending">Nejnovější</key>
<key alias="sortCreateDateAscending">Nejnovější</key>
<key alias="sortCreateDateDescending">Nejstarší</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,8 +2308,9 @@ 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.</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="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>
+10 -8
View File
@@ -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">Standardværdi</key>
<key alias="resendInvite">Gensend Invitation</key>
<key alias="defaultValue">Standard værdi</key>
</area>
<area alias="actionCategories">
<key alias="content">Indhold</key>
@@ -717,7 +717,6 @@
<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>
@@ -1895,11 +1894,14 @@ 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.
gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar.
</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>
@@ -1925,8 +1927,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">Ældste</key>
<key alias="sortCreateDateDescending">Nyeste</key>
<key alias="sortCreateDateAscending">Nyeste</key>
<key alias="sortCreateDateDescending">Ældste</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>
@@ -2230,7 +2232,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å rodniveau</key>
<key alias="allowBlockInRoot">Tillad på rodniveay</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>
@@ -2238,7 +2240,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">Standardlayout stylesheet</key>
<key alias="defaultLayoutStylesheet">Standart layout 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>
+31 -30
View File
@@ -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 &lt;strong&gt;weiter&lt;/strong&gt;, um fortzufahren.</key>
<key alias="databaseText">Um diesen Schritt abzuschließen, müssen Sie die notwendigen Informationen zur Datenbankverbindung angeben.&lt;br /&gt;Bitte kontaktieren Sie Ihren Provider bzw. Server-Administrator für weitere Informationen.</key>
<key alias="databaseUpgrade">
@@ -2059,8 +2059,9 @@
<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.</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="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>
<!--???-->
@@ -2178,8 +2179,8 @@
<key alias="stateInactive">Nicht aktiv</key>
<key alias="sortNameAscending">Name (A-Z)</key>
<key alias="sortNameDescending">Name (Z-A)</key>
<key alias="sortCreateDateAscending">Oldest</key>
<key alias="sortCreateDateDescending">Newest</key>
<key alias="sortCreateDateAscending">Newest</key>
<key alias="sortCreateDateDescending">Oldest</key>
<key alias="sortLastLoginDateDescending">Last login</key>
</area>
<area alias="validation">
@@ -2338,7 +2339,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>
@@ -2380,7 +2381,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,7 +341,6 @@
<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>
@@ -489,7 +488,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 and all content related to the language</key>
<key alias="languagedeletewarning">This will delete 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>
@@ -2125,11 +2124,14 @@ 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 youll be good to go, we
just need you to setup a password.
just need you to setup a password and add a picture for your avatar.
</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>
@@ -2243,8 +2245,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">Oldest</key>
<key alias="sortCreateDateDescending">Newest</key>
<key alias="sortCreateDateAscending">Newest</key>
<key alias="sortCreateDateDescending">Oldest</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,7 +352,6 @@
<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>
@@ -504,7 +503,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 and all content related to the language</key>
<key alias="languagedeletewarning">This will delete 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>
@@ -758,7 +757,6 @@
<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>
@@ -2220,11 +2218,14 @@ 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 youll be good to go, we
just need you to setup a password.
just need you to setup a password and add a picture for your avatar.
</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>
@@ -2339,8 +2340,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">Oldest</key>
<key alias="sortCreateDateDescending">Newest</key>
<key alias="sortCreateDateAscending">Newest</key>
<key alias="sortCreateDateDescending">Oldest</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>
@@ -3004,7 +3005,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, Backoffice external login providers, and Property Editors in use.</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>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,7 +1394,8 @@
<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.</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="writer">Redactor</key>
<key alias="change">Cambiar</key>
<key alias="yourProfile" version="7.0">Tu perfil</key>
@@ -1795,8 +1795,9 @@ 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.</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="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>
@@ -1909,8 +1910,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 ancien</key>
<key alias="sortCreateDateDescending">Plus récent</key>
<key alias="sortCreateDateAscending">Plus récent</key>
<key alias="sortCreateDateDescending">Plus ancien</key>
<key alias="sortLastLoginDateDescending">Dernière connexion</key>
</area>
<area alias="validation">
File diff suppressed because it is too large Load Diff
@@ -2197,10 +2197,13 @@ 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.
solamente impostare una password e aggiungere una foto profilo.
</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>
@@ -2316,8 +2319,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ù vecchi]]></key>
<key alias="sortCreateDateDescending"><![CDATA[Più nuovi]]></key>
<key alias="sortCreateDateAscending"><![CDATA[Più nuovi]]></key>
<key alias="sortCreateDateDescending"><![CDATA[Più vecchi]]></key>
<key alias="sortLastLoginDateDescending">Ultimo login</key>
<key alias="noUserGroupsAdded">Non sono stati aggiunti gruppi di utenti</key>
</area>
@@ -2657,7 +2660,6 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in
<key alias="labelUsedByMemberTypes">Usato nei tipi di membro</key>
<key alias="noMemberTypes">Non ci sono riferimenti a tipi di membro.</key>
<key alias="usedByProperties">Usato da</key>
<key alias="labelUsedByItems">Correlato ai seguenti elementi</key>
<key alias="labelUsedByDocuments">Usato nei documenti</key>
<key alias="labelUsedByMembers">Usato nei membri</key>
<key alias="labelUsedByMedia">Usato nei media</key>
@@ -340,7 +340,6 @@
<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>
@@ -1938,11 +1937,12 @@ 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.
moet enkel je wachtwoord instellen en een foto toevoegen.
</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">Oudste</key>
<key alias="sortCreateDateDescending">Nieuwste</key>
<key alias="sortCreateDateAscending">Nieuwste</key>
<key alias="sortCreateDateDescending">Oudste</key>
<key alias="sortLastLoginDateDescending">Laatste login</key>
<key alias="noUserGroupsAdded">Er zijn geen gebruikersgroepen toegevoegd</key>
</area>
@@ -822,8 +822,6 @@ 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,7 +1703,8 @@
<key alias="usergroup">Группа пользователей</key>
<key alias="userInvited"> был приглашен</key>
<key alias="userInvitedSuccessHelp">Новому пользователю было отправлено приглашение, в котором содержатся инструкции для входа в панель Umbraco.</key>
<key alias="userinviteWelcomeMessage">Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа.</key>
<key alias="userinviteWelcomeMessage">Здравствуйте и добро пожаловать в Umbraco! Все будет готово в течении пары минут, нам лишь нужно задать Ваш пароль для входа и добавить аватар.</key>
<key alias="userinviteAvatarMessage">Загрузите изображение, это поможет другим пользователям идентифицировать Вас.</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">Spara och förhandsgranska</key>
<key alias="saveAndPreview">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">Äldst</key>
<key alias="sortCreateDateDescending">Nyast</key>
<key alias="sortCreateDateAscending">Nyast</key>
<key alias="sortCreateDateDescending">Äldst</key>
<key alias="sortLastLoginDateDescending">Senaste login</key>
</area>
<area alias="logViewer">
@@ -1879,8 +1879,9 @@ 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.</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="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>
@@ -1995,8 +1996,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 eski</key>
<key alias="sortCreateDateDescending">En yeni</key>
<key alias="sortCreateDateAscending">En yeni</key>
<key alias="sortCreateDateDescending">En eski</key>
<key alias="sortLastLoginDateDescending">Son giriş</key>
<key alias="noUserGroupsAdded">Hiçbir kullanıcı grubu eklenmedi</key>
</area>
@@ -1703,7 +1703,8 @@
<key alias="usergroup">Група користувачів</key>
<key alias="userInvited"> був запрошений</key>
<key alias="userInvitedSuccessHelp">Новому користувачеві було надіслано запрошення, яке містить інструкції для входу в панель Umbraco.</key>
<key alias="userinviteWelcomeMessage">Привіт і ласкаво просимо до Umbraco! Все буде готове протягом декількох хвилин, нам потрібно задати пароль для входу.</key>
<key alias="userinviteWelcomeMessage">Привіт і ласкаво просимо до Umbraco! Все буде готове протягом декількох хвилин, нам потрібно задати пароль для входу і додати аватар.</key>
<key alias="userinviteAvatarMessage">Завантажте зображення, що допоможе іншим користувачам ідентифікувати Вас.</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 async Task HandleAsync(
public override Task HandleAsync(
INotification notification,
CancellationToken cancellationToken,
ServiceFactory serviceFactory,
@@ -155,7 +155,7 @@ internal class NotificationAsyncHandlerWrapperImpl<TNotification> : Notification
(theNotification, theToken) =>
x.HandleAsync((TNotification)theNotification, theToken)));
await publish(handlers, notification, cancellationToken);
return publish(handlers, notification, cancellationToken);
}
}
+2 -2
View File
@@ -50,7 +50,7 @@ public partial class EventAggregator : IEventAggregator
=> _serviceFactory = serviceFactory;
/// <inheritdoc />
public async Task PublishAsync<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
public 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);
await PublishNotificationAsync(notification, cancellationToken);
return PublishNotificationAsync(notification, cancellationToken);
}
/// <inheritdoc />
@@ -134,6 +134,27 @@ 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>
@@ -174,6 +195,8 @@ 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))
{
@@ -1040,15 +1040,14 @@ public static class StringExtensions
throw new ArgumentNullException(nameof(text));
}
ReadOnlySpan<char> spanText = text.AsSpan();
var pos = spanText.IndexOf(search, StringComparison.InvariantCulture);
var pos = text.IndexOf(search, StringComparison.InvariantCulture);
if (pos < 0)
{
return text;
}
return string.Concat(spanText[..pos], replace.AsSpan(), spanText[(pos + search.Length)..]);
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
}
/// <summary>
+80 -79
View File
@@ -219,52 +219,96 @@ public static class TypeExtensions
/// <returns></returns>
public static PropertyInfo[] GetAllProperties(this Type type)
{
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance;
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
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);
}
/// <summary>
/// Returns public properties including inherited properties even for interfaces
/// Returns all 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)
{
const BindingFlags bindingFlags = BindingFlags.FlattenHierarchy
| BindingFlags.Public
| BindingFlags.Instance;
return type.GetAllMemberInfos(t => t.GetProperties(bindingFlags));
}
if (type.IsInterface)
{
var propertyInfos = new List<PropertyInfo>();
/// <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));
}
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 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));
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>
@@ -468,47 +512,4 @@ 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();
}
}
+1 -1
View File
@@ -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($"Requested path {originalPath} is outside this filesystem's root.");
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
}
/// <summary>
+19 -19
View File
@@ -7,21 +7,19 @@ namespace Umbraco.Cms.Core.IO;
internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
private const string ShadowFsPath = "ShadowFs";
private readonly IIOHelper _ioHelper;
private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IIOHelper _ioHelper;
private readonly Func<bool?>? _isScoped;
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;
@@ -37,19 +35,18 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
get
{
Func<bool?>? isScoped = _isScoped;
if (isScoped is not null && _shadowFileSystem is not null)
if (_isScoped is not null && _shadowFileSystem is not null)
{
bool? scoped = isScoped();
var isScoped = _isScoped!();
// if the filesystem is created *after* shadowing starts, it won't be shadowing
// better not ignore that situation and raise a meaningful (?) exception
if (scoped.HasValue && scoped.Value && _shadowFileSystem == null)
// better not ignore that situation and raised a meaningful (?) exception
if (isScoped.HasValue && isScoped.Value && _shadowFileSystem == null)
{
throw new Exception("The filesystems are shadowing, but this filesystem is not.");
}
return scoped.HasValue && scoped.Value
return isScoped.HasValue && isScoped.Value
? _shadowFileSystem
: InnerFileSystem;
}
@@ -59,7 +56,8 @@ 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);
@@ -71,7 +69,8 @@ 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);
@@ -108,7 +107,8 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory
{
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
var shadowDir = Path.Combine(hostingEnvironment.LocalTempPath, ShadowFsPath, id);
var virt = ShadowFsPath + "/" + id;
var shadowDir = hostingEnvironment.MapPathContentRoot(virt);
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 rootUrl = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = Path.Combine(_hostingEnvironment.LocalTempPath, rootUrl);
var virt = Path.Combine(ShadowFsPath, id, _shadowPath);
_shadowDir = _hostingEnvironment.MapPathContentRoot(virt);
Directory.CreateDirectory(_shadowDir);
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, rootUrl);
var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger<PhysicalFileSystem>(), _shadowDir, _hostingEnvironment.ToAbsolute(virt));
_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 = Path.Combine(_hostingEnvironment.LocalTempPath, ShadowFsPath).Length;
var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length;
var pos = dir.LastIndexOf(Path.DirectorySeparatorChar);
while (pos > min)
{
@@ -19,9 +19,6 @@ public class Issuu : EmbedProviderBase
public override Dictionary<string, string> RequestParams => new()
{
// ApiUrl/?iframe=true
{ "iframe", "true" },
// ApiUrl/?format=xml
{ "format", "xml" },
};
@@ -55,12 +55,11 @@ 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).GetAwaiter().GetResult();
HttpResponseMessage response = _httpClient.SendAsync(request).Result;
return response.Content.ReadAsStringAsync().Result;
}
}
@@ -1,88 +1,49 @@
namespace Umbraco.Cms.Core.Models.Editors;
/// <summary>
/// Used to track a reference to another entity in a property value.
/// Used to track reference to other entities 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:
// No relation type alias convention for this entity type, so leave it empty
RelationTypeAlias = string.Empty;
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
break;
}
}
/// <summary>
/// Gets the UDI.
/// </summary>
/// <value>
/// The UDI.
/// </value>
public Udi Udi { get; }
/// <summary>
/// Gets the relation type alias.
/// </summary>
/// <value>
/// The relation type alias.
/// </value>
public string RelationTypeAlias { get; }
/// <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 string RelationTypeAlias { get; }
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right);
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;
@@ -91,9 +52,5 @@ 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);
}
+1 -2
View File
@@ -2,7 +2,6 @@ using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models;
@@ -133,7 +132,7 @@ public class PropertyGroup : EntityBase, IEquatable<PropertyGroup>
}
public bool Equals(PropertyGroup? other) =>
base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias && Id == other.Id);
base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias);
public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode();
@@ -30,7 +30,7 @@ public class PropertyTypeCollection : KeyedCollection<string, IPropertyType>, IN
// This baseclass calling is needed, else compiler will complain about nullability
/// <inheritdoc />
public bool IsReadOnly => false;
public bool IsReadOnly => ((ICollection<IPropertyType>)this).IsReadOnly;
// 'new' keyword is required! we can explicitly implement ICollection<IPropertyType>.Add BUT since normally a concrete PropertyType type
// is passed in, the explicit implementation doesn't get called, this ensures it does get called.
@@ -5,7 +5,6 @@ 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,15 +7,7 @@ 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,7 +20,6 @@ public class PublishedValueFallback : IPublishedValueFallback
_variationContextAccessor = variationContextAccessor;
}
[Obsolete("Scheduled for removal in v14")]
public IVariationContextAccessor VariationContextAccessor { get { return _variationContextAccessor; } }
/// <inheritdoc />
@@ -1,18 +0,0 @@
// 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)
{
}
}
@@ -1,23 +0,0 @@
// 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,156 +4,64 @@ 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>
{
// 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
/// <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))
// 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)
{
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor))
{
continue;
}
// 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))
// 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)
{
values.Add(propertyValue.EditedValue);
values.Add(propertyValue.PublishedValue);
}
var val = propertyVal.EditedValue;
references.UnionWith(GetReferences(dataEditor, values));
}
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;
}
}
// 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))
IDataValueEditor? valueEditor = editor?.GetValueEditor();
if (valueEditor is IDataValueReference reference)
{
yield return reference;
IEnumerable<UmbracoEntityReference> refs = reference.GetReferences(val);
foreach (UmbracoEntityReference r in refs)
{
trackedRelations.Add(r);
}
}
// 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);
}
}
}
}
}
}
/// <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;
}
}
}
return trackedRelations;
}
}
@@ -9,22 +9,11 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// </summary>
public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory
{
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
{
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,10 +16,11 @@ 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 MultiUrlPickerValueConverter and the JsonValueConverter
/// An example where this is useful is that both the RelatedLiksEditorValueConverter 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 MultiUrlPickerValueConverter is more specific than it, so the
/// MultiUrlPickerValueConverter can specify that it 'shadows' 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.
/// </remarks>
public Type[] DefaultConvertersToShadow { get; }
}
@@ -22,14 +22,5 @@ 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,10 +1,5 @@
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;
@@ -16,37 +11,21 @@ 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, IOptionsMonitor<IndexingSettings> indexingSettings)
protected JsonPropertyIndexValueFactoryBase(IJsonSerializer jsonSerializer)
{
_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,
IEnumerable<string> availableCultures,
IDictionary<Guid, IContentType> contentTypeDictionary)
bool published)
{
var result = new List<KeyValuePair<string, IEnumerable<object?>>>();
@@ -64,7 +43,7 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
return result;
}
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published, availableCultures, contentTypeDictionary));
result.AddRange(Handle(deserializedPropertyValue, property, culture, segment, published));
}
catch (InvalidCastException)
{
@@ -78,44 +57,13 @@ public abstract class JsonPropertyIndexValueFactoryBase<TSerialized> : IProperty
}
}
IEnumerable<KeyValuePair<string, IEnumerable<object?>>> summary = HandleResume(result, property, culture, segment, published);
if (_indexingSettings.ExplicitlyIndexEachNestedProperty || ForceExplicitlyIndexEachNestedProperty)
{
result.AddRange(summary);
return result;
}
result.AddRange(HandleResume(result, property, culture, segment, published));
return summary;
return result;
}
/// <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 summary of the content. By default this returns an empty list
/// Method to return a list of resume 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,
@@ -127,33 +75,10 @@ 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);
}
@@ -1,39 +0,0 @@
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,7 +3,6 @@
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;
@@ -51,11 +50,4 @@ 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,15 +8,5 @@ namespace Umbraco.Cms.Core.PropertyEditors;
public class NoopPropertyIndexValueFactory : IPropertyIndexValueFactory
{
/// <inheritdoc />
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published,
IEnumerable<string> availableCultures, IDictionary<Guid, IContentType> contentTypeDictionary)
=> Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published, IEnumerable<string> availableCultures) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
[Obsolete("Use the overload with the availableCultures parameter instead, scheduled for removal in v14")]
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)
=> GetIndexValues(property, culture, segment, published);
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) => Array.Empty<KeyValuePair<string, IEnumerable<object?>>>();
}
@@ -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", Description = "Must be smaller than the Maximum value")]
[ConfigurationField("minVal", "Minimum value", "number")]
public decimal MinimumValue { get; set; }
[ConfigurationField("maxVal", "Maximum value", "number", Description = "Must be larger than the Minimum value")]
[ConfigurationField("maxVal", "Maximum value", "number")]
public decimal MaximumValue { get; set; }
[ConfigurationField("step", "Step increments", "number", Description = "Must be a positive value")]
[ConfigurationField("step", "Step increments", "number")]
public decimal StepIncrements { get; set; }
}
@@ -25,19 +25,4 @@ 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,41 +1,14 @@
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,
IOptionsMonitor<IndexingSettings> indexingSettings)
: base(jsonSerializer, indexingSettings)
public TagPropertyIndexValueFactory(IJsonSerializer jsonSerializer) : base(jsonSerializer)
{
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.Globalization;
using System.Collections.Concurrent;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Services;
@@ -6,123 +6,74 @@ 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
{
/// <summary>
/// Initializes a new instance of the <see cref="SliderValueConverter" /> class.
/// </summary>
public SliderValueConverter()
{ }
private static readonly ConcurrentDictionary<int, bool> Storages = new();
private readonly IDataTypeService _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 SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService =
dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
/// <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()
{ }
public static void ClearCaches() => Storages.Clear();
/// <inheritdoc />
public override bool IsConverter(IPublishedPropertyType propertyType)
=> propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider);
/// <inheritdoc />
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> IsRange(propertyType) ? typeof(Range<decimal>) : typeof(decimal);
=> IsRangeDataType(propertyType.DataType.Id) ? 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)
{
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)
if (source == null)
{
return new Range<decimal>();
return null;
}
string[] rangeRawValues = sourceString.Split(Constants.CharArrays.Comma);
if (TryParseDecimal(rangeRawValues[0], out var minimum))
if (IsRangeDataType(propertyType.DataType.Id))
{
if (rangeRawValues.Length == 1)
{
// Configuration is probably changed from single to range, return range with same min/max
return new Range<decimal>
{
Minimum = minimum,
Maximum = minimum
};
}
var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma);
Attempt<decimal> minimumAttempt = rangeRawValues[0].TryConvertTo<decimal>();
Attempt<decimal> maximumAttempt = rangeRawValues[1].TryConvertTo<decimal>();
if (rangeRawValues.Length == 2 && TryParseDecimal(rangeRawValues[1], out var maximum))
if (minimumAttempt.Success && maximumAttempt.Success)
{
return new Range<decimal>
{
Minimum = minimum,
Maximum = maximum
};
return new Range<decimal> { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result };
}
}
return new Range<decimal>();
}
private static decimal HandleDecimal(string? sourceString)
{
if (string.IsNullOrEmpty(sourceString))
Attempt<decimal> valueAttempt = source.ToString().TryConvertTo<decimal>();
if (valueAttempt.Success)
{
return default;
return valueAttempt.Result;
}
// 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;
// Something failed in the conversion of the strings to decimals
return null;
}
/// <summary>
/// Helper method for parsing a double consistently
/// Discovers if the slider is set to range mode.
/// </summary>
private static bool TryParseDecimal(string? representation, out decimal value)
=> decimal.TryParse(representation, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
/// <param name="dataTypeId">
/// The data type id.
/// </param>
/// <returns>
/// The <see cref="bool" />.
/// </returns>
private bool IsRangeDataType(int dataTypeId) =>
private static bool IsRange(IPublishedPropertyType propertyType)
=> propertyType.DataType.ConfigurationAs<SliderConfiguration>()?.EnableRange == true;
// 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;
});
}
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Serialization;
@@ -6,66 +7,69 @@ 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)
: this(jsonSerializer)
{ }
{
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
_jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));
}
/// <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()
{ }
public static void ClearCaches() => Storages.Clear();
/// <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)
{
string? sourceString = source?.ToString();
if (string.IsNullOrEmpty(sourceString))
if (source == null)
{
return Array.Empty<string>();
}
return IsJson(propertyType)
? _jsonSerializer.Deserialize<string[]>(sourceString) ?? Array.Empty<string>()
: sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
// 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);
}
private static bool IsJson(IPublishedPropertyType propertyType)
=> propertyType.DataType.ConfigurationAs<TagConfiguration>()?.StorageType == TagsStorageType.Json;
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;
});
}
@@ -1,8 +1,6 @@
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;
@@ -11,24 +9,10 @@ public abstract class PublishedCacheBase : IPublishedCache
{
private readonly IVariationContextAccessor? _variationContextAccessor;
public PublishedCacheBase(IVariationContextAccessor variationContextAccessor) => _variationContextAccessor =
variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
[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;
}
protected PublishedCacheBase(bool previewDefault) => PreviewDefault = previewDefault;
public bool PreviewDefault { get; }
-24
View File
@@ -50,28 +50,4 @@ 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,56 +7,26 @@ 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 not null)
if (!(culture is 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)
{
string? culture = identity.GetCultureString();
if (!string.IsNullOrEmpty(culture))
if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) &&
umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null)
{
return CultureInfo.GetCultureInfo(culture);
return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!);
}
return null;
+38 -25
View File
@@ -167,7 +167,12 @@ public class ContentPermissions
throw new ArgumentNullException(nameof(user));
}
bool hasPathAccess;
if (permissionsToCheck == null)
{
permissionsToCheck = Array.Empty<char>();
}
bool? hasPathAccess = null;
entity = null;
if (nodeId == Constants.System.Root)
@@ -178,18 +183,20 @@ public class ContentPermissions
{
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
}
else
if (hasPathAccess.HasValue)
{
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
if (entity == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
}
entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document);
if (entity == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches);
if (hasPathAccess == false)
{
return ContentAccess.Denied;
@@ -201,8 +208,7 @@ public class ContentPermissions
}
// get the implicit/inherited permissions for the user for this path
// 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)
return CheckPermissionsPath(entity.Path, user, permissionsToCheck)
? ContentAccess.Granted
: ContentAccess.Denied;
}
@@ -229,7 +235,12 @@ public class ContentPermissions
throw new ArgumentNullException(nameof(user));
}
bool hasPathAccess;
if (permissionsToCheck == null)
{
permissionsToCheck = Array.Empty<char>();
}
bool? hasPathAccess = null;
contentItem = null;
if (nodeId == Constants.System.Root)
@@ -240,18 +251,20 @@ public class ContentPermissions
{
hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches);
}
else
if (hasPathAccess.HasValue)
{
contentItem = _contentService.GetById(nodeId);
if (contentItem == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied;
}
contentItem = _contentService.GetById(nodeId);
if (contentItem == null)
{
return ContentAccess.NotFound;
}
hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches);
if (hasPathAccess == false)
{
return ContentAccess.Denied;
@@ -263,8 +276,7 @@ public class ContentPermissions
}
// 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)
return CheckPermissionsPath(contentItem?.Path ?? nodeId.ToString(), user, permissionsToCheck)
return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck)
? ContentAccess.Granted
: ContentAccess.Denied;
}
@@ -276,7 +288,8 @@ public class ContentPermissions
permissionsToCheck = Array.Empty<char>();
}
// get the implicit/inherited permissions for the user for this path
// 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)
EntityPermissionSet permission = _userService.GetPermissionsForPath(user, path);
var allowed = true;
@@ -1,38 +0,0 @@
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;
}
}
@@ -1,20 +0,0 @@
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);
}
@@ -1,11 +0,0 @@
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);
}
@@ -1,14 +0,0 @@
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);
}
@@ -1,8 +0,0 @@
namespace Umbraco.Cms.Core.Security;
/// <inheritdoc />
public class NoopMarkdownSanitizer : IMarkdownSanitizer
{
/// <inheritdoc />
public string Sanitize(string markdown) => markdown;
}
+34 -49
View File
@@ -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())
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
// locking the content tree secures content types too
scope.WriteLock(Constants.Locks.ContentTree);
@@ -395,8 +395,6 @@ public class ContentService : RepositoryService, IContentService
Save(content, userId);
scope.Complete();
return content;
}
}
@@ -418,7 +416,7 @@ public class ContentService : RepositoryService, IContentService
throw new ArgumentNullException(nameof(parent));
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
// locking the content tree secures content types too
scope.WriteLock(Constants.Locks.ContentTree);
@@ -433,7 +431,6 @@ public class ContentService : RepositoryService, IContentService
Save(content, userId);
scope.Complete();
return content;
}
}
@@ -511,11 +508,10 @@ public class ContentService : RepositoryService, IContentService
/// <inheritdoc />
public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
scope.WriteLock(Constants.Locks.ContentTree);
_documentRepository.PersistContentSchedule(content, contentSchedule);
scope.Complete();
}
}
@@ -1150,8 +1146,6 @@ 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))
{
@@ -1162,7 +1156,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.
@@ -1546,7 +1540,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, notificationState);
unpublishResult = StrategyCanUnpublish(scope, content, eventMessages);
if (unpublishResult.Success)
{
unpublishResult = StrategyUnpublish(content, eventMessages);
@@ -1849,7 +1843,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)
@@ -1933,14 +1927,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
@@ -2109,7 +2103,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, out IDictionary<string, object?> notificationState);
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -2144,7 +2138,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, out _);
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs);
if (result != null)
{
results.Add(result);
@@ -2168,7 +2162,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).WithState(notificationState));
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages));
scope.Complete();
}
@@ -2189,10 +2183,8 @@ public class ContentService : RepositoryService, IContentService
ICollection<IContent> publishedDocuments,
EventMessages evtMsgs,
int userId,
IReadOnlyCollection<ILanguage> allLangs,
out IDictionary<string, object?> notificationState)
IReadOnlyCollection<ILanguage> allLangs)
{
notificationState = new Dictionary<string, object?>();
HashSet<string>? culturesToPublish = shouldPublish(document);
// null = do not include
@@ -2224,7 +2216,6 @@ public class ContentService : RepositoryService, IContentService
if (result.Success)
{
publishedDocuments.Add(document);
notificationState = savingNotification.State;
}
return result;
@@ -2441,7 +2432,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;
}
@@ -2592,8 +2583,7 @@ public class ContentService : RepositoryService, IContentService
IContent[] contents = _documentRepository.Get(query).ToArray();
var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages);
var deletingContentNotification = new ContentDeletingNotification(contents, eventMessages);
if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification) || scope.Notifications.PublishCancelable(deletingContentNotification))
if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
{
scope.Complete();
return OperationResult.Cancel(eventMessages);
@@ -2958,13 +2948,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())
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
scope.WriteLock(Constants.Locks.ContentTree);
@@ -2977,8 +2967,6 @@ public class ContentService : RepositoryService, IContentService
scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
}
scope.Complete();
return report;
}
}
@@ -3305,10 +3293,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, IDictionary<string, object?>? notificationState)
private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs)
{
// raise Unpublishing notification
if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs).WithState(notificationState)))
if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs)))
{
_logger.LogInformation(
"Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
@@ -3574,7 +3562,6 @@ 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();
}
@@ -3589,7 +3576,6 @@ 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();
}
}
@@ -3690,7 +3676,6 @@ 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,8 +16,7 @@ public static class ContentServiceExtensions
{
#region RTE Anchor values
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 };
private static readonly Regex AnchorRegex = new("<a id=\"(.*?)\">", RegexOptions.Compiled);
public static IEnumerable<IContent>? GetByIds(this IContentService contentService, IEnumerable<Udi> ids)
{
@@ -68,22 +67,21 @@ 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 null)
if (content is not null)
{
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))
foreach (IProperty contentProperty in content.Properties)
{
result.AddRange(contentService.GetAnchorValuesFromRTEContent(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));
}
}
}
}
@@ -98,7 +96,7 @@ public static class ContentServiceExtensions
MatchCollection matches = AnchorRegex.Matches(rteContent);
foreach (Match match in matches)
{
result.Add(match.Groups[1].Value);
result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);
}
return result;
@@ -322,6 +322,7 @@ 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())
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
{
scope.WriteLock(Constants.Locks.ContentTree);
_documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
@@ -77,7 +77,6 @@ internal class ContentVersionService : IContentVersionService
if (version is null)
{
scope.Complete();
return;
}
@@ -88,7 +87,6 @@ internal class ContentVersionService : IContentVersionService
var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
scope.Complete();
}
}
@@ -122,14 +120,13 @@ 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())
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
{
IReadOnlyCollection<ContentVersionMeta>? allHistoricVersions =
_documentVersionRepository.GetDocumentVersionsEligibleForCleanup();
if (allHistoricVersions is null)
{
scope.Complete();
return Array.Empty<ContentVersionMeta>();
}
@@ -152,8 +149,6 @@ internal class ContentVersionService : IContentVersionService
versionsToDelete.Add(version);
}
scope.Complete();
}
if (!versionsToDelete.Any())
@@ -166,7 +161,7 @@ internal class ContentVersionService : IContentVersionService
foreach (IEnumerable<ContentVersionMeta> group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount))
{
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
{
scope.WriteLock(Constants.Locks.ContentTree);
var groupEnumerated = group.ToList();
@@ -179,16 +174,12 @@ internal class ContentVersionService : IContentVersionService
scope.Notifications.Publish(
new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId));
}
scope.Complete();
}
}
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
using (_scopeProvider.CreateCoreScope(autoComplete: true))
{
Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy");
scope.Complete();
}
return versionsToDelete;
+1 -1
View File
@@ -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 (ICoreScope scope = _scopeProvider.CreateCoreScope())
using (_scopeProvider.CreateCoreScope(autoComplete: true))
{
var policyOverrides = _documentVersionRepository.GetCleanupPolicies()?
.ToDictionary(x => x.ContentTypeId);
@@ -77,8 +77,6 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
yield return version;
}
}
scope.Complete();
}
}
+1 -4
View File
@@ -728,8 +728,6 @@ namespace Umbraco.Cms.Core.Services
media.CreatorId = userId;
}
media.WriterId = userId;
_mediaRepository.Save(media);
scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification));
// TODO: See note about suppressing events in content service
@@ -1197,7 +1195,7 @@ namespace Umbraco.Cms.Core.Services
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
scope.WriteLock(Constants.Locks.MediaTree);
@@ -1210,7 +1208,6 @@ namespace Umbraco.Cms.Core.Services
scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
}
scope.Complete();
return report;
}
}
@@ -59,10 +59,8 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public async Task DeleteUserLoginsAsync(Guid userOrMemberKey)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey);
scope.Complete();
}
/// <inheritdoc />
@@ -157,12 +155,8 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public async Task<bool> DisableAsync(Guid userOrMemberKey, string providerName)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
var result = await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
scope.Complete();
return result;
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName);
}
/// <inheritdoc />
@@ -179,10 +173,9 @@ public class TwoFactorLoginService : ITwoFactorLoginService2
/// <inheritdoc />
public Task SaveAsync(TwoFactorLogin twoFactorLogin)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope();
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
_twoFactorLoginRepository.Save(twoFactorLogin);
scope.Complete();
return Task.CompletedTask;
}
@@ -8,11 +8,6 @@ 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 =>
@@ -28,7 +23,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]);
return userService.GetPermissions(user, ids[^1]).FirstOrDefault();
}
/// <summary>
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
@@ -305,10 +305,10 @@ namespace Umbraco.Cms.Core.Strings
return text;
}
private string RemoveSurrogatePairs(string text)
private static string RemoveSurrogatePairs(string text)
{
var input = text.AsSpan();
Span<char> output = input.Length <= 1024 ? stackalloc char[input.Length] : new char[text.Length];
var input = text.ToCharArray();
var output = new char[input.Length];
var opos = 0;
for (var ipos = 0; ipos < input.Length; ipos++)
@@ -325,7 +325,7 @@ namespace Umbraco.Cms.Core.Strings
}
}
return new string(output);
return new string(output, 0, opos);
}
// here was a subtle, ascii-optimized version of the cleaning code, and I was
@@ -347,8 +347,7 @@ namespace Umbraco.Cms.Core.Strings
// it's faster to use an array than a StringBuilder
var ilen = input.Length;
var totalSize = ilen * 2;
Span<char> output = totalSize <= 1024 ? stackalloc char[totalSize] : new char[totalSize]; // twice the length should be OK in all cases
var output = new char[ilen * 2]; // twice the length should be OK in all cases
for (var i = 0; i < ilen; i++)
{
@@ -480,11 +479,11 @@ namespace Umbraco.Cms.Core.Strings
throw new Exception("Invalid state.");
}
return new string(output.Slice(0, opos));
return new string(output, 0, opos);
}
// note: supports surrogate pairs in input string
internal void CopyTerm(string input, int ipos, Span<char> output, ref int opos, int len, CleanStringType caseType, string culture, bool isAcronym)
internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, CleanStringType caseType, string culture, bool isAcronym)
{
var term = input.Substring(ipos, len);
CultureInfo cultureInfo = string.IsNullOrEmpty(culture) ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture);
@@ -510,19 +509,19 @@ namespace Umbraco.Cms.Core.Strings
//case CleanStringType.LowerCase:
//case CleanStringType.UpperCase:
case CleanStringType.Unchanged:
term.CopyTo(output.Slice(opos, len));
term.CopyTo(0, output, opos, len);
opos += len;
break;
case CleanStringType.LowerCase:
term = term.ToLower(cultureInfo);
term.CopyTo(output.Slice(opos, term.Length));
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
break;
case CleanStringType.UpperCase:
term = term.ToUpper(cultureInfo);
term.CopyTo(output.Slice(opos, term.Length));
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
break;
@@ -533,7 +532,7 @@ namespace Umbraco.Cms.Core.Strings
{
s = term.Substring(ipos, 2);
s = opos == 0 ? s.ToLower(cultureInfo) : s.ToUpper(cultureInfo);
s.CopyTo(output.Slice(opos, s.Length));
s.CopyTo(0, output, opos, s.Length);
opos += s.Length;
i++; // surrogate pair len is 2
}
@@ -544,7 +543,7 @@ namespace Umbraco.Cms.Core.Strings
if (len > i)
{
term = term.Substring(i).ToLower(cultureInfo);
term.CopyTo(output.Slice(opos, term.Length));
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
}
break;
@@ -556,7 +555,7 @@ namespace Umbraco.Cms.Core.Strings
{
s = term.Substring(ipos, 2);
s = s.ToUpper(cultureInfo);
s.CopyTo(output.Slice(opos, s.Length));
s.CopyTo(0, output, opos, s.Length);
opos += s.Length;
i++; // surrogate pair len is 2
}
@@ -567,7 +566,7 @@ namespace Umbraco.Cms.Core.Strings
if (len > i)
{
term = term.Substring(i).ToLower(cultureInfo);
term.CopyTo(output.Slice(opos, term.Length));
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
}
break;
@@ -579,7 +578,7 @@ namespace Umbraco.Cms.Core.Strings
{
s = term.Substring(ipos, 2);
s = opos == 0 ? s : s.ToUpper(cultureInfo);
s.CopyTo(output.Slice(opos, s.Length));
s.CopyTo(0, output, opos, s.Length);
opos += s.Length;
i++; // surrogate pair len is 2
}
@@ -590,7 +589,7 @@ namespace Umbraco.Cms.Core.Strings
if (len > i)
{
term = term.Substring(i);
term.CopyTo(output.Slice(opos, term.Length));
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
}
break;
@@ -11,27 +11,21 @@ namespace Umbraco.Cms.Core.Strings;
/// </remarks>
public static class Utf8ToAsciiConverter
{
[Obsolete("Use ToAsciiString(ReadOnlySpan<char>..) instead")]
public static string ToAsciiString(string text, char fail = '?')
{
return ToAsciiString(text.AsSpan(), fail);
}
/// <summary>
/// Converts an Utf8 string into an Ascii string.
/// </summary>
/// <param name="text">The text to convert.</param>
/// <param name="fail">The character to use to replace characters that cannot properly be converted.</param>
/// <returns>The converted text.</returns>
public static string ToAsciiString(ReadOnlySpan<char> text, char fail = '?')
public static string ToAsciiString(string text, char fail = '?')
{
var input = text.ToCharArray();
// this is faster although it uses more memory
// but... we should be filtering short strings only...
var totalSize = text.Length * 3;
Span<char> output = totalSize <= 1024 ? stackalloc char[totalSize] : new char[totalSize]; // *3 because of things such as OE
var len = ToAscii(text, output, fail);
return new string(output[..len]);
var output = new char[input.Length * 3]; // *3 because of things such as OE
var len = ToAscii(input, output, fail);
return new string(output, 0, len);
// var output = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra
// ToAscii(input, output);
@@ -72,7 +66,7 @@ public static class Utf8ToAsciiConverter
/// <returns>The number of characters in the output array.</returns>
/// <remarks>The caller must ensure that the output array is big enough.</remarks>
/// <exception cref="OverflowException">The output array is not big enough.</exception>
private static int ToAscii(ReadOnlySpan<char> input, Span<char> output, char fail = '?')
private static int ToAscii(char[] input, char[] output, char fail = '?')
{
var opos = 0;
@@ -127,7 +121,7 @@ public static class Utf8ToAsciiConverter
/// <para>Input should contain Utf8 characters exclusively and NOT Unicode.</para>
/// <para>Removes controls, normalizes whitespaces, replaces symbols by '?'.</para>
/// </remarks>
private static void ToAscii(ReadOnlySpan<char> input, int ipos, Span<char> output, ref int opos, char fail = '?')
private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, char fail = '?')
{
var c = input[ipos];
-118
View File
@@ -1,118 +0,0 @@
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;
}
}

Some files were not shown because too many files have changed in this diff Show More