Compare commits
148
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0331cd154b | ||
|
|
0f98b8342c | ||
|
|
b13f90046d | ||
|
|
304db8c28a | ||
|
|
723d3be905 | ||
|
|
5f37cd3d20 | ||
|
|
203cc0e380 | ||
|
|
442de9a803 | ||
|
|
eea6b9b2f1 | ||
|
|
b3b1ce2dcd | ||
|
|
69b69a5fc1 | ||
|
|
605cda9224 | ||
|
|
b65ad4e234 | ||
|
|
4e680c88bb | ||
|
|
e8f991f485 | ||
|
|
66891333a8 | ||
|
|
044c9d2c87 | ||
|
|
7b0c419ace | ||
|
|
6ec1b9f1e4 | ||
|
|
18069633b2 | ||
|
|
b24c29c647 | ||
|
|
f3f7fcc051 | ||
|
|
b6fa93edac | ||
|
|
b07a24ba55 | ||
|
|
42cb9b5c1e | ||
|
|
88657c1bb0 | ||
|
|
e91a25dcb6 | ||
|
|
81e63e7fa9 | ||
|
|
2f9d58fc68 | ||
|
|
e9ba06929f | ||
|
|
a5da305093 | ||
|
|
e1e5f527e4 | ||
|
|
f4e0e38824 | ||
|
|
fa09ce6214 | ||
|
|
dd8fb66300 | ||
|
|
a02db287ce | ||
|
|
e9c97f8c9b | ||
|
|
f016dbd223 | ||
|
|
da5669d9fc | ||
|
|
74aa3d9676 | ||
|
|
c671668e45 | ||
|
|
e337821d0e | ||
|
|
b2aa04ae1a | ||
|
|
d3c6c87378 | ||
|
|
b3e192916d | ||
|
|
dfe100d7fc | ||
|
|
3420748b35 | ||
|
|
e270adc50f | ||
|
|
ce9460161f | ||
|
|
fd5b8de533 | ||
|
|
e3d9b042c2 | ||
|
|
54931472f8 | ||
|
|
df6a88b4d5 | ||
|
|
2cc9d2cab0 | ||
|
|
c0b6317da8 | ||
|
|
b2f69691d6 | ||
|
|
b684bf0a69 | ||
|
|
434dd50021 | ||
|
|
863d3d11c6 | ||
|
|
d906fc346d | ||
|
|
ced3db8542 | ||
|
|
a48d8a4110 | ||
|
|
c1671f4e9b | ||
|
|
be4d4bef39 | ||
|
|
f4ae796b30 | ||
|
|
367d0386b6 | ||
|
|
1d48261b7b | ||
|
|
ebb6a580dc | ||
|
|
5b54bed406 | ||
|
|
bc488d78e4 | ||
|
|
d3b60277d7 | ||
|
|
a51cbd3ee9 | ||
|
|
458eb2e52f | ||
|
|
7ef11621dd | ||
|
|
0d066c4ccc | ||
|
|
4fe168f540 | ||
|
|
ead277ed3e | ||
|
|
c35d7d18a8 | ||
|
|
2ec6ff4ebc | ||
|
|
78a8d017a2 | ||
|
|
50c3756eb0 | ||
|
|
c757f9fcb0 | ||
|
|
c03b1612b6 | ||
|
|
a79095a869 | ||
|
|
95c892000d | ||
|
|
7b6cef6083 | ||
|
|
5bf63c2fa8 | ||
|
|
a939f8ee1c | ||
|
|
29b4056f68 | ||
|
|
45ea6a3cfc | ||
|
|
6d0dd82781 | ||
|
|
7b422598f9 | ||
|
|
52bb32205b | ||
|
|
8a68457421 | ||
|
|
84d5b7aa52 | ||
|
|
62690194cf | ||
|
|
900938edd0 | ||
|
|
00e7602b55 | ||
|
|
7a9e1b66d9 | ||
|
|
1a205d8727 | ||
|
|
867d405992 | ||
|
|
a99c581ab5 | ||
|
|
85883cee85 | ||
|
|
0f9c2332e0 | ||
|
|
a7f5b142e9 | ||
|
|
ca6da751f2 | ||
|
|
5358c0346e | ||
|
|
6d5b6a4553 | ||
|
|
396b5ea211 | ||
|
|
7e4dda7bab | ||
|
|
3cf9099f56 | ||
|
|
92cb355f4d | ||
|
|
fb11191fd1 | ||
|
|
30186e0bdd | ||
|
|
2b24b40a75 | ||
|
|
09bcede957 | ||
|
|
998b6f9296 | ||
|
|
2826678049 | ||
|
|
67ed38f6a2 | ||
|
|
fbe2cfacd8 | ||
|
|
e8ff21987b | ||
|
|
b9250c20e9 | ||
|
|
e9ef05057e | ||
|
|
8379cba15d | ||
|
|
51cce234f4 | ||
|
|
865a2cd83a | ||
|
|
33d83e1491 | ||
|
|
f6b4ddf598 | ||
|
|
2ebc6badd7 | ||
|
|
2bb894c038 | ||
|
|
3ef9d64f1a | ||
|
|
a20f11e48f | ||
|
|
4795609973 | ||
|
|
8e7809d37e | ||
|
|
96febee559 | ||
|
|
82eeb0737b | ||
|
|
451b5f96e7 | ||
|
|
15ebae5025 | ||
|
|
64024e418f | ||
|
|
a7a6eb8af6 | ||
|
|
f6ab6eea75 | ||
|
|
9809db4179 | ||
|
|
32bfbadb8e | ||
|
|
5ed09ebefa | ||
|
|
eaea7a67ba | ||
|
|
de2114b8c5 | ||
|
|
64607ddbaa | ||
|
|
ac3b17b390 |
@@ -6,8 +6,8 @@ body:
|
||||
- type: input
|
||||
id: "version"
|
||||
attributes:
|
||||
label: "Which Umbraco version are you using? (Please write the *exact* version, example: 10.1.0)"
|
||||
description: "Use the help icon in the Umbraco backoffice to find the version you're using"
|
||||
label: "Which Umbraco version are you using?"
|
||||
description: "Please write the *exact* version, example: `10.1.0`. Use the help icon in the Umbraco backoffice to find the version you're using"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
+5
-4
@@ -1,11 +1,12 @@
|
||||
# [Umbraco CMS](https://umbraco.com)
|
||||
|
||||
[](../LICENSE.md)
|
||||
[](CONTRIBUTING.md)
|
||||

|
||||
[](https://discord.gg/umbraco)
|
||||
[](https://discord-chats.umbraco.com)
|
||||
[](https://www.nuget.org/packages/Umbraco.Cms)
|
||||
[](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301)
|
||||
[](CONTRIBUTING.md)
|
||||
[](https://discord.gg/umbraco)
|
||||
[](https://discord-chats.umbraco.com)
|
||||

|
||||
|
||||
|
||||
### Umbraco is a free and open source .NET content management system. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.6.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.6.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.0" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
|
||||
@@ -593,8 +593,8 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
@@ -623,13 +623,24 @@ stages:
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
variables:
|
||||
@@ -749,8 +760,8 @@ stages:
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
@@ -788,13 +799,24 @@ stages:
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
###############################################
|
||||
## Release
|
||||
###############################################
|
||||
|
||||
@@ -248,6 +248,16 @@ stages:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
timeoutInMinutes: 180
|
||||
@@ -418,3 +428,13 @@ stages:
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
|
||||
@@ -9,9 +9,10 @@ schedules:
|
||||
branches:
|
||||
include:
|
||||
- v10/dev
|
||||
- v12/dev
|
||||
- v13/dev
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
- v16/dev
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
@@ -11,5 +11,5 @@ internal sealed class RequestPreviewService : RequestHeaderHandler, IRequestPrev
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPreview() => GetHeaderValue("Preview") == "true";
|
||||
public bool IsPreview() => string.Equals(GetHeaderValue("Preview"), "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
+22
-3
@@ -30,14 +30,33 @@ public class SearchDocumentItemController : DocumentItemControllerBase
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
|
||||
=> await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId);
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(
|
||||
CancellationToken cancellationToken,
|
||||
string query,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null) =>
|
||||
await SearchWithTrashed(cancellationToken, query, null, skip, take, parentId, allowedDocumentTypes);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedDocumentTypes = null)
|
||||
public async Task<IActionResult> SearchWithTrashed(
|
||||
CancellationToken cancellationToken,
|
||||
string query,
|
||||
bool? trashed = null,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
Guid? parentId = null,
|
||||
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, skip, take);
|
||||
var result = new PagedModel<DocumentItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
|
||||
|
||||
+12
-4
@@ -1,5 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -9,7 +8,6 @@ using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
@@ -25,6 +23,15 @@ public class AllowedChildrenDocumentTypeController : DocumentTypeControllerBase
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Use the non obsoleted method instead. Scheduled to be removed in v16")]
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
=> await AllowedChildrenByKey(cancellationToken, id, null, skip, take);
|
||||
|
||||
[HttpGet("{id:guid}/allowed-children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<AllowedDocumentType>), StatusCodes.Status200OK)]
|
||||
@@ -32,10 +39,11 @@ public class AllowedChildrenDocumentTypeController : DocumentTypeControllerBase
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
Guid? parentContentKey = null,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
{
|
||||
Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, skip, take);
|
||||
Attempt<PagedModel<IContentType>?, ContentTypeOperationStatus> attempt = await _contentTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take);
|
||||
if (attempt.Success is false)
|
||||
{
|
||||
return OperationStatusResult(attempt.Status);
|
||||
|
||||
+32
-17
@@ -1,45 +1,60 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType.Item;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Item;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class SearchDocumentTypeItemController : DocumentTypeItemControllerBase
|
||||
{
|
||||
private readonly IEntitySearchService _entitySearchService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
private readonly IContentTypeSearchService _contentTypeSearchService;
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
public SearchDocumentTypeItemController(IEntitySearchService entitySearchService, IContentTypeService contentTypeService, IUmbracoMapper mapper)
|
||||
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<IContentTypeSearchService>())
|
||||
{
|
||||
_entitySearchService = entitySearchService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[Obsolete("Please use ctor that only accepts IUmbracoMapper & IContentTypeSearchService, scheduled for removal in v17")]
|
||||
// We need to have this constructor, or else we get ambiguous constructor error
|
||||
public SearchDocumentTypeItemController(
|
||||
IEntitySearchService entitySearchService,
|
||||
IContentTypeService contentTypeService,
|
||||
IUmbracoMapper mapper,
|
||||
IContentTypeSearchService contentTypeSearchService)
|
||||
: this(mapper, contentTypeSearchService)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public SearchDocumentTypeItemController(IUmbracoMapper mapper, IContentTypeSearchService contentTypeSearchService)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_contentTypeSearchService = contentTypeSearchService;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
=> await SearchDocumentType(cancellationToken, query, null, skip, take);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<DocumentTypeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Search(CancellationToken cancellationToken, string query, int skip = 0, int take = 100)
|
||||
public async Task<IActionResult> SearchDocumentType(CancellationToken cancellationToken, string query, bool? isElement = null, int skip = 0, int take = 100)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _entitySearchService.Search(UmbracoObjectTypes.DocumentType, query, skip, take);
|
||||
if (searchResult.Items.Any() is false)
|
||||
{
|
||||
return await Task.FromResult(Ok(new PagedModel<DocumentTypeItemResponseModel> { Total = searchResult.Total }));
|
||||
}
|
||||
|
||||
IEnumerable<IContentType> contentTypes = _contentTypeService.GetMany(searchResult.Items.Select(item => item.Key).ToArray().EmptyNull());
|
||||
PagedModel<IContentType> contentTypes = await _contentTypeSearchService.SearchAsync(query, isElement, cancellationToken, skip, take);
|
||||
var result = new PagedModel<DocumentTypeItemResponseModel>
|
||||
{
|
||||
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes),
|
||||
Total = searchResult.Total
|
||||
Items = _mapper.MapEnumerable<IContentType, DocumentTypeItemResponseModel>(contentTypes.Items),
|
||||
Total = contentTypes.Total
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
|
||||
@@ -32,12 +32,17 @@ public class SearchMediaItemController : MediaItemControllerBase
|
||||
public async Task<IActionResult> SearchFromParent(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, skip, take, parentId);
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Scheduled to be removed in v16, use the non obsoleted method instead")]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
=> await SearchFromParentWithAllowedTypes(cancellationToken, query, null, skip, take, parentId);
|
||||
|
||||
[HttpGet("search")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedModel<MediaItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
public async Task<IActionResult> SearchFromParentWithAllowedTypes(CancellationToken cancellationToken, string query, bool? trashed = null, int skip = 0, int take = 100, Guid? parentId = null, [FromQuery]IEnumerable<Guid>? allowedMediaTypes = null)
|
||||
{
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, skip, take);
|
||||
PagedModel<IEntitySlim> searchResult = _indexedEntitySearchService.Search(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, skip, take);
|
||||
var result = new PagedModel<MediaItemResponseModel>
|
||||
{
|
||||
Items = searchResult.Items.OfType<IMediaEntitySlim>().Select(_mediaPresentationFactory.CreateItemResponseModel),
|
||||
|
||||
+12
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
@@ -23,6 +23,15 @@ public class AllowedChildrenMediaTypeController : MediaTypeControllerBase
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[NonAction]
|
||||
[Obsolete("Use the non obsoleted method instead. Scheduled for removal in Umbraco 16.")]
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
=> await AllowedChildrenByKey(cancellationToken, id, null, skip, take);
|
||||
|
||||
[HttpGet("{id:guid}/allowed-children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<AllowedMediaType>), StatusCodes.Status200OK)]
|
||||
@@ -30,10 +39,11 @@ public class AllowedChildrenMediaTypeController : MediaTypeControllerBase
|
||||
public async Task<IActionResult> AllowedChildrenByKey(
|
||||
CancellationToken cancellationToken,
|
||||
Guid id,
|
||||
Guid? parentContentKey = null,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
{
|
||||
Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, skip, take);
|
||||
Attempt<PagedModel<IMediaType>?, ContentTypeOperationStatus> attempt = await _mediaTypeService.GetAllowedChildrenAsync(id, parentContentKey, skip, take);
|
||||
if (attempt.Success is false)
|
||||
{
|
||||
return OperationStatusResult(attempt.Status);
|
||||
|
||||
+15
-2
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
@@ -17,7 +17,20 @@ public class RebuildPublishedCacheController : PublishedCacheControllerBase
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Rebuild(CancellationToken cancellationToken)
|
||||
{
|
||||
_databaseCacheRebuilder.Rebuild();
|
||||
if (_databaseCacheRebuilder.IsRebuilding())
|
||||
{
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Title = "Database cache can not be rebuilt",
|
||||
Detail = $"The database cache is in the process of rebuilding.",
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
return await Task.FromResult(Conflict(problemDetails));
|
||||
}
|
||||
|
||||
_databaseCacheRebuilder.Rebuild(true);
|
||||
return await Task.FromResult(Ok());
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.PublishedCache;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class RebuildPublishedCacheStatusController : PublishedCacheControllerBase
|
||||
{
|
||||
private readonly IDatabaseCacheRebuilder _databaseCacheRebuilder;
|
||||
|
||||
public RebuildPublishedCacheStatusController(IDatabaseCacheRebuilder databaseCacheRebuilder) => _databaseCacheRebuilder = databaseCacheRebuilder;
|
||||
|
||||
[HttpGet("rebuild/status")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(RebuildStatusModel), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Status(CancellationToken cancellationToken)
|
||||
{
|
||||
var isRebuilding = _databaseCacheRebuilder.IsRebuilding();
|
||||
return Task.FromResult((IActionResult)Ok(new RebuildStatusModel
|
||||
{
|
||||
IsRebuilding = isRebuilding
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Security;
|
||||
public class BackOfficeGraphicsController : Controller
|
||||
{
|
||||
public const string LogoRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(Logo);
|
||||
public const string LogoAlternativeRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LogoAlternative);
|
||||
public const string LoginBackGroundRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginBackground);
|
||||
public const string LoginLogoRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginLogo);
|
||||
public const string LoginLogoAlternativeRouteName = nameof(BackOfficeGraphicsController) + "." + nameof(LoginLogoAlternative);
|
||||
@@ -44,6 +45,11 @@ public class BackOfficeGraphicsController : Controller
|
||||
[MapToApiVersion("1.0")]
|
||||
public IActionResult Logo() => HandleFileRequest(_contentSettings.Value.BackOfficeLogo);
|
||||
|
||||
[HttpGet("logo-alternative", Name = LogoAlternativeRouteName)]
|
||||
[AllowAnonymous]
|
||||
[MapToApiVersion("1.0")]
|
||||
public IActionResult LogoAlternative() => HandleFileRequest(_contentSettings.Value.BackOfficeLogoAlternative);
|
||||
|
||||
[HttpGet("login-logo", Name = LoginLogoRouteName)]
|
||||
[AllowAnonymous]
|
||||
[MapToApiVersion("1.0")]
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Security.OperationStatus;
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase
|
||||
.Build()),
|
||||
BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid client ID")
|
||||
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 255 characters.")
|
||||
.WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~]. Furthermore, including the prefix it cannot be longer than 100 characters.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
|
||||
.WithTitle("Unknown client credentials operation status.")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
@@ -59,7 +59,7 @@ public abstract class BackOfficeSecurityRequirementsOperationFilterBase : IOpera
|
||||
{
|
||||
operation.Responses.Add(StatusCodes.Status403Forbidden.ToString(), new OpenApiResponse()
|
||||
{
|
||||
Description = "The authenticated user do not have access to this resource"
|
||||
Description = "The authenticated user does not have access to this resource"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.PublishedCache;
|
||||
|
||||
public class RebuildStatusModel
|
||||
{
|
||||
public bool IsRebuilding { get; set; }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 468 468"><path d="M5.5 233.7C5.7 107.5 108.1 5.3 234.3 5.5s228.4 102.6 228.2 228.8c-.2 126.1-102.4 228.2-228.5 228.2C107.7 462.4 5.4 360 5.5 233.7zM229.4 312c-17.7.5-35.4-1.1-52.8-4.8-12.8-2.6-23.6-11.2-28.9-23.1-5.2-11.9-7.7-30.3-7.5-55.1.1-13 .9-25.9 2.4-38.8 1.5-12.6 2.9-22.9 4.4-31.1l1.6-8.1v-.7c0-2.3-1.6-4.2-3.8-4.6l-29.5-4.6h-.7c-2.2 0-4 1.5-4.5 3.6-.5 1.9-.8 3.3-1.7 7.8-1.7 8.7-3.2 17.1-5 29.5-1.8 13.4-2.9 26.9-3.3 40.4-.6 9.4-.6 18.9 0 28.3.7 25 5 45 12.9 59.9s21.2 25.7 40.1 32.3c18.8 6.6 45.1 9.8 78.7 9.6h4.2c33.7.1 59.9-3.1 78.7-9.6 18.8-6.6 32.2-17.3 40.1-32.3 7.9-15 12.2-34.9 12.9-59.9.6-9.4.6-18.9 0-28.3-.4-13.5-1.5-27-3.3-40.4-1.7-12.2-3.3-20.6-5-29.5-.9-4.5-1.2-5.9-1.7-7.8-.5-2.1-2.3-3.6-4.5-3.6h-.8l-29.5 4.6c-2.2.4-3.9 2.3-3.9 4.6v.7l1.6 8.1c1.5 8.2 3 18.5 4.5 31 1.5 12.9 2.3 25.9 2.4 38.8.2 24.8-2.3 43.2-7.6 55.1-5.2 11.9-15.9 20.5-28.6 23.2-17.3 3.7-35.1 5.3-52.8 4.8h-9.1z" fill="#283a97"/></svg>
|
||||
|
After Width: | Height: | Size: 980 B |
@@ -1,51 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000159465072208785227250000008040336475015305350_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#283A97;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,5,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.8c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2h-0.2"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg version="1.1" id="prefix__Layer_2_00000159465072208785227250000008040336475015305350_" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1256.6 344.8" xml:space="preserve"><style>.prefix__st0{fill:#283a97}</style><g id="prefix__Layer_1-2"><path class="prefix__st0" d="M5 172.2C5.1 79.7 80.2 4.9 172.6 5s167.3 75.2 167.2 167.6c-.1 92.4-75 167.2-167.4 167.2C79.9 339.7 5 264.7 5 172.2zm164 57.3c-13 .4-26-.8-38.7-3.5-9.4-1.9-17.3-8.2-21.2-17-3.8-8.7-5.6-22.2-5.5-40.4.1-9.5.7-19 1.8-28.4 1.1-9.2 2.2-16.8 3.2-22.8l1.1-5.9v-.5c0-1.7-1.2-3.1-2.8-3.4l-21.6-3.4H85c-1.6 0-2.9 1.1-3.3 2.6-.4 1.4-.6 2.4-1.2 5.7-1.2 6.4-2.4 12.6-3.6 21.6-1.3 9.8-2.1 19.7-2.4 29.6-.5 6.9-.5 13.8 0 20.7.5 18.3 3.7 33 9.4 43.9s15.6 18.8 29.4 23.6c13.8 4.8 33 7.2 57.7 7.1h3.1c24.7.1 43.9-2.2 57.7-7.1 13.8-4.8 23.6-12.7 29.4-23.6 5.8-11 8.9-25.6 9.4-43.9.5-6.9.5-13.8 0-20.7-.3-9.9-1.1-19.8-2.4-29.6-1.3-9-2.4-15.1-3.6-21.6-.6-3.3-.9-4.3-1.2-5.7-.4-1.5-1.7-2.6-3.3-2.6h-.6l-21.6 3.4c-1.6.3-2.8 1.7-2.8 3.4v.5l1.2 5.9c1.1 6 2.2 13.6 3.3 22.7 1.1 9.4 1.7 18.9 1.8 28.4.2 18.2-1.7 31.6-5.5 40.4-3.8 8.7-11.6 15-20.9 17-12.7 2.7-25.7 3.9-38.7 3.5l-7.3.1zM1155.2 186.1c0-33.4 9.6-56.9 48.2-56.9s48.2 23.5 48.2 56.9-9.6 56.9-48.2 56.9-48.2-23.5-48.2-56.9zm71.9 0c0-23.2-3-36.5-23.6-36.5s-23.6 13.3-23.6 36.5 3.1 36.5 23.6 36.5 23.6-13.4 23.6-36.5zM487.2 238.7c.6 1 1.6 1.6 2.8 1.6h9c1.8 0 3.2-1.4 3.2-3.2V135c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v81c-7.7 4.3-16.4 6.5-25.2 6.3-11.5 0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9c-1.8 0-3.2 1.4-3.2 3.2v73.4c0 20.8 9.8 34.5 37.3 34.5 12.6-.1 24.9-4.1 35.2-11.3l3.2 7.2.4-.1zM689.8 163.7c0-20.6-10.2-34.5-35.9-34.5-12.4 0-24.5 3.8-34.8 10.7-4.6-6.7-13.3-10.7-27.8-10.7-11.8.2-23.3 4.2-32.8 11.3l-3.2-7.2c-.6-1-1.6-1.7-2.8-1.7h-9c-1.8 0-3.2 1.4-3.2 3.2v102.1c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-80.8c6.9-4.1 14.8-6.3 22.8-6.3 9.8 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-81.1c6.8-4.2 14.8-6.4 22.8-6.4 9.6 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2l-.1-73.4zM745.4 231.7c10.2 7.5 22.5 11.5 35.2 11.3 31.7 0 43.4-21.3 43.4-56.9s-11.7-56.9-43.4-56.9c-10.3.1-20.4 3-29.1 8.5v-32.8c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v132.3c0 1.8 1.4 3.2 3.2 3.2h9c1.2 0 2.2-.6 2.8-1.6l3.3-7.1zm29.5-9.4c-8.2 0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1 15.2-6.3 23.4-6.3 21.3 0 24.5 16.3 24.5 36.2s-3.2 36.3-24.5 36.3zM913.9 150.5c-2.8-.4-5.6-.6-8.5-.5-9.7-.4-19.3 1.9-27.8 6.5v80.6c0 1.8-1.4 3.2-3.2 3.2h-18c-1.8 0-3.2-1.4-3.2-3.2V135c0-1.8 1.4-3.2 3.2-3.2h9c1.2 0 2.2.6 2.8 1.6l3.2 7.2c9.9-7.5 22-11.5 34.4-11.3 2.8 0 5.7.2 8.5.7 1.7 0 3 2.7 3 4.4v13c0 1.8-1.4 3.2-3.2 3.2h-.2M976.1 190.2c-10.7 1.3-17.2 5.4-17.2 16.7 0 8.3 3.6 16.1 16.7 16.1 8.3.1 16.4-2.4 23.2-7.2v-28.1l-22.7 2.5zm28.7 41.5C995.5 239 984 243 972.1 243c-28 0-37.3-17.4-37.3-34.8 0-23.5 15.2-33.4 39.7-35.4l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4-10.1 0-20.1 1.7-29.6 4.9-.3.1-.7.1-1 0-1.8 0-3.2-1.4-3.2-3.2v-14.4c0-1.4.8-2.6 2.1-3.1 10.8-3.6 22.1-5.4 33.5-5.4 35.6 0 43.8 15.6 43.8 38.7v69.3c0 1.8-1.4 3.2-3.2 3.2h-9c-1.2 0-2.2-.6-2.8-1.6l-3.4-6.8zM1128.6 218h1c1.8 0 3.2 1.4 3.2 3.2v14.4c0 1.3-.8 2.5-2 3-8.9 3.2-18.4 4.8-27.9 4.6-38.5 0-50.3-23-50.3-56.9s11.8-56.9 50.3-56.9c9.4-.2 18.9 1.2 27.8 4.4 1.2.5 2 1.7 2 3v14.5c0 1.8-1.4 3.2-3.2 3.2-.4.1-.7.1-1.1 0-7.8-2.5-16-3.7-24.2-3.6-21.1 0-27.2 14.4-27.2 35.4s6.1 35.4 27.2 35.4c8.2.1 16.4-1.1 24.2-3.6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -1,51 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2_00000109717856507224707600000004572449649498936508_"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1256.6 344.8"
|
||||
style="enable-background:new 0 0 1256.6 344.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_1-2">
|
||||
<g>
|
||||
<path class="st0" d="M5,172.2C5.1,79.7,80.2,4.9,172.6,5s167.3,75.2,167.2,167.6c-0.1,92.4-75,167.2-167.4,167.2
|
||||
C79.9,339.7,4.9,264.7,5,172.2L5,172.2L5,172.2z M169,229.5c-13,0.4-26-0.8-38.7-3.5c-9.4-1.9-17.3-8.2-21.2-17
|
||||
c-3.8-8.7-5.6-22.2-5.5-40.4c0.1-9.5,0.7-19,1.8-28.4c1.1-9.2,2.2-16.8,3.2-22.8l1.1-5.9c0-0.2,0-0.3,0-0.5c0-1.7-1.2-3.1-2.8-3.4
|
||||
l-21.6-3.4H85c-1.6,0-2.9,1.1-3.3,2.6c-0.4,1.4-0.6,2.4-1.2,5.7c-1.2,6.4-2.4,12.6-3.6,21.6c-1.3,9.8-2.1,19.7-2.4,29.6
|
||||
c-0.5,6.9-0.5,13.8,0,20.7c0.5,18.3,3.7,33,9.4,43.9s15.6,18.8,29.4,23.6c13.8,4.8,33,7.2,57.7,7.1h3.1
|
||||
c24.7,0.1,43.9-2.2,57.7-7.1c13.8-4.8,23.6-12.7,29.4-23.6c5.8-11,8.9-25.6,9.4-43.9c0.5-6.9,0.5-13.8,0-20.7
|
||||
c-0.3-9.9-1.1-19.8-2.4-29.6c-1.3-8.9-2.4-15.1-3.6-21.6c-0.6-3.3-0.9-4.3-1.2-5.7c-0.4-1.5-1.7-2.6-3.3-2.6h-0.6l-21.6,3.4
|
||||
c-1.6,0.3-2.8,1.7-2.8,3.4c0,0.2,0,0.3,0,0.5l1.2,5.9c1.1,6,2.2,13.6,3.3,22.7c1.1,9.4,1.7,18.9,1.8,28.4
|
||||
c0.2,18.2-1.7,31.6-5.5,40.4c-3.8,8.7-11.6,15-20.9,17c-12.7,2.7-25.7,3.9-38.7,3.5L169,229.5L169,229.5z"/>
|
||||
<g>
|
||||
<path class="st0" d="M1155.2,186.1c0-33.4,9.6-56.9,48.2-56.9s48.2,23.5,48.2,56.9s-9.6,56.9-48.2,56.9
|
||||
S1155.2,219.5,1155.2,186.1z M1227.1,186.1c0-23.2-3-36.5-23.6-36.5s-23.6,13.3-23.6,36.5s3.1,36.5,23.6,36.5
|
||||
S1227.1,209.2,1227.1,186.1z"/>
|
||||
<path class="st0" d="M487.2,238.7c0.6,1,1.6,1.6,2.8,1.6h9c1.8,0,3.2-1.4,3.2-3.2l0,0V135c0-1.8-1.4-3.2-3.2-3.2h-18
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v81c-7.7,4.3-16.4,6.5-25.2,6.3c-11.5,0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9
|
||||
c-1.8,0-3.2,1.4-3.2,3.2v73.4c0,20.8,9.8,34.5,37.3,34.5c12.6-0.1,24.9-4.1,35.2-11.3l3.2,7.2L487.2,238.7L487.2,238.7z"/>
|
||||
<path class="st0" d="M689.8,163.7c0-20.6-10.2-34.5-35.9-34.5c-12.4,0-24.5,3.8-34.8,10.7c-4.6-6.7-13.3-10.7-27.8-10.7
|
||||
c-11.8,0.2-23.3,4.2-32.8,11.3l-3.2-7.2l0,0c-0.6-1-1.6-1.7-2.8-1.7h-9c-1.8,0-3.2,1.4-3.2,3.2v102.1c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-80.8c6.9-4.1,14.8-6.3,22.8-6.3c9.8,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2v-81.1c6.8-4.2,14.8-6.4,22.8-6.4c9.6,0,15.4,3.6,15.4,14v73.4c0,1.8,1.4,3.2,3.2,3.2h18
|
||||
c1.8,0,3.2-1.4,3.2-3.2l0,0L689.8,163.7L689.8,163.7z"/>
|
||||
<path class="st0" d="M745.4,231.7c10.2,7.5,22.5,11.5,35.2,11.3c31.7,0,43.4-21.3,43.4-56.9s-11.7-56.9-43.4-56.9
|
||||
c-10.3,0.1-20.4,3-29.1,8.5v-32.9c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8,0-3.2,1.4-3.2,3.2v132.3c0,1.8,1.4,3.2,3.2,3.2h9
|
||||
c1.2,0,2.2-0.6,2.8-1.6l0,0L745.4,231.7L745.4,231.7z M774.9,222.3c-8.2,0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1,15.2-6.3,23.4-6.3
|
||||
c21.3,0,24.5,16.3,24.5,36.2S796.2,222.3,774.9,222.3L774.9,222.3L774.9,222.3z"/>
|
||||
<path class="st0" d="M913.9,150.5c-2.8-0.4-5.6-0.6-8.5-0.5c-9.7-0.4-19.3,1.9-27.8,6.5v80.6c0,1.8-1.4,3.2-3.2,3.2h-18
|
||||
c-1.8,0-3.2-1.4-3.2-3.2V135c0-1.8,1.4-3.2,3.2-3.2h9c1.2,0,2.2,0.6,2.8,1.6l0,0l3.2,7.2c9.9-7.5,22-11.5,34.4-11.3
|
||||
c2.8,0,5.7,0.2,8.5,0.7l0,0c1.7,0,3,2.7,3,4.4v13c0,1.8-1.4,3.2-3.2,3.2L913.9,150.5"/>
|
||||
<path class="st0" d="M976.1,190.2c-10.7,1.3-17.2,5.4-17.2,16.7c0,8.3,3.6,16.1,16.7,16.1c8.3,0.1,16.4-2.4,23.2-7.2v-28.1
|
||||
L976.1,190.2L976.1,190.2z M1004.8,231.7C995.5,239,984,243,972.1,243c-28,0-37.3-17.4-37.3-34.8c0-23.5,15.2-33.4,39.7-35.4
|
||||
l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4c-10.1,0-20.1,1.7-29.6,4.9c-0.3,0.1-0.7,0.1-1,0c-1.8,0-3.2-1.4-3.2-3.2v-14.4
|
||||
c0-1.4,0.8-2.6,2.1-3.1l0,0c10.8-3.6,22.1-5.4,33.5-5.4c35.6,0,43.8,15.6,43.8,38.7v69.3c0,1.8-1.4,3.2-3.2,3.2h-9
|
||||
c-1.2,0-2.2-0.6-2.8-1.6l0,0L1004.8,231.7L1004.8,231.7z"/>
|
||||
<path class="st0" d="M1128.6,218c0.3,0,0.7,0,1,0c1.8,0,3.2,1.4,3.2,3.2v14.4c0,1.3-0.8,2.5-2,3l0,0c-8.9,3.2-18.4,4.8-27.9,4.6
|
||||
c-38.5,0-50.3-23-50.3-56.9s11.8-56.9,50.3-56.9c9.4-0.2,18.9,1.2,27.8,4.4l0,0c1.2,0.5,2,1.7,2,3v14.5c0,1.8-1.4,3.2-3.2,3.2
|
||||
c-0.4,0.1-0.7,0.1-1.1,0l0,0c-7.8-2.5-16-3.7-24.2-3.6c-21.1,0-27.2,14.4-27.2,35.4s6.1,35.4,27.2,35.4
|
||||
c8.2,0.1,16.4-1.1,24.2-3.6"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg version="1.1" id="prefix__Layer_2_00000109717856507224707600000004572449649498936508_" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 1256.6 344.8" xml:space="preserve"><style>.prefix__st0{fill:#fff}</style><g id="prefix__Layer_1-2"><path class="prefix__st0" d="M5 172.2C5.1 79.7 80.2 4.9 172.6 5s167.3 75.2 167.2 167.6c-.1 92.4-75 167.2-167.4 167.2C79.9 339.7 4.9 264.7 5 172.2zm164 57.3c-13 .4-26-.8-38.7-3.5-9.4-1.9-17.3-8.2-21.2-17-3.8-8.7-5.6-22.2-5.5-40.4.1-9.5.7-19 1.8-28.4 1.1-9.2 2.2-16.8 3.2-22.8l1.1-5.9v-.5c0-1.7-1.2-3.1-2.8-3.4l-21.6-3.4H85c-1.6 0-2.9 1.1-3.3 2.6-.4 1.4-.6 2.4-1.2 5.7-1.2 6.4-2.4 12.6-3.6 21.6-1.3 9.8-2.1 19.7-2.4 29.6-.5 6.9-.5 13.8 0 20.7.5 18.3 3.7 33 9.4 43.9s15.6 18.8 29.4 23.6c13.8 4.8 33 7.2 57.7 7.1h3.1c24.7.1 43.9-2.2 57.7-7.1 13.8-4.8 23.6-12.7 29.4-23.6 5.8-11 8.9-25.6 9.4-43.9.5-6.9.5-13.8 0-20.7-.3-9.9-1.1-19.8-2.4-29.6-1.3-8.9-2.4-15.1-3.6-21.6-.6-3.3-.9-4.3-1.2-5.7-.4-1.5-1.7-2.6-3.3-2.6h-.6l-21.6 3.4c-1.6.3-2.8 1.7-2.8 3.4v.5l1.2 5.9c1.1 6 2.2 13.6 3.3 22.7 1.1 9.4 1.7 18.9 1.8 28.4.2 18.2-1.7 31.6-5.5 40.4-3.8 8.7-11.6 15-20.9 17-12.7 2.7-25.7 3.9-38.7 3.5l-7.3.1zM1155.2 186.1c0-33.4 9.6-56.9 48.2-56.9s48.2 23.5 48.2 56.9-9.6 56.9-48.2 56.9-48.2-23.5-48.2-56.9zm71.9 0c0-23.2-3-36.5-23.6-36.5s-23.6 13.3-23.6 36.5 3.1 36.5 23.6 36.5 23.6-13.4 23.6-36.5zM487.2 238.7c.6 1 1.6 1.6 2.8 1.6h9c1.8 0 3.2-1.4 3.2-3.2V135c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v81c-7.7 4.3-16.4 6.5-25.2 6.3-11.5 0-17.2-5-17.2-16.1V135c0-1.8-1.4-3.2-3.2-3.2h-17.9c-1.8 0-3.2 1.4-3.2 3.2v73.4c0 20.8 9.8 34.5 37.3 34.5 12.6-.1 24.9-4.1 35.2-11.3l3.2 7.2.4-.1zM689.8 163.7c0-20.6-10.2-34.5-35.9-34.5-12.4 0-24.5 3.8-34.8 10.7-4.6-6.7-13.3-10.7-27.8-10.7-11.8.2-23.3 4.2-32.8 11.3l-3.2-7.2c-.6-1-1.6-1.7-2.8-1.7h-9c-1.8 0-3.2 1.4-3.2 3.2v102.1c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-80.8c6.9-4.1 14.8-6.3 22.8-6.3 9.8 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2v-81.1c6.8-4.2 14.8-6.4 22.8-6.4 9.6 0 15.4 3.6 15.4 14v73.4c0 1.8 1.4 3.2 3.2 3.2h18c1.8 0 3.2-1.4 3.2-3.2l-.1-73.4zM745.4 231.7c10.2 7.5 22.5 11.5 35.2 11.3 31.7 0 43.4-21.3 43.4-56.9s-11.7-56.9-43.4-56.9c-10.3.1-20.4 3-29.1 8.5v-32.9c0-1.8-1.4-3.2-3.2-3.2h-18c-1.8 0-3.2 1.4-3.2 3.2v132.3c0 1.8 1.4 3.2 3.2 3.2h9c1.2 0 2.2-.6 2.8-1.6l3.3-7zm29.5-9.4c-8.2 0-16.3-2.1-23.4-6.3v-59.9c7.1-4.1 15.2-6.3 23.4-6.3 21.3 0 24.5 16.3 24.5 36.2s-3.2 36.3-24.5 36.3zM913.9 150.5c-2.8-.4-5.6-.6-8.5-.5-9.7-.4-19.3 1.9-27.8 6.5v80.6c0 1.8-1.4 3.2-3.2 3.2h-18c-1.8 0-3.2-1.4-3.2-3.2V135c0-1.8 1.4-3.2 3.2-3.2h9c1.2 0 2.2.6 2.8 1.6l3.2 7.2c9.9-7.5 22-11.5 34.4-11.3 2.8 0 5.7.2 8.5.7 1.7 0 3 2.7 3 4.4v13c0 1.8-1.4 3.2-3.2 3.2l-.2-.1M976.1 190.2c-10.7 1.3-17.2 5.4-17.2 16.7 0 8.3 3.6 16.1 16.7 16.1 8.3.1 16.4-2.4 23.2-7.2v-28.1l-22.7 2.5zm28.7 41.5C995.5 239 984 243 972.1 243c-28 0-37.3-17.4-37.3-34.8 0-23.5 15.2-33.4 39.7-35.4l24.4-1.9v-5.4c0-11.1-5.2-15.4-21.3-15.4-10.1 0-20.1 1.7-29.6 4.9-.3.1-.7.1-1 0-1.8 0-3.2-1.4-3.2-3.2v-14.4c0-1.4.8-2.6 2.1-3.1 10.8-3.6 22.1-5.4 33.5-5.4 35.6 0 43.8 15.6 43.8 38.7v69.3c0 1.8-1.4 3.2-3.2 3.2h-9c-1.2 0-2.2-.6-2.8-1.6l-3.4-6.8zM1128.6 218h1c1.8 0 3.2 1.4 3.2 3.2v14.4c0 1.3-.8 2.5-2 3-8.9 3.2-18.4 4.8-27.9 4.6-38.5 0-50.3-23-50.3-56.9s11.8-56.9 50.3-56.9c9.4-.2 18.9 1.2 27.8 4.4 1.2.5 2 1.7 2 3v14.5c0 1.8-1.4 3.2-3.2 3.2-.4.1-.7.1-1.1 0-7.8-2.5-16-3.7-24.2-3.6-21.1 0-27.2 14.4-27.2 35.4s6.1 35.4 27.2 35.4c8.2.1 16.4-1.1 24.2-3.6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -23,6 +23,7 @@ public class ContentSettings
|
||||
internal const string StaticLoginLogoImage = "assets/logo_light.svg";
|
||||
internal const string StaticLoginLogoImageAlternative = "assets/logo_dark.svg";
|
||||
internal const string StaticBackOfficeLogo = "assets/logo.svg";
|
||||
internal const string StaticBackOfficeLogoAlternative = "assets/logo_blue.svg";
|
||||
internal const bool StaticHideBackOfficeLogo = false;
|
||||
internal const bool StaticDisableDeleteWhenReferenced = false;
|
||||
internal const bool StaticDisableUnpublishWhenReferenced = false;
|
||||
@@ -88,9 +89,18 @@ public class ContentSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the path to the backoffice logo.
|
||||
/// </summary>
|
||||
/// <remarks>The alternative version of this logo can be found at <see cref="BackOfficeLogoAlternative"/>.</remarks>
|
||||
[DefaultValue(StaticBackOfficeLogo)]
|
||||
public string BackOfficeLogo { get; set; } = StaticBackOfficeLogo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value for the path to the alternative backoffice logo, which can be shown
|
||||
/// on top of a light background.
|
||||
/// </summary>
|
||||
/// <remarks>This is the alternative version to the regular logo found at <see cref="BackOfficeLogo"/>.</remarks>
|
||||
[DefaultValue(StaticBackOfficeLogoAlternative)]
|
||||
public string BackOfficeLogoAlternative { get; set; } = StaticBackOfficeLogoAlternative;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<language alias="da" intName="Danish" localName="dansk" lcid="6" culture="da-DK">
|
||||
<creator>
|
||||
<name>The Umbraco community</name>
|
||||
@@ -114,13 +114,23 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="entriesExceed"><![CDATA[Maksimum %0% element(er), <strong>%1%</strong> for mange.]]></key>
|
||||
<key alias="entriesAreasMismatch">Ét eller flere områder lever ikke op til kravene for antal indholdselementer.</key>
|
||||
<key alias="invalidMediaType">Den valgte medie type er ugyldig.</key>
|
||||
<key alias="invalidContentType">Det valgte indhold er af en ugyldig type.</key>
|
||||
<key alias="missingContent">Det valgte indhold eksistere ikke.</key>
|
||||
<key alias="multipleMediaNotAllowed">Det er kun tilladt at vælge ét medie.</key>
|
||||
<key alias="invalidStartNode">Valgt medie kommer fra en ugyldig mappe.</key>
|
||||
<key alias="invalidStartNode">Valgt indhold kommer fra en ugyldig mappe.</key>
|
||||
<key alias="outOfRangeMinimum">Værdien %0% er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMaximum">Værdien %0% er større end det tilladte maksimum af %1%.</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">Den ene enhed givet er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">De %0% enheder givet er mindre end det tilladte minimum af %1%.</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">De %0% enheder givet er større end det tilladte minimum af %1%.</key>
|
||||
<key alias="invalidStep">Værdien %0% passer ikke med den konfigureret trin værdi af %1% og mindste værdi af %2%.</key>
|
||||
<key alias="unexpectedRange">Værdien %0% forventes ikke at indeholde et spænd.</key>
|
||||
<key alias="stringLengthExceeded">Tekststrengen er længere end den tilladte længde på %0% tegn, %1% for mange.</key>
|
||||
<key alias="invalidRange">Værdien %0% forventes at have en værdi der er større end fra værdien.</key>
|
||||
<key alias="invalidObjectType">Det valgte indhold er af den forkerte type.</key>
|
||||
<key alias="notOneOfOptions">"Værdien '%0%' er ikke en af de tilgængelige valgmuligheder.</key>
|
||||
<key alias="multipleNotOneOfOptions">Værdierne '%0%' er ikke tilstede i de tilgængelige valgmuligheder.</key>
|
||||
<key alias="invalidColor">"Den valgte farve '%0%' er ikke en af de tilgængelige valgmuligheder.</key>
|
||||
</area>
|
||||
<area alias="recycleBin">
|
||||
<key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key>
|
||||
|
||||
@@ -378,6 +378,7 @@
|
||||
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMemberGroupName">Invalid member group name</key>
|
||||
<key alias="invalidUserGroupName">Invalid user group name</key>
|
||||
@@ -389,12 +390,22 @@
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
<key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key>
|
||||
<key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key>
|
||||
<key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key>
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="invalidContentType">The chosen content is of invalid type.</key>
|
||||
<key alias="missingContent">The chosen content does not exist.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
<key alias="notOneOfOptions">The value '%0%' is not one of the available options.</key>
|
||||
<key alias="multipleNotOneOfOptions">The values '%0%' are not found in the the available options.</key>
|
||||
<key alias="invalidColor">"The selected colour '%0%' is not one of the available options.</key>
|
||||
<key alias="invalidStartNode">The selected item is from the wrong folder.</key>
|
||||
<key alias="invalidObjectType">The selected item is of the wrong type.</key>
|
||||
<key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -387,15 +387,25 @@
|
||||
<key alias="duplicateUsername">Username '%0%' is already taken</key>
|
||||
<key alias="outOfRangeMinimum">The value %0% is less than the allowed minimum value of %1%</key>
|
||||
<key alias="outOfRangeMaximum">The value %0% is greater than the allowed maximum value of %1%</key>
|
||||
<key alias="outOfRangeSingleItemMinimum">The 1 item provided is less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMinimum">The %0% items provided are less than the allowed minimum of %1%</key>
|
||||
<key alias="outOfRangeMultipleItemsMaximum">The %0% items provided are greater than the allowed maximum of %1%</key>
|
||||
<key alias="invalidStep">The value %0% does not correspond with the configured step value of %1% and minimum value of %2%</key>
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="invalidContentType">The chosen content is of invalid type.</key>
|
||||
<key alias="missingContent">The chosen content does not exist.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
<key alias="invalidStartNode">The selected item is from the wrong folder.</key>
|
||||
<key alias="invalidObjectType">The selected item is of the wrong type.</key>
|
||||
<key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key>
|
||||
<key alias="multipleNotOneOfOptions">The values '%0%' are not found in the the available options.</key>
|
||||
<key alias="invalidColor">The selected color '%0%' is not one of the available options.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -12,6 +12,9 @@ public class PackageManifest
|
||||
|
||||
public bool AllowTelemetry { get; set; } = true;
|
||||
|
||||
[Obsolete("Use AllowTelemetry instead. This property will be removed in future versions.")]
|
||||
public bool AllowPackageTelemetry { get; set; } = true;
|
||||
|
||||
public required object[] Extensions { get; set; }
|
||||
|
||||
public PackageManifestImportmap? Importmap { get; set; }
|
||||
|
||||
@@ -11,7 +11,7 @@ public static class ContentBaseExtensions
|
||||
private static DefaultUrlSegmentProvider? _defaultUrlSegmentProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment for a specified content and culture.
|
||||
/// Gets a single URL segment for a specified content and culture.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="shortStringHelper"></param>
|
||||
@@ -19,29 +19,73 @@ public static class ContentBaseExtensions
|
||||
/// <param name="culture">The culture.</param>
|
||||
/// <param name="published">Whether to get the published or draft.</param>
|
||||
/// <returns>The URL segment.</returns>
|
||||
/// <remarks>
|
||||
/// If more than one URL segment provider is available, the first one that returns a non-null value will be returned.
|
||||
/// </remarks>
|
||||
public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable<IUrlSegmentProvider> urlSegmentProviders, string? culture = null, bool published = true)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
var urlSegment = GetUrlSegments(content, urlSegmentProviders, culture, published).FirstOrDefault();
|
||||
|
||||
if (urlSegmentProviders == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(urlSegmentProviders));
|
||||
}
|
||||
// Ensure we have at least the segment from the default URL provider returned.
|
||||
urlSegment ??= GetDefaultUrlSegment(shortStringHelper, content, culture, published);
|
||||
|
||||
var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, published, culture)).FirstOrDefault(u => u != null);
|
||||
if (url == null)
|
||||
return urlSegment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all URL segments for a specified content and culture.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="shortStringHelper"></param>
|
||||
/// <param name="urlSegmentProviders"></param>
|
||||
/// <param name="culture">The culture.</param>
|
||||
/// <param name="published">Whether to get the published or draft.</param>
|
||||
/// <returns>The collection of URL segments.</returns>
|
||||
public static IEnumerable<string> GetUrlSegments(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable<IUrlSegmentProvider> urlSegmentProviders, string? culture = null, bool published = true)
|
||||
{
|
||||
var urlSegments = GetUrlSegments(content, urlSegmentProviders, culture, published).Distinct().ToList();
|
||||
|
||||
// Ensure we have at least the segment from the default URL provider returned.
|
||||
if (urlSegments.Count == 0)
|
||||
{
|
||||
if (_defaultUrlSegmentProvider == null)
|
||||
var defaultUrlSegment = GetDefaultUrlSegment(shortStringHelper, content, culture, published);
|
||||
if (defaultUrlSegment is not null)
|
||||
{
|
||||
_defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper);
|
||||
urlSegments.Add(defaultUrlSegment);
|
||||
}
|
||||
|
||||
url = _defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe
|
||||
}
|
||||
|
||||
return url;
|
||||
return urlSegments;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetUrlSegments(
|
||||
IContentBase content,
|
||||
IEnumerable<IUrlSegmentProvider> urlSegmentProviders,
|
||||
string? culture,
|
||||
bool published)
|
||||
{
|
||||
foreach (IUrlSegmentProvider urlSegmentProvider in urlSegmentProviders)
|
||||
{
|
||||
var segment = urlSegmentProvider.GetUrlSegment(content, published, culture);
|
||||
if (string.IsNullOrEmpty(segment) == false)
|
||||
{
|
||||
yield return segment;
|
||||
|
||||
if (urlSegmentProvider.AllowAdditionalSegments is false)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetDefaultUrlSegment(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IContentBase content,
|
||||
string? culture,
|
||||
bool published)
|
||||
{
|
||||
_defaultUrlSegmentProvider ??= new DefaultUrlSegmentProvider(shortStringHelper);
|
||||
return _defaultUrlSegmentProvider.GetUrlSegment(content, published, culture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ public class ContentPropertySettings
|
||||
public ISet<string> ReservedFieldNames => _reservedFieldNames;
|
||||
|
||||
public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name);
|
||||
|
||||
public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names);
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ public class MediaPropertySettings
|
||||
public ISet<string> ReservedFieldNames => _reservedFieldNames;
|
||||
|
||||
public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name);
|
||||
|
||||
public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names);
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ public class MemberPropertySettings
|
||||
public ISet<string> ReservedFieldNames => _reservedFieldNames;
|
||||
|
||||
public bool AddReservedFieldName(string name) => _reservedFieldNames.Add(name);
|
||||
|
||||
public void AddReservedFieldNames(ISet<string> names) => _reservedFieldNames.UnionWith(names);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
namespace Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
public interface IPublishedMember : IPublishedContent
|
||||
{
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
namespace Umbraco.Cms.Core.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a URL segment for a published document.
|
||||
/// </summary>
|
||||
public class PublishedDocumentUrlSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the document key.
|
||||
/// </summary>
|
||||
public required Guid DocumentKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language Id.
|
||||
/// </summary>
|
||||
public required int LanguageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL segment string.
|
||||
/// </summary>
|
||||
public required string UrlSegment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the URL segment is for a draft.
|
||||
/// </summary>
|
||||
public required bool IsDraft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the URL segment is the primary one (first resolved from the collection of URL providers).
|
||||
/// </summary>
|
||||
public required bool IsPrimary { get; set; }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig
|
||||
[ConfigurationField("maxNumber")]
|
||||
public int MaxNumber { get; set; }
|
||||
|
||||
[ConfigurationField("filter")]
|
||||
public string? Filter { get; set; }
|
||||
|
||||
[ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes)]
|
||||
public bool IgnoreUserStartNodes { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
[Obsolete("This is no longer used and has been migrated to an internal class within RadioButtonsPropertyEditor. Scheduled for removal in Umbraco 17.")]
|
||||
public class RadioValueEditor : DataValueEditor
|
||||
{
|
||||
public RadioValueEditor(
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -12,23 +17,27 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
/// </summary>
|
||||
public class TextOnlyValueEditor : DataValueEditor
|
||||
{
|
||||
[Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")]
|
||||
public TextOnlyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper)
|
||||
: this(attribute, shortStringHelper, jsonSerializer, ioHelper)
|
||||
{
|
||||
}
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute) =>
|
||||
Validators.Add(new LengthValidator(localizedTextService));
|
||||
|
||||
[Obsolete($"Use the constructor that accepts {nameof(ILocalizedTextService)}. Will be removed in V16.")]
|
||||
public TextOnlyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
: this(
|
||||
attribute,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>(),
|
||||
shortStringHelper,
|
||||
jsonSerializer,
|
||||
ioHelper)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -65,4 +74,48 @@ public class TextOnlyValueEditor : DataValueEditor
|
||||
" can only be used with string based property editors");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A common length validator for both textbox and text area.
|
||||
/// </summary>
|
||||
internal class LengthValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public LengthValidator(ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
int? maxCharacters = dataTypeConfiguration switch
|
||||
{
|
||||
TextAreaConfiguration areaConfiguration => areaConfiguration.MaxChars,
|
||||
TextboxConfiguration textboxConfiguration => textboxConfiguration.MaxChars,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (maxCharacters is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (value is string typedValue && typedValue.Length > maxCharacters)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"stringLengthExceeded",
|
||||
[maxCharacters.ToString(), (typedValue.Length - maxCharacters).ToString() ]),
|
||||
["value'"])
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,22 @@ public class TypedJsonValidatorRunner<TValue, TConfiguration> : IValueValidator
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ITypedJsonValidator<TValue, TConfiguration>[] _validators;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypedJsonValidatorRunner{TValue, TConfiguration}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <param name="validators">The collection of validators to run.</param>
|
||||
public TypedJsonValidatorRunner(IJsonSerializer jsonSerializer, params ITypedJsonValidator<TValue, TConfiguration>[] validators)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_validators = validators;
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration,
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
object? value,
|
||||
string? valueType,
|
||||
object? dataTypeConfiguration,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
@@ -36,7 +45,8 @@ public class TypedJsonValidatorRunner<TValue, TConfiguration> : IValueValidator
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (value is null || _jsonSerializer.TryDeserialize(value, out TValue? deserializedValue) is false)
|
||||
TValue? deserializedValue = null;
|
||||
if (value is not null && _jsonSerializer.TryDeserialize(value, out deserializedValue) is false)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
|
||||
/// <summary>
|
||||
@@ -26,4 +28,49 @@ public static class ValidationHelper
|
||||
|
||||
return (value - min) % step == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all provided entities has the start node as an ancestor.
|
||||
/// </summary>
|
||||
/// <param name="entityKeys">Keys to check.</param>
|
||||
/// <param name="startNode">The configured start node.</param>
|
||||
/// <param name="navigationQueryService">The navigation query service to use for the checks.</param>
|
||||
/// <returns>True if the startnode key is in the ancestry tree.</returns>
|
||||
public static bool HasValidStartNode(IEnumerable<Guid> entityKeys, Guid startNode, INavigationQueryService navigationQueryService)
|
||||
{
|
||||
List<Guid> uniqueParentKeys = [];
|
||||
foreach (Guid distinctMediaKey in entityKeys.Distinct())
|
||||
{
|
||||
if (navigationQueryService.TryGetParentKey(distinctMediaKey, out Guid? parentKey) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a start node, the media must have a parent.
|
||||
if (parentKey is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uniqueParentKeys.Add(parentKey.Value);
|
||||
}
|
||||
|
||||
IEnumerable<Guid> parentKeysNotInStartNode = uniqueParentKeys.Where(x => x != startNode);
|
||||
foreach (Guid parentKey in parentKeysNotInStartNode)
|
||||
{
|
||||
if (navigationQueryService.TryGetAncestorsKeys(parentKey, out IEnumerable<Guid> foundAncestorKeys) is false)
|
||||
{
|
||||
// We couldn't find the parent node, so we fail.
|
||||
return false;
|
||||
}
|
||||
|
||||
Guid[] ancestorKeys = foundAncestorKeys.ToArray();
|
||||
if (ancestorKeys.Length == 0 || ancestorKeys.Contains(startNode) is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// A validator that validates an email address
|
||||
/// A validator that validates an email address.
|
||||
/// </summary>
|
||||
public sealed class EmailValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailValidator"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public EmailValidator()
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailValidator"/> class.
|
||||
/// </summary>
|
||||
public EmailValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
var asString = value == null ? string.Empty : value.ToString();
|
||||
var valueAsString = value?.ToString() ?? string.Empty;
|
||||
|
||||
var emailVal = new EmailAddressAttribute();
|
||||
var emailAddressAttribute = new EmailAddressAttribute();
|
||||
|
||||
if (asString != string.Empty && emailVal.IsValid(asString) == false)
|
||||
if (valueAsString != string.Empty && emailAddressAttribute.IsValid(valueAsString) == false)
|
||||
{
|
||||
// TODO: localize these!
|
||||
yield return new ValidationResult("Email is invalid", new[] { "value" });
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidEmail", [valueAsString]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// A value validator for property editors that handle multiple values from a configured list of options.
|
||||
/// </summary>
|
||||
public class MultipleValueValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleValueValidator"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor that takes all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public MultipleValueValidator()
|
||||
: this(StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleValueValidator"/> class.
|
||||
/// </summary>
|
||||
public MultipleValueValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
// don't validate if empty
|
||||
@@ -24,13 +47,21 @@ public class MultipleValueValidator : IValueValidator
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var selectedValue in values)
|
||||
var invalidValues = values
|
||||
.Where(x => valueListConfiguration.Items.Contains(x) is false)
|
||||
.ToList();
|
||||
|
||||
if (invalidValues.Count == 1)
|
||||
{
|
||||
if (valueListConfiguration.Items.Contains(selectedValue) is false)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
$"The value {selectedValue} is not a part of the pre-values", ["items"]);
|
||||
}
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "notOneOfOptions", [invalidValues[0]]),
|
||||
["value"]);
|
||||
}
|
||||
else if (invalidValues.Count > 1)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "multipleNotOneOfOptions", [string.Join(", ", invalidValues)]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
[Obsolete("This is no longer used and has been migrated to an internal class within RadioButtonsPropertyEditor. Scheduled for removal in Umbraco 17.")]
|
||||
public class RadioValueValidator : IValueValidator
|
||||
{
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration,
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
namespace Umbraco.Cms.Core.PublishedCache;
|
||||
namespace Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
/// <summary>
|
||||
/// Defines operations for rebuild of the published content cache in the database.
|
||||
/// </summary>
|
||||
public interface IDatabaseCacheRebuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the database cache is in the process of being rebuilt.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IsRebuilding() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the database cache.
|
||||
/// </summary>
|
||||
[Obsolete("Use the overload with the useBackgroundThread parameter. Scheduled for removal in Umbraco 17.")]
|
||||
void Rebuild();
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the database cache, optionally using a background thread.
|
||||
/// </summary>
|
||||
/// <param name="useBackgroundThread">Flag indicating whether to use a background thread for the operation and immediately return to the caller.</param>
|
||||
void Rebuild(bool useBackgroundThread)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> Rebuild();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <summary>
|
||||
/// Rebuids the database cache if the configured serializer has changed.
|
||||
/// </summary>
|
||||
void RebuildDatabaseCacheIfSerializerChanged();
|
||||
}
|
||||
|
||||
@@ -1267,7 +1267,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
// we need to guard against unsaved changes before proceeding; the content will be saved, but we're not firing any saved notifications
|
||||
if (HasUnsavedChanges(content))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, EventMessagesFactory.Get(), content);
|
||||
return new PublishResult(PublishResultType.FailedPublishUnsavedChanges, evtMsgs, content);
|
||||
}
|
||||
|
||||
if (content.Name != null && content.Name.Length > 255)
|
||||
@@ -1324,13 +1324,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
// Change state to publishing
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
var publishingNotification = new ContentPublishingNotification(content, evtMsgs);
|
||||
if (scope.Notifications.PublishCancelable(publishingNotification))
|
||||
{
|
||||
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
|
||||
}
|
||||
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, publishingNotification.State, userId);
|
||||
PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, new Dictionary<string, object?>(), userId);
|
||||
scope.Complete();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
@@ -16,6 +15,7 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
|
||||
{
|
||||
private readonly ITemplateService _templateService;
|
||||
private readonly IElementSwitchValidator _elementSwitchValidator;
|
||||
private readonly IReservedFieldNamesService _reservedFieldNamesService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public ContentTypeEditingService(
|
||||
@@ -24,12 +24,33 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IElementSwitchValidator elementSwitchValidator)
|
||||
IElementSwitchValidator elementSwitchValidator,
|
||||
IReservedFieldNamesService reservedFieldNamesService)
|
||||
: base(contentTypeService, contentTypeService, dataTypeService, entityService, shortStringHelper)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
_templateService = templateService;
|
||||
_elementSwitchValidator = elementSwitchValidator;
|
||||
_reservedFieldNamesService = reservedFieldNamesService;
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that is not marked obsolete, will be removed in v17")]
|
||||
public ContentTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
ITemplateService templateService,
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IElementSwitchValidator elementSwitchValidator)
|
||||
: this(
|
||||
contentTypeService,
|
||||
templateService,
|
||||
dataTypeService,
|
||||
entityService,
|
||||
shortStringHelper,
|
||||
elementSwitchValidator,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>())
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Use the constructor that is not marked obsolete, will be removed in v16")]
|
||||
@@ -39,11 +60,15 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper)
|
||||
: base(contentTypeService, contentTypeService, dataTypeService, entityService, shortStringHelper)
|
||||
: this(
|
||||
contentTypeService,
|
||||
templateService,
|
||||
dataTypeService,
|
||||
entityService,
|
||||
shortStringHelper,
|
||||
StaticServiceProvider.Instance.GetRequiredService<IElementSwitchValidator>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>())
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
_templateService = templateService;
|
||||
_elementSwitchValidator = StaticServiceProvider.Instance.GetRequiredService<IElementSwitchValidator>();
|
||||
}
|
||||
|
||||
public async Task<Attempt<IContentType?, ContentTypeOperationStatus>> CreateAsync(ContentTypeCreateModel model, Guid userKey)
|
||||
@@ -184,4 +209,6 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
|
||||
protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.DocumentType;
|
||||
|
||||
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.DocumentTypeContainer;
|
||||
|
||||
protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetDocumentReservedFieldNames();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
@@ -18,7 +17,6 @@ internal abstract class ContentTypeEditingServiceBase<TContentType, TContentType
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
protected ContentTypeEditingServiceBase(
|
||||
IContentTypeService contentTypeService,
|
||||
TContentTypeService concreteContentTypeService,
|
||||
@@ -91,7 +89,12 @@ internal abstract class ContentTypeEditingServiceBase<TContentType, TContentType
|
||||
return Attempt.FailWithStatus<TContentType?, ContentTypeOperationStatus>(operationStatus, null);
|
||||
}
|
||||
|
||||
await AdditionalCreateValidationAsync(model);
|
||||
// perform additional, content type specific validation
|
||||
operationStatus = await AdditionalCreateValidationAsync(model);
|
||||
if (operationStatus is not ContentTypeOperationStatus.Success)
|
||||
{
|
||||
return Attempt.FailWithStatus<TContentType?, ContentTypeOperationStatus>(operationStatus, null);
|
||||
}
|
||||
|
||||
// get the ID of the parent to create the content type under (we already validated that it exists)
|
||||
var parentId = GetParentId(model, containerKey) ?? throw new ArgumentException("Parent ID could not be found", nameof(model));
|
||||
@@ -414,13 +417,13 @@ internal abstract class ContentTypeEditingServiceBase<TContentType, TContentType
|
||||
return reservedAliases.InvariantContains(alias);
|
||||
}
|
||||
|
||||
protected abstract ISet<string> GetReservedFieldNames();
|
||||
|
||||
private bool ContainsReservedPropertyTypeAlias(ContentTypeEditingModelBase<TPropertyTypeModel, TPropertyTypeContainer> model)
|
||||
{
|
||||
// Because of models builder you cannot have an alias that already exists in IPublishedContent, for instance Path.
|
||||
// Since MyModel.Path would conflict with IPublishedContent.Path.
|
||||
var reservedPropertyTypeNames = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name)
|
||||
.Union(typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name))
|
||||
.ToArray();
|
||||
ISet<string> reservedPropertyTypeNames = GetReservedFieldNames();
|
||||
|
||||
return model.Properties.Any(propertyType => reservedPropertyTypeNames.InvariantContains(propertyType.Alias));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -13,7 +15,25 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
private readonly IReservedFieldNamesService _reservedFieldNamesService;
|
||||
|
||||
public MediaTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
IMediaTypeService mediaTypeService,
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IImageUrlGenerator imageUrlGenerator,
|
||||
IReservedFieldNamesService reservedFieldNamesService)
|
||||
: base(contentTypeService, mediaTypeService, dataTypeService, entityService, shortStringHelper)
|
||||
{
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_dataTypeService = dataTypeService;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
_reservedFieldNamesService = reservedFieldNamesService;
|
||||
}
|
||||
|
||||
[Obsolete("Use the non obsolete constructor instead. Scheduled for removal in v16")]
|
||||
public MediaTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
IMediaTypeService mediaTypeService,
|
||||
@@ -26,6 +46,7 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_dataTypeService = dataTypeService;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
_reservedFieldNamesService = StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>();
|
||||
}
|
||||
|
||||
public async Task<Attempt<IMediaType?, ContentTypeOperationStatus>> CreateAsync(MediaTypeCreateModel model, Guid userKey)
|
||||
@@ -145,6 +166,8 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase<IM
|
||||
|
||||
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MediaTypeContainer;
|
||||
|
||||
protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMediaReservedFieldNames();
|
||||
|
||||
private async Task<IDictionary<IMediaType, IEnumerable<string>>> FetchAllowedFileExtensionsByMediaTypeAsync(IEnumerable<IMediaType> mediaTypes)
|
||||
{
|
||||
var allowedFileExtensionsByMediaType = new Dictionary<IMediaType, IEnumerable<string>>();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
@@ -10,7 +12,24 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IReservedFieldNamesService _reservedFieldNamesService;
|
||||
|
||||
public MemberTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IDataTypeService dataTypeService,
|
||||
IEntityService entityService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IUserService userService,
|
||||
IReservedFieldNamesService reservedFieldNamesService)
|
||||
: base(contentTypeService, memberTypeService, dataTypeService, entityService, shortStringHelper)
|
||||
{
|
||||
_memberTypeService = memberTypeService;
|
||||
_userService = userService;
|
||||
_reservedFieldNamesService = reservedFieldNamesService;
|
||||
}
|
||||
|
||||
[Obsolete("Use the non obsolete constructor instead. Scheduled for removal in v16")]
|
||||
public MemberTypeEditingService(
|
||||
IContentTypeService contentTypeService,
|
||||
IMemberTypeService memberTypeService,
|
||||
@@ -22,6 +41,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
|
||||
{
|
||||
_memberTypeService = memberTypeService;
|
||||
_userService = userService;
|
||||
_reservedFieldNamesService = StaticServiceProvider.Instance.GetRequiredService<IReservedFieldNamesService>();
|
||||
}
|
||||
|
||||
public async Task<Attempt<IMemberType?, ContentTypeOperationStatus>> CreateAsync(MemberTypeCreateModel model, Guid userKey)
|
||||
@@ -79,6 +99,8 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
|
||||
|
||||
protected override UmbracoObjectTypes ContainerObjectType => throw new NotSupportedException("Member type tree does not support containers");
|
||||
|
||||
protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMemberReservedFieldNames();
|
||||
|
||||
private void UpdatePropertyTypeVisibility(IMemberType memberType, MemberTypeModelBase model)
|
||||
{
|
||||
foreach (MemberTypePropertyTypeModel propertyType in model.Properties)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
@@ -147,6 +148,16 @@ public class ContentTypeService : ContentTypeServiceBase<IContentTypeRepository,
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IContentType>> GetByQueryAsync(IQuery<IContentType> query, CancellationToken cancellationToken)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
// that one is special because it works across content, media and member types
|
||||
scope.ReadLock(Constants.Locks.ContentTypes);
|
||||
IEnumerable<IContentType> contentTypes = Repository.Get(query);
|
||||
scope.Complete();
|
||||
return contentTypes;
|
||||
}
|
||||
|
||||
protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
|
||||
@@ -1183,8 +1183,13 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
|
||||
return pagedModel;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take)
|
||||
=> await GetAllowedChildrenAsync(key, null, skip, take);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, Guid? parentContentKey, int skip, int take)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
TItem? parent = Get(key);
|
||||
@@ -1197,7 +1202,7 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
|
||||
IEnumerable<ContentTypeSort> allowedContentTypes = parent.AllowedContentTypes;
|
||||
foreach (IContentTypeFilter filter in _contentTypeFilters)
|
||||
{
|
||||
allowedContentTypes = await filter.FilterAllowedChildrenAsync(allowedContentTypes, key);
|
||||
allowedContentTypes = await filter.FilterAllowedChildrenAsync(allowedContentTypes, key, parentContentKey);
|
||||
}
|
||||
|
||||
PagedModel<TItem> result;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,13 +13,28 @@ public interface IContentTypeFilter
|
||||
/// <param name="contentTypes">Retrieved collection of content types.</param>
|
||||
/// <returns>Filtered collection of content types.</returns>
|
||||
Task<IEnumerable<TItem>> FilterAllowedAtRootAsync<TItem>(IEnumerable<TItem> contentTypes)
|
||||
where TItem : IContentTypeComposition;
|
||||
where TItem : IContentTypeComposition
|
||||
=> Task.FromResult(contentTypes);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the content types retrieved for being allowed as children of a parent content type.
|
||||
/// </summary>
|
||||
/// <param name="contentTypes">Retrieved collection of content types.</param>
|
||||
/// <param name="parentKey">The parent content type key.</param>
|
||||
/// <param name="parentKey">The parent content key.</param>
|
||||
/// <returns>Filtered collection of content types.</returns>
|
||||
Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentKey);
|
||||
[Obsolete("Please use the method overload taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentKey)
|
||||
=> Task.FromResult(contentTypes);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the content types retrieved for being allowed as children of a parent content type.
|
||||
/// </summary>
|
||||
/// <param name="contentTypes">Retrieved collection of content types.</param>
|
||||
/// <param name="parentContentTypeKey">The parent content type key.</param>
|
||||
/// <param name="parentContentKey">The parent content key (provided to allow for custom filtering of the returned list of children based on the content context).</param>
|
||||
/// <returns>Filtered collection of content types.</returns>
|
||||
Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentContentTypeKey, Guid? parentContentKey)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
=> FilterAllowedChildrenAsync(contentTypes, parentContentTypeKey);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface IContentTypeSearchService
|
||||
{
|
||||
Task<PagedModel<IContentType>> SearchAsync(string query, bool? isElement, CancellationToken cancellationToken, int skip = 0, int take = 100);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
@@ -30,4 +31,6 @@ public interface IContentTypeService : IContentTypeBaseService<IContentType>
|
||||
/// <param name="aliases"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<int> GetAllContentTypeIds(string[] aliases);
|
||||
|
||||
Task<IEnumerable<IContentType>> GetByQueryAsync(IQuery<IContentType> query, CancellationToken cancellationToken) => Task.FromResult(Enumerable.Empty<IContentType>());
|
||||
}
|
||||
|
||||
@@ -159,12 +159,20 @@ public interface IContentTypeBaseService<TItem> : IContentTypeBaseService, IServ
|
||||
/// <summary>
|
||||
/// Returns all the content type allowed as root.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<PagedModel<TItem>> GetAllAllowedAsRootAsync(int skip, int take);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content types allowed as children for a given content type key.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <param name="key">The content type key.</param>
|
||||
Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all content types allowed as children for a given content type key.
|
||||
/// </summary>
|
||||
/// <param name="key">The content type key.</param>
|
||||
/// <param name="parentContentKey">The parent content key.</param>
|
||||
Task<Attempt<PagedModel<TItem>?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, Guid? parentContentKey, int skip, int take)
|
||||
=> GetAllowedChildrenAsync(key, skip, take);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +1,97 @@
|
||||
using Umbraco.Cms.Core.Media.EmbedProviders;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines operations for handling document URLs.
|
||||
/// </summary>
|
||||
public interface IDocumentUrlService
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the service and ensure the content in the database is correct with the current configuration.
|
||||
/// </summary>
|
||||
/// <param name="forceEmpty"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="forceEmpty">Forces an early return when we know there are no routes (i.e. on install).</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task InitAsync(bool forceEmpty, CancellationToken cancellationToken);
|
||||
|
||||
Task RebuildAllUrlsAsync();
|
||||
/// <summary>
|
||||
/// Gets the Url from a document key, culture and segment. Preview urls are returned if isPreview is true.
|
||||
/// Rebuilds all document URLs.
|
||||
/// </summary>
|
||||
Task RebuildAllUrlsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single URL segment from a document key and culture. Preview urls are returned if isDraft is true.
|
||||
/// </summary>
|
||||
/// <param name="documentKey">The key of the document.</param>
|
||||
/// <param name="culture">The culture code.</param>
|
||||
/// <param name="isDraft">Whether to get the url of the draft or published document.</param>
|
||||
/// <returns>The url of the document.</returns>
|
||||
/// <returns>A URL segment for the document.</returns>
|
||||
/// <remarks>If more than one segment is available, the first retrieved and indicated as primary will be returned.</remarks>
|
||||
string? GetUrlSegment(Guid documentKey, string culture, bool isDraft);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segments from a document key and culture. Preview urls are returned if isDraft is true.
|
||||
/// </summary>
|
||||
/// <param name="documentKey">The key of the document.</param>
|
||||
/// <param name="culture">The culture code.</param>
|
||||
/// <param name="isDraft">Whether to get the url of the draft or published document.</param>
|
||||
/// <returns>The URL segments for the document.</returns>
|
||||
IEnumerable<string> GetUrlSegments(Guid documentKey, string culture, bool isDraft)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates the URL segments for a single document.
|
||||
/// </summary>
|
||||
/// <param name="key">The document key.</param>
|
||||
Task CreateOrUpdateUrlSegmentsAsync(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates the URL segments for a document and it's descendants.
|
||||
/// </summary>
|
||||
/// <param name="key">The document key.</param>
|
||||
Task CreateOrUpdateUrlSegmentsWithDescendantsAsync(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates the URL segments for a collection of documents.
|
||||
/// </summary>
|
||||
/// <param name="documents">The document collection.</param>
|
||||
Task CreateOrUpdateUrlSegmentsAsync(IEnumerable<IContent> documents);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all URLs from the cache for a collection of document keys.
|
||||
/// </summary>
|
||||
/// <param name="documentKeys">The collection of document keys.</param>
|
||||
Task DeleteUrlsFromCacheAsync(IEnumerable<Guid> documentKeys);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a document key by route.
|
||||
/// </summary>
|
||||
/// <param name="route">The route.</param>
|
||||
/// <param name="culture">The culture code.</param>
|
||||
/// <param name="documentStartNodeId">The document start node Id.</param>
|
||||
/// <param name="isDraft">Whether to get the url of the draft or published document.</param>
|
||||
/// <returns>The document key, or null if not found.</returns>
|
||||
Guid? GetDocumentKeyByRoute(string route, string? culture, int? documentStartNodeId, bool isDraft);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the URLs for a given content key.
|
||||
/// </summary>
|
||||
/// <param name="contentKey">The content key.</param>
|
||||
[Obsolete("This method is obsolete and will be removed in future versions. Use IPublishedUrlInfoProvider.GetAllAsync instead. Scheduled for removal in Umbraco 17.")]
|
||||
Task<IEnumerable<UrlInfo>> ListUrlsAsync(Guid contentKey);
|
||||
Task CreateOrUpdateUrlSegmentsWithDescendantsAsync(Guid key);
|
||||
Task CreateOrUpdateUrlSegmentsAsync(Guid key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the legacy route format for a document key and culture.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the document.</param>
|
||||
/// <param name="culture">The culture code.</param>
|
||||
/// <param name="isDraft">Whether to get the url of the draft or published document.</param>
|
||||
/// <returns></returns>
|
||||
string GetLegacyRouteFormat(Guid key, string? culture, bool isDraft);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any URLs have been cached.
|
||||
/// </summary>
|
||||
bool HasAny();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,19 @@ public interface IIndexedEntitySearchService
|
||||
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
|
||||
|
||||
// default implementation to avoid breaking changes falls back to old behaviour
|
||||
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
|
||||
PagedModel<IEntitySlim> Search(UmbracoObjectTypes objectType, string query, Guid? parentId, IEnumerable<Guid>? contentTypeIds, int skip = 0, int take = 100, bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
|
||||
|
||||
// default implementation to avoid breaking changes falls back to old behaviour
|
||||
PagedModel<IEntitySlim> Search(
|
||||
UmbracoObjectTypes objectType,
|
||||
string query,
|
||||
Guid? parentId,
|
||||
IEnumerable<Guid>? contentTypeIds,
|
||||
bool? trashed,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType,query, skip, take, ignoreUserStartNodes);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public interface ITwoFactorLoginService : IService
|
||||
/// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
|
||||
/// the provider.
|
||||
/// </remarks>
|
||||
[Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoWithStatusAsync. This will be removed in Umbraco 15.")]
|
||||
[Obsolete("Use IUserTwoFactorLoginService.GetSetupInfoAsync. This will be removed in Umbraco 15.")]
|
||||
Task<object?> GetSetupInfoAsync(Guid userOrMemberKey, string providerName);
|
||||
|
||||
/// <summary>
|
||||
@@ -60,13 +60,13 @@ public interface ITwoFactorLoginService : IService
|
||||
/// <summary>
|
||||
/// Disables 2FA with Code.
|
||||
/// </summary>
|
||||
[Obsolete("Use IUserTwoFactorLoginService.DisableByCodeWithStatusAsync. This will be removed in Umbraco 15.")]
|
||||
[Obsolete("Use IUserTwoFactorLoginService.DisableByCodeAsync. This will be removed in Umbraco 15.")]
|
||||
Task<bool> DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
|
||||
|
||||
/// <summary>
|
||||
/// Validates and Saves.
|
||||
/// </summary>
|
||||
[Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveWithStatusAsync. This will be removed in Umbraco 15.")]
|
||||
[Obsolete("Use IUserTwoFactorLoginService.ValidateAndSaveAsync. This will be removed in Umbraco 15.")]
|
||||
Task<bool> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
@@ -88,7 +88,7 @@ public class PropertyValidationService : IPropertyValidationService
|
||||
{
|
||||
// Retrieve default messages used for required and regex validatation. We'll replace these
|
||||
// if set with custom ones if they've been provided for a given property.
|
||||
var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing };
|
||||
var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing, Constants.Validation.ErrorMessages.Properties.Empty };
|
||||
var formatDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.PatternMismatch };
|
||||
|
||||
IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType);
|
||||
|
||||
@@ -2680,7 +2680,7 @@ internal partial class UserService : RepositoryService, IUserService
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[\w\d\-\._~]{1,255}$")]
|
||||
[GeneratedRegex(@"^[\w\d\-\._~]{1,100}$")]
|
||||
private static partial Regex ValidClientId();
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -8,6 +8,16 @@ namespace Umbraco.Cms.Core.Strings;
|
||||
/// <remarks>Url segments should comply with IETF RFCs regarding content, encoding, etc.</remarks>
|
||||
public interface IUrlSegmentProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the URL segment provider allows additional segments after providing one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set to true, when more than one URL segment provider is available, futher providers after this one in the collection will be called
|
||||
/// even if the current provider provides a segment.
|
||||
/// If false, the provider will terminate the chain of URL segment providers if it provides a segment.
|
||||
/// </remarks>
|
||||
bool AllowAdditionalSegments => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment for a specified content and culture.
|
||||
/// </summary>
|
||||
@@ -20,6 +30,18 @@ public interface IUrlSegmentProvider
|
||||
/// URL per culture.
|
||||
/// </remarks>
|
||||
string? GetUrlSegment(IContentBase content, string? culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment for a specified content, published status and and culture.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="culture">The culture.</param>
|
||||
/// <returns>The URL segment.</returns>
|
||||
/// <remarks>
|
||||
/// This is for when Umbraco is capable of managing more than one URL
|
||||
/// per content, in 1-to-1 multilingual configurations. Then there would be one
|
||||
/// URL per culture.
|
||||
/// </remarks>
|
||||
string? GetUrlSegment(IContentBase content, bool published, string? culture = null) => GetUrlSegment(content, culture);
|
||||
|
||||
// TODO: For the 301 tracking, we need to add another extended interface to this so that
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -61,7 +62,19 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
out long totalFound,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);
|
||||
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, null, searchFrom, ignoreUserStartNodes);
|
||||
|
||||
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
|
||||
public IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
UmbracoEntityTypes entityType,
|
||||
int pageSize,
|
||||
long pageIndex,
|
||||
out long totalFound,
|
||||
string[]? contentTypeAliases,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(query, entityType, pageSize, pageIndex, out totalFound, contentTypeAliases, null, searchFrom, ignoreUserStartNodes);
|
||||
|
||||
public IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
@@ -70,6 +83,7 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
long pageIndex,
|
||||
out long totalFound,
|
||||
string[]? contentTypeAliases,
|
||||
bool? trashed,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
{
|
||||
@@ -133,6 +147,12 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches)
|
||||
: Array.Empty<int>();
|
||||
AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
|
||||
|
||||
if (trashed.HasValue)
|
||||
{
|
||||
AppendRequiredTrashPath(trashed.Value, sb, Constants.System.RecycleBinMedia);
|
||||
}
|
||||
|
||||
break;
|
||||
case UmbracoEntityTypes.Document:
|
||||
type = "content";
|
||||
@@ -146,6 +166,12 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches)
|
||||
: Array.Empty<int>();
|
||||
AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
|
||||
|
||||
if (trashed.HasValue)
|
||||
{
|
||||
AppendRequiredTrashPath(trashed.Value, sb, Constants.System.RecycleBinContent);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("The " + typeof(BackOfficeExamineSearcher) +
|
||||
@@ -181,6 +207,14 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AppendRequiredTrashPath(bool trashed, StringBuilder sb, int recycleBinId)
|
||||
{
|
||||
var requiredOrNotString = trashed ? "+" : "!";
|
||||
var trashPath = $"-1,{recycleBinId}";
|
||||
trashPath = trashPath.Replace("-", "\\-").Replace(",", "\\,");
|
||||
sb.Append($"{requiredOrNotString}__Path:{trashPath}\\,* ");
|
||||
}
|
||||
|
||||
private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List<string> fields, string type)
|
||||
{
|
||||
//build a lucene query:
|
||||
@@ -394,6 +428,7 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher
|
||||
searchFromId > 0
|
||||
? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault()
|
||||
: null;
|
||||
|
||||
if (entityPath != null)
|
||||
{
|
||||
// find... only what's underneath
|
||||
|
||||
@@ -78,6 +78,7 @@ public static partial class UmbracoBuilderExtensions
|
||||
builder.Services.AddUnique<IContentListViewService, ContentListViewService>();
|
||||
builder.Services.AddUnique<IMediaListViewService, MediaListViewService>();
|
||||
builder.Services.AddUnique<IEntitySearchService, EntitySearchService>();
|
||||
builder.Services.AddUnique<IContentTypeSearchService, ContentTypeSearchService>();
|
||||
builder.Services.AddUnique<IIndexedEntitySearchService, IndexedEntitySearchService>();
|
||||
builder.Services.TryAddTransient<IReservedFieldNamesService, ReservedFieldNamesService>();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public interface IBackOfficeExamineSearcher
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false);
|
||||
|
||||
// default implementation to avoid breaking changes falls back to old behaviour
|
||||
[Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")]
|
||||
IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
UmbracoEntityTypes entityType,
|
||||
@@ -28,5 +28,18 @@ public interface IBackOfficeExamineSearcher
|
||||
string[]? contentTypeAliases,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(query, entityType, pageSize, pageIndex, out totalFound, searchFrom, ignoreUserStartNodes);
|
||||
|
||||
// default implementation to avoid breaking changes falls back to old behaviour
|
||||
IEnumerable<ISearchResult> Search(
|
||||
string query,
|
||||
UmbracoEntityTypes entityType,
|
||||
int pageSize,
|
||||
long pageIndex,
|
||||
out long totalFound,
|
||||
string[]? contentTypeAliases,
|
||||
bool? trashed,
|
||||
string? searchFrom = null,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(query, entityType, pageSize, pageIndex, out totalFound, null, searchFrom, ignoreUserStartNodes);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ public abstract class MigrationExpressionBase : IMigrationExpression
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stmtBuilder.Length > 0)
|
||||
{
|
||||
stmtBuilder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
stmtBuilder.Append(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
|
||||
{
|
||||
_appCaches.RuntimeCache.Clear();
|
||||
_appCaches.IsolatedCaches.ClearAllCaches();
|
||||
_databaseCacheRebuilder.Rebuild();
|
||||
_databaseCacheRebuilder.Rebuild(false);
|
||||
_distributedCache.RefreshAllPublishedSnapshot();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations;
|
||||
/// <summary>
|
||||
/// Implements <see cref="ICacheRebuilder" /> in Umbraco.Web (rebuilding).
|
||||
/// </summary>
|
||||
[Obsolete("This is no longer used. Scheduled for removal in Umbraco 17.")]
|
||||
public class CacheRebuilder : ICacheRebuilder
|
||||
{
|
||||
private readonly DistributedCache _distributedCache;
|
||||
@@ -26,7 +27,7 @@ public class CacheRebuilder : ICacheRebuilder
|
||||
/// <inheritdoc />
|
||||
public void Rebuild()
|
||||
{
|
||||
_databaseCacheRebuilder.Rebuild();
|
||||
_databaseCacheRebuilder.Rebuild(false);
|
||||
_distributedCache.RefreshAllPublishedSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.PostMigrations;
|
||||
/// be refactored, really.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Obsolete("This is no longer used. Scheduled for removal in Umbraco 17.")]
|
||||
public interface ICacheRebuilder
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -111,5 +111,8 @@ public class UmbracoPlan : MigrationPlan
|
||||
|
||||
// To 15.3.0
|
||||
To<V_15_3_0.AddNameAndDescriptionToWebhooks>("{7B11F01E-EE33-4B0B-81A1-F78F834CA45B}");
|
||||
|
||||
// To 15.4.0
|
||||
To<V_15_4_0.UpdateDocumentUrlToPersistMultipleSegmentsPerDocument>("{A9E72794-4036-4563-B543-1717C73B8879}");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class AddNameAndDescriptionToWebhooks : MigrationBase
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"Table {Constants.DatabaseSchema.Tables.Webhook} does not exist so the addition of the name and description by columnss in migration {nameof(AddNameAndDescriptionToWebhooks)} cannot be completed.");
|
||||
Logger.LogWarning($"Table {Constants.DatabaseSchema.Tables.Webhook} does not exist so the addition of the name and description by columns in migration {nameof(AddNameAndDescriptionToWebhooks)} cannot be completed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_4_0;
|
||||
|
||||
/// <summary>
|
||||
/// Migration to make necessary schema updates to support multiple segments per document.
|
||||
/// </summary>
|
||||
public class UpdateDocumentUrlToPersistMultipleSegmentsPerDocument : MigrationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UpdateDocumentUrlToPersistMultipleSegmentsPerDocument"/> class.
|
||||
/// </summary>
|
||||
public UpdateDocumentUrlToPersistMultipleSegmentsPerDocument(IMigrationContext context)
|
||||
: base(context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Migrate()
|
||||
{
|
||||
Logger.LogDebug("Schema updates to {TableName} for support of multiple segments per document", Constants.DatabaseSchema.Tables.DocumentUrl);
|
||||
|
||||
if (TableExists(Constants.DatabaseSchema.Tables.DocumentUrl))
|
||||
{
|
||||
ExtendUniqueIndexAcrossSegmentField();
|
||||
AddAndPopulateIsPrimaryColumn();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Table {Constants.DatabaseSchema.Tables.DocumentUrl} does not exist so the migration {nameof(UpdateDocumentUrlToPersistMultipleSegmentsPerDocument)} could not be completed.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtendUniqueIndexAcrossSegmentField()
|
||||
{
|
||||
Logger.LogDebug("Extending the unique index on {TableName} to include the urlSegment column", Constants.DatabaseSchema.Tables.DocumentUrl);
|
||||
|
||||
var indexName = "IX_" + Constants.DatabaseSchema.Tables.DocumentUrl;
|
||||
if (IndexExists(indexName))
|
||||
{
|
||||
DeleteIndex<DocumentUrlDto>(indexName);
|
||||
}
|
||||
|
||||
CreateIndex<DocumentUrlDto>(indexName);
|
||||
}
|
||||
|
||||
private void AddAndPopulateIsPrimaryColumn()
|
||||
{
|
||||
const string IsPrimaryColumnName = "isPrimary";
|
||||
|
||||
Logger.LogDebug("Adding the {Column} column {TableName} to include the urlSegment column", IsPrimaryColumnName, Constants.DatabaseSchema.Tables.DocumentUrl);
|
||||
|
||||
var columns = Context.SqlContext.SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
|
||||
if (columns
|
||||
.SingleOrDefault(x => x.TableName == Constants.DatabaseSchema.Tables.DocumentUrl && x.ColumnName == IsPrimaryColumnName) is null)
|
||||
{
|
||||
AddColumn<DocumentUrlDto>(Constants.DatabaseSchema.Tables.DocumentUrl, IsPrimaryColumnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,15 @@ public class TextBuilder : Builder
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
public static void WriteHeader(StringBuilder sb) => TextHeaderWriter.WriteHeader(sb);
|
||||
[Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public static void WriteHeader(StringBuilder sb) => WriteHeader(sb, true);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
/// <param name="includeVersion">Flag indicating whether the tool version number should be included in the output.</param>
|
||||
public static void WriteHeader(StringBuilder sb, bool includeVersion) => TextHeaderWriter.WriteHeader(sb, includeVersion);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a generated model to a string builder.
|
||||
@@ -60,7 +68,7 @@ public class TextBuilder : Builder
|
||||
/// <param name="typeModel">The model to generate.</param>
|
||||
public void Generate(StringBuilder sb, TypeModel typeModel)
|
||||
{
|
||||
WriteHeader(sb);
|
||||
WriteHeader(sb, Config.IncludeVersionNumberInGeneratedModels);
|
||||
|
||||
foreach (var t in TypesUsing)
|
||||
{
|
||||
@@ -83,7 +91,7 @@ public class TextBuilder : Builder
|
||||
/// <param name="typeModels">The models to generate.</param>
|
||||
public void Generate(StringBuilder sb, IEnumerable<TypeModel> typeModels)
|
||||
{
|
||||
WriteHeader(sb);
|
||||
WriteHeader(sb, Config.IncludeVersionNumberInGeneratedModels);
|
||||
|
||||
foreach (var t in TypesUsing)
|
||||
{
|
||||
|
||||
@@ -8,13 +8,30 @@ internal static class TextHeaderWriter
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
public static void WriteHeader(StringBuilder sb)
|
||||
[Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public static void WriteHeader(StringBuilder sb) => WriteHeader(sb, true);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs an "auto-generated" header to a string builder.
|
||||
/// </summary>
|
||||
/// <param name="sb">The string builder.</param>
|
||||
/// <param name="includeVersion">Flag indicating whether the tool version number should be included in the output.</param>
|
||||
public static void WriteHeader(StringBuilder sb, bool includeVersion)
|
||||
{
|
||||
sb.Append("//------------------------------------------------------------------------------\n");
|
||||
sb.Append("// <auto-generated>\n");
|
||||
sb.Append("// This code was generated by a tool.\n");
|
||||
sb.Append("//\n");
|
||||
sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version);
|
||||
|
||||
if (includeVersion)
|
||||
{
|
||||
sb.AppendFormat("// Umbraco.ModelsBuilder.Embedded v{0}\n", ApiVersion.Current.Version);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("// Umbraco.ModelsBuilder.Embedded\n");
|
||||
}
|
||||
|
||||
sb.Append("//\n");
|
||||
sb.Append("// Changes to this file will be lost if the code is regenerated.\n");
|
||||
sb.Append("// </auto-generated>\n");
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Options;
|
||||
|
||||
public class ConfigurePropertySettingsOptions :
|
||||
IConfigureOptions<ContentPropertySettings>,
|
||||
IConfigureOptions<MemberPropertySettings>,
|
||||
IConfigureOptions<MediaPropertySettings>
|
||||
{
|
||||
|
||||
public void Configure(ContentPropertySettings options)
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
options.AddReservedFieldNames(reservedProperties);
|
||||
options.AddReservedFieldNames(reservedMethods);
|
||||
}
|
||||
|
||||
public void Configure(MemberPropertySettings options)
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedMember).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedMember).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
options.AddReservedFieldNames(reservedProperties);
|
||||
options.AddReservedFieldNames(reservedMethods);
|
||||
}
|
||||
|
||||
public void Configure(MediaPropertySettings options)
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
options.AddReservedFieldNames(reservedProperties);
|
||||
options.AddReservedFieldNames(reservedMethods);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class DocumentUrlDto
|
||||
[PrimaryKeyColumn(Clustered = false, AutoIncrement = true)]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Index(IndexTypes.UniqueClustered, ForColumns = "uniqueId, languageId, isDraft", Name = "IX_" + TableName)]
|
||||
[Index(IndexTypes.UniqueClustered, ForColumns = "uniqueId, languageId, isDraft, urlSegment", Name = "IX_" + TableName)]
|
||||
[Column("uniqueId")]
|
||||
[ForeignKey(typeof(NodeDto), Column = "uniqueId")]
|
||||
public Guid UniqueId { get; set; }
|
||||
@@ -28,14 +28,11 @@ public class DocumentUrlDto
|
||||
[ForeignKey(typeof(LanguageDto))]
|
||||
public int LanguageId { get; set; }
|
||||
|
||||
//
|
||||
// [Column("segment")]
|
||||
// [NullSetting(NullSetting = NullSettings.Null)]
|
||||
// [Length(PropertyDataDto.SegmentLength)]
|
||||
// public string Segment { get; set; } = string.Empty;
|
||||
|
||||
[Column("urlSegment")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string UrlSegment { get; set; } = string.Empty;
|
||||
|
||||
[Column("isPrimary")]
|
||||
[Constraint(Default = 1)]
|
||||
public bool IsPrimary { get; set; }
|
||||
}
|
||||
|
||||
+11
-29
@@ -1,18 +1,11 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Factories;
|
||||
using Umbraco.Cms.Infrastructure.Scoping;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
||||
|
||||
public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
@@ -36,14 +29,15 @@ public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
|
||||
public void Save(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
|
||||
{
|
||||
//TODO avoid this is called as first thing on first restart after install
|
||||
// TODO: avoid this is called as first thing on first restart after install
|
||||
IEnumerable<Guid> documentKeys = publishedDocumentUrlSegments.Select(x => x.DocumentKey).Distinct();
|
||||
|
||||
Dictionary<(Guid UniqueId, int LanguageId, bool isDraft), DocumentUrlDto> dtoDictionary = publishedDocumentUrlSegments.Select(BuildDto).ToDictionary(x=> (x.UniqueId, x.LanguageId, x.IsDraft));
|
||||
Dictionary<(Guid UniqueId, int LanguageId, bool isDraft, string urlSegment), DocumentUrlDto> dtoDictionary = publishedDocumentUrlSegments
|
||||
.Select(BuildDto)
|
||||
.ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft, x.UrlSegment));
|
||||
|
||||
var toUpdate = new List<DocumentUrlDto>();
|
||||
var toDelete = new List<int>();
|
||||
var toInsert = dtoDictionary.Values.ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft));
|
||||
var toInsert = dtoDictionary.Values.ToDictionary(x => (x.UniqueId, x.LanguageId, x.IsDraft, x.UrlSegment));
|
||||
|
||||
foreach (IEnumerable<Guid> group in documentKeys.InGroupsOf(Constants.Sql.MaxParameterCount))
|
||||
{
|
||||
@@ -57,18 +51,12 @@ public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
|
||||
foreach (DocumentUrlDto existing in existingUrlsInBatch)
|
||||
{
|
||||
|
||||
if (dtoDictionary.TryGetValue((existing.UniqueId, existing.LanguageId, existing.IsDraft), out DocumentUrlDto? found))
|
||||
if (dtoDictionary.TryGetValue((existing.UniqueId, existing.LanguageId, existing.IsDraft, existing.UrlSegment), out DocumentUrlDto? found))
|
||||
{
|
||||
found.NodeId = existing.NodeId;
|
||||
|
||||
// Only update if the url segment is different
|
||||
if (found.UrlSegment != existing.UrlSegment)
|
||||
{
|
||||
toUpdate.Add(found);
|
||||
}
|
||||
// if we found it, we know we should not insert it as a new
|
||||
toInsert.Remove((found.UniqueId, found.LanguageId, found.IsDraft));
|
||||
// If we found it, we know we should not insert it as a new record.
|
||||
toInsert.Remove((found.UniqueId, found.LanguageId, found.IsDraft, found.UrlSegment));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -83,14 +71,6 @@ public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
Database.DeleteMany<DocumentUrlDto>().Where(x => toDelete.Contains(x.NodeId)).Execute();
|
||||
}
|
||||
|
||||
if (toUpdate.Any())
|
||||
{
|
||||
foreach (DocumentUrlDto updated in toUpdate)
|
||||
{
|
||||
Database.Update(updated);
|
||||
}
|
||||
}
|
||||
|
||||
Database.InsertBulk(toInsert.Values);
|
||||
}
|
||||
|
||||
@@ -115,7 +95,8 @@ public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
UrlSegment = dto.UrlSegment,
|
||||
DocumentKey = dto.UniqueId,
|
||||
LanguageId = dto.LanguageId,
|
||||
IsDraft = dto.IsDraft
|
||||
IsDraft = dto.IsDraft,
|
||||
IsPrimary = dto.IsPrimary
|
||||
};
|
||||
|
||||
private DocumentUrlDto BuildDto(PublishedDocumentUrlSegment model)
|
||||
@@ -126,6 +107,7 @@ public class DocumentUrlRepository : IDocumentUrlRepository
|
||||
UniqueId = model.DocumentKey,
|
||||
LanguageId = model.LanguageId,
|
||||
IsDraft = model.IsDraft,
|
||||
IsPrimary = model.IsPrimary,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@@ -16,12 +16,22 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
where TValue : BlockValue<TLayout>, new()
|
||||
where TLayout : class, IBlockLayoutItem, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockEditorMinMaxValidatorBase{TValue, TLayout}"/> class.
|
||||
/// </summary>
|
||||
protected BlockEditorMinMaxValidatorBase(ILocalizedTextService textService) => TextService = textService;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ILocalizedTextService"/>
|
||||
/// </summary>
|
||||
protected ILocalizedTextService TextService { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the number of blocks are within the configured minimum and maximum values.
|
||||
/// </summary>
|
||||
protected IEnumerable<ValidationResult> ValidateNumberOfBlocks(BlockEditorData<TValue, TLayout>? blockEditorData, int? min, int? max)
|
||||
{
|
||||
var numberOfBlocks = blockEditorData?.Layout?.Count() ?? 0;
|
||||
@@ -35,8 +45,8 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
TextService.Localize(
|
||||
"validation",
|
||||
"entriesShort",
|
||||
new[] { min.ToString(), (min - numberOfBlocks).ToString(), }),
|
||||
new[] { "minCount" });
|
||||
[min.ToString(), (min - numberOfBlocks).ToString(),]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +56,8 @@ internal abstract class BlockEditorMinMaxValidatorBase<TValue, TLayout> : IValue
|
||||
TextService.Localize(
|
||||
"validation",
|
||||
"entriesExceed",
|
||||
new[] { max.ToString(), (numberOfBlocks - max).ToString(), }),
|
||||
new[] { "maxCount" });
|
||||
[max.ToString(), (numberOfBlocks - max).ToString(),]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
|
||||
where TValue : BlockValue<TLayout>, new()
|
||||
where TLayout : class, IBlockLayoutItem, new()
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
|
||||
protected BlockEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
@@ -52,7 +50,12 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
: base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) =>
|
||||
_jsonSerializer = jsonSerializer;
|
||||
JsonSerializer = jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IJsonSerializer"/>.
|
||||
/// </summary>
|
||||
protected IJsonSerializer JsonSerializer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
@@ -143,6 +146,6 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
|
||||
MapBlockValueFromEditor(blockEditorData.BlockValue);
|
||||
|
||||
// return json
|
||||
return _jsonSerializer.Serialize(blockEditorData.BlockValue);
|
||||
return JsonSerializer.Serialize(blockEditorData.BlockValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a block list property editor.
|
||||
/// Represents a block list property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.BlockList,
|
||||
@@ -19,6 +19,9 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
{
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public BlockListPropertyEditor(
|
||||
IDataValueEditorFactory dataValueEditorFactory,
|
||||
IIOHelper ioHelper,
|
||||
@@ -27,6 +30,9 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
: base(dataValueEditorFactory, blockValuePropertyIndexValueFactory, jsonSerializer)
|
||||
=> _ioHelper = ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use constructor that doesn't take PropertyEditorCollection, scheduled for removal in V15")]
|
||||
public BlockListPropertyEditor(
|
||||
IDataValueEditorFactory dataValueEditorFactory,
|
||||
@@ -38,6 +44,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool SupportsConfigurableElements => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,6 +57,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
return valueEditor.MergePartialPropertyValueForCulture(sourceValue, targetValue, culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? MergeVariantInvariantPropertyValue(
|
||||
object? sourceValue,
|
||||
object? targetValue,
|
||||
@@ -60,10 +68,7 @@ public class BlockListPropertyEditor : BlockListPropertyEditorBase
|
||||
return valueEditor.MergeVariantInvariantPropertyValue(sourceValue, targetValue, canUpdateInvariantData, allowedCultures);
|
||||
}
|
||||
|
||||
#region Pre Value Editor
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new BlockListConfigurationEditor(_ioHelper);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
using StaticServiceProvider = Umbraco.Cms.Core.DependencyInjection.StaticServiceProvider;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -23,12 +24,18 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 15.")]
|
||||
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
|
||||
: this(dataValueEditorFactory,blockValuePropertyIndexValueFactory, StaticServiceProvider.Instance.GetRequiredService<IJsonSerializer>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListPropertyEditorBase"/> class.
|
||||
/// </summary>
|
||||
protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory, IJsonSerializer jsonSerializer)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
@@ -37,21 +44,27 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
|
||||
|
||||
#region Value Editor
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="BlockEditorDataConverter"/> for use with the block list editor property value editor.
|
||||
/// Instantiates a new <see cref="BlockEditorDataConverter{BlockListValue, BlockListLayoutItem}"/> for use with the block list editor property value editor.
|
||||
/// </summary>
|
||||
/// <returns>A new instance of <see cref="BlockListEditorDataConverter"/>.</returns>
|
||||
protected virtual BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(_jsonSerializer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<BlockListEditorPropertyValueEditor>(Attribute!, CreateBlockEditorDataConverter());
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the block list property editors.
|
||||
/// </summary>
|
||||
internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor<BlockListValue, BlockListLayoutItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListEditorPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public BlockListEditorPropertyValueEditor(
|
||||
DataEditorAttribute attribute,
|
||||
BlockEditorDataConverter<BlockListValue, BlockListLayoutItem> blockEditorDataConverter,
|
||||
@@ -74,8 +87,15 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
Validators.Add(new MinMaxValidator(BlockEditorValues, textService));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IValueRequiredValidator RequiredValidator => new BlockListValueRequiredValidator(JsonSerializer);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override BlockListValue CreateWithLayout(IEnumerable<BlockListLayoutItem> layout) => new(layout);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for block list property editors.
|
||||
/// </summary>
|
||||
private class MinMaxValidator : BlockEditorMinMaxValidatorBase<BlockListValue, BlockListLayoutItem>
|
||||
{
|
||||
private readonly BlockEditorValues<BlockListValue, BlockListLayoutItem> _blockEditorValues;
|
||||
@@ -100,12 +120,11 @@ public abstract class BlockListPropertyEditorBase : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<Guid> ConfiguredElementTypeKeys()
|
||||
{
|
||||
var configuration = ConfigurationObject as BlockListConfiguration;
|
||||
return configuration?.Blocks.SelectMany(ConfiguredElementTypeKeys) ?? Enumerable.Empty<Guid>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color picker property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.ColorPicker,
|
||||
ValueEditorIsReusable = true)]
|
||||
@@ -14,6 +24,9 @@ public class ColorPickerPropertyEditor : DataEditor
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
@@ -22,10 +35,78 @@ public class ColorPickerPropertyEditor : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
=> DataValueEditorFactory.Create<ColorPickerPropertyValueEditor>(Attribute!);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new ColorPickerConfigurationEditor(_ioHelper, _configurationEditorJsonSerializer);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the color picker property editor.
|
||||
/// </summary>
|
||||
internal class ColorPickerPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
Validators.AddRange(new ConfiguredColorValidator(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the color selection for the color picker property editor.
|
||||
/// </summary>
|
||||
internal class ConfiguredColorValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfiguredColorValidator"/> class.
|
||||
/// </summary>
|
||||
public ConfiguredColorValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
if (value is null || value is not JsonObject valueAsJsonObject)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (dataTypeConfiguration is not ColorPickerConfiguration colorPickerConfiguration)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
string? selectedColor = valueAsJsonObject["value"]?.GetValue<string>();
|
||||
if (selectedColor.IsNullOrWhiteSpace())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
IEnumerable<string> validColors = colorPickerConfiguration.Items.Select(x => EnsureConsistentColorRepresentation(x.Value));
|
||||
if (validColors.Contains(EnsureConsistentColorRepresentation(selectedColor)) is false)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidColor", [selectedColor]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string EnsureConsistentColorRepresentation(string color)
|
||||
=> (color.StartsWith('#') ? color : $"#{color}").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,50 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Defines an email address property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.EmailAddress,
|
||||
ValueEditorIsReusable = true)]
|
||||
public class EmailAddressPropertyEditor : DataEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The constructor will setup the property editor based on the attribute if one is found
|
||||
/// </summary>
|
||||
public EmailAddressPropertyEditor(IDataValueEditorFactory dataValueEditorFactory)
|
||||
: base(dataValueEditorFactory)
|
||||
=> SupportsReadOnly = true;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailAddressPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public EmailAddressPropertyEditor(IDataValueEditorFactory dataValueEditorFactory)
|
||||
: this(
|
||||
dataValueEditorFactory,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailAddressPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public EmailAddressPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, ILocalizedTextService localizedTextService)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
SupportsReadOnly = true;
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
{
|
||||
IDataValueEditor editor = base.CreateValueEditor();
|
||||
|
||||
// add an email address validator
|
||||
editor.Validators.Add(new EmailValidator());
|
||||
editor.Validators.Add(new EmailValidator(_localizedTextService));
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a media picker property editor.
|
||||
/// Represents a media picker property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.MediaPicker3,
|
||||
@@ -38,6 +38,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -48,8 +49,9 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<MediaPicker3PropertyValueEditor>(Attribute!);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the media picker property editor.
|
||||
/// </summary>
|
||||
internal class MediaPicker3PropertyValueEditor : DataValueEditor, IDataValueReference
|
||||
{
|
||||
private readonly IDataTypeConfigurationCache _dataTypeReadCache;
|
||||
@@ -60,6 +62,13 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaPicker3PropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note on FromEditor() and ToEditor() methods.
|
||||
/// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string.
|
||||
/// </remarks>
|
||||
public MediaPicker3PropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
@@ -92,10 +101,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
Validators.Add(validators);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Note: no FromEditor() and ToEditor() methods
|
||||
/// We do not want to transform the way the data is stored in the DB and would like to keep a raw JSON string
|
||||
/// </remarks>
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
{
|
||||
foreach (MediaWithCropsDto dto in Deserialize(_jsonSerializer, value))
|
||||
@@ -104,6 +110,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
{
|
||||
var value = property.GetValue(culture, segment);
|
||||
@@ -111,7 +118,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
var dtos = Deserialize(_jsonSerializer, value).ToList();
|
||||
dtos = UpdateMediaTypeAliases(dtos);
|
||||
|
||||
var configuration = _dataTypeReadCache.GetConfigurationAs<MediaPicker3Configuration>(property.PropertyType.DataTypeKey);
|
||||
MediaPicker3Configuration? configuration = _dataTypeReadCache.GetConfigurationAs<MediaPicker3Configuration>(property.PropertyType.DataTypeKey);
|
||||
if (configuration is not null)
|
||||
{
|
||||
foreach (MediaWithCropsDto dto in dtos)
|
||||
@@ -123,6 +130,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
return dtos;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
if (editorValue.Value is null ||
|
||||
@@ -145,6 +153,9 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
return _jsonSerializer.Serialize(mediaWithCropsDtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the provided JSON value into a list of <see cref="MediaWithCropsDto"/>.
|
||||
/// </summary>
|
||||
internal static IEnumerable<MediaWithCropsDto> Deserialize(IJsonSerializer jsonSerializer, object? value)
|
||||
{
|
||||
var rawJson = value is string str ? str : value?.ToString();
|
||||
@@ -248,14 +259,29 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
/// </summary>
|
||||
internal class MediaWithCropsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the key.
|
||||
/// </summary>
|
||||
public Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the media key.
|
||||
/// </summary>
|
||||
public Guid MediaKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the media type alias.
|
||||
/// </summary>
|
||||
public string MediaTypeAlias { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the crops.
|
||||
/// </summary>
|
||||
public IEnumerable<ImageCropperValue.ImageCropperCrop>? Crops { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the focal point.
|
||||
/// </summary>
|
||||
public ImageCropperValue.ImageCropperFocalPoint? FocalPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -309,12 +335,19 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for the media picker property editor.
|
||||
/// </summary>
|
||||
internal class MinMaxValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinMaxValidator"/> class.
|
||||
/// </summary>
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
List<MediaWithCropsDto>? mediaWithCropsDtos,
|
||||
MediaPicker3Configuration? mediaPickerConfiguration,
|
||||
@@ -363,12 +396,18 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the allowed type configuration for the media picker property editor.
|
||||
/// </summary>
|
||||
internal class AllowedTypeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IMediaService _mediaService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AllowedTypeValidator"/> class.
|
||||
/// </summary>
|
||||
public AllowedTypeValidator(ILocalizedTextService localizedTextService, IMediaTypeService mediaTypeService, IMediaService mediaService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
@@ -376,6 +415,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
_mediaService = mediaService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
List<MediaWithCropsDto>? value,
|
||||
MediaPicker3Configuration? configuration,
|
||||
@@ -429,11 +469,17 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the start node configuration for the media picker property editor.
|
||||
/// </summary>
|
||||
internal class StartNodeValidator : ITypedJsonValidator<List<MediaWithCropsDto>, MediaPicker3Configuration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMediaNavigationQueryService _mediaNavigationQueryService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StartNodeValidator"/> class.
|
||||
/// </summary>
|
||||
public StartNodeValidator(
|
||||
ILocalizedTextService localizedTextService,
|
||||
IMediaNavigationQueryService mediaNavigationQueryService)
|
||||
@@ -442,6 +488,7 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
_mediaNavigationQueryService = mediaNavigationQueryService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
List<MediaWithCropsDto>? value,
|
||||
MediaPicker3Configuration? configuration,
|
||||
@@ -453,53 +500,14 @@ public class MediaPicker3PropertyEditor : DataEditor
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
List<Guid> uniqueParentKeys = [];
|
||||
foreach (Guid distinctMediaKey in value.DistinctBy(x => x.MediaKey).Select(x => x.MediaKey))
|
||||
if (ValidationHelper.HasValidStartNode(value.Select(x => x.MediaKey), configuration.StartNodeId.Value, _mediaNavigationQueryService) is false)
|
||||
{
|
||||
if (_mediaNavigationQueryService.TryGetParentKey(distinctMediaKey, out Guid? parentKey) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a start node, the media must have a parent.
|
||||
if (parentKey is null)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
uniqueParentKeys.Add(parentKey.Value);
|
||||
}
|
||||
|
||||
IEnumerable<Guid> parentKeysNotInStartNode = uniqueParentKeys.Where(x => x != configuration.StartNodeId.Value);
|
||||
foreach (Guid parentKey in parentKeysNotInStartNode)
|
||||
{
|
||||
if (_mediaNavigationQueryService.TryGetAncestorsKeys(parentKey, out IEnumerable<Guid> foundAncestorKeys) is false)
|
||||
{
|
||||
// We couldn't find the parent node, so we fail.
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
Guid[] ancestorKeys = foundAncestorKeys.ToArray();
|
||||
if (ancestorKeys.Length == 0 || ancestorKeys.Contains(configuration.StartNodeId.Value) is false)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidStartNode"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a multi-node tree picker property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.MultiNodeTreePicker,
|
||||
ValueType = ValueTypes.Text,
|
||||
@@ -19,6 +30,9 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
|
||||
{
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public MultiNodeTreePickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
@@ -26,32 +40,75 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new MultiNodePickerConfigurationEditor(_ioHelper);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<MultiNodeTreePickerPropertyValueEditor>(Attribute!);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the media picker property editor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// At first glance, the fromEditor and toEditor methods might seem strange.
|
||||
/// This is because we wanted to stop the leaking of UDI's to the frontend while not having to do database migrations
|
||||
/// so we opted to, for now, translate the udi string in the database into a structured format unique to the client
|
||||
/// At first glance, the FromEditor and ToEditor methods might seem strange.
|
||||
/// This is because we wanted to stop the leaking of UDIs to the frontend while not having to do database migrations
|
||||
/// so we opted to, for now, translate the UDI string in the database into a structured format unique to the client.
|
||||
/// This way, for now, no migration is needed and no changes outside of the editor logic needs to be touched to stop the leaking.
|
||||
/// </remarks>
|
||||
public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public MultiNodeTreePickerPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IEntityService entityService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IContentService contentService,
|
||||
IMediaService mediaService,
|
||||
IMemberService memberService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
Validators.Add(new TypedJsonValidatorRunner<EditorEntityReference[], MultiNodePickerConfiguration>(
|
||||
jsonSerializer,
|
||||
new MinMaxValidator(localizedTextService),
|
||||
new ObjectTypeValidator(localizedTextService, coreScopeProvider, entityService),
|
||||
new ContentTypeValidator(localizedTextService, coreScopeProvider, contentService, mediaService, memberService)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultiNodeTreePickerPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public MultiNodeTreePickerPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
: this(
|
||||
shortStringHelper,
|
||||
jsonSerializer,
|
||||
ioHelper,
|
||||
attribute,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizedTextService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IEntityService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICoreScopeProvider>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IMediaService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IMemberService>())
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
{
|
||||
var asString = value == null ? string.Empty : value is string str ? str : value.ToString();
|
||||
@@ -66,12 +123,13 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
=> editorValue.Value is JsonArray jsonArray
|
||||
? EntityReferencesToUdis(_jsonSerializer.Deserialize<IEnumerable<EditorEntityReference>>(jsonArray.ToJsonString()) ?? Enumerable.Empty<EditorEntityReference>())
|
||||
: null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
{
|
||||
var value = property.GetValue(culture, segment);
|
||||
@@ -80,7 +138,7 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
|
||||
: null;
|
||||
}
|
||||
|
||||
private IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<string> stringUdis)
|
||||
private static IEnumerable<EditorEntityReference> UdisToEntityReferences(IEnumerable<string> stringUdis)
|
||||
{
|
||||
foreach (var stringUdi in stringUdis)
|
||||
{
|
||||
@@ -93,14 +151,277 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
private string EntityReferencesToUdis(IEnumerable<EditorEntityReference> nodeReferences)
|
||||
private static string EntityReferencesToUdis(IEnumerable<EditorEntityReference> nodeReferences)
|
||||
=> string.Join(",", nodeReferences.Select(entityReference => Udi.Create(entityReference.Type, entityReference.Unique).ToString()));
|
||||
|
||||
/// <summary>
|
||||
/// Describes and editor entity reference.
|
||||
/// </summary>
|
||||
public class EditorEntityReference
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the entity object type.
|
||||
/// </summary>
|
||||
public required string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the entity unique identifier.
|
||||
/// </summary>
|
||||
public required Guid Unique { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the configured object type for documents.
|
||||
/// </summary>
|
||||
internal const string DocumentObjectType = "content";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the configured object type for media.
|
||||
/// </summary>
|
||||
internal const string MediaObjectType = "media";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the configured object type for members.
|
||||
/// </summary>
|
||||
internal const string MemberObjectType = "member";
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for the multi-node tree picker property editor.
|
||||
/// </summary>
|
||||
internal class MinMaxValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinMaxValidator"/> class.
|
||||
/// </summary>
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
EditorEntityReference[]? entityReferences,
|
||||
MultiNodePickerConfiguration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
|
||||
if (configuration is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (configuration.MinNumber > 0 && (entityReferences is null || entityReferences.Length < configuration.MinNumber))
|
||||
{
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesShort",
|
||||
[configuration.MinNumber.ToString(), (configuration.MinNumber - (entityReferences?.Length ?? 0)).ToString()
|
||||
]),
|
||||
["value"]));
|
||||
}
|
||||
|
||||
if (entityReferences is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (configuration.MaxNumber > 0 && entityReferences.Length > configuration.MaxNumber)
|
||||
{
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesExceed",
|
||||
[configuration.MaxNumber.ToString(), (entityReferences.Length - configuration.MaxNumber).ToString()
|
||||
]),
|
||||
["value"]));
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Validates the selected object type for the multi-node tree picker property editor.
|
||||
/// </summary>
|
||||
internal class ObjectTypeValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectTypeValidator"/> class.
|
||||
/// </summary>
|
||||
public ObjectTypeValidator(
|
||||
ILocalizedTextService localizedTextService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IEntityService entityService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
_entityService = entityService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
EditorEntityReference[]? entityReferences,
|
||||
MultiNodePickerConfiguration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
|
||||
if (entityReferences is null || configuration?.TreeSource?.ObjectType is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
Guid[] uniqueKeys = entityReferences.DistinctBy(x => x.Unique).Select(x => x.Unique).ToArray();
|
||||
|
||||
if (uniqueKeys.Length == 0)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
Guid? allowedObjectType = GetObjectType(configuration.TreeSource.ObjectType);
|
||||
if (allowedObjectType is null)
|
||||
{
|
||||
return
|
||||
[
|
||||
|
||||
// Some invalid object type was sent.
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"invalidObjectType"),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
foreach (Guid key in uniqueKeys)
|
||||
{
|
||||
IEntitySlim? entity = _entityService.Get(key);
|
||||
if (entity is not null && entity.NodeObjectType != allowedObjectType)
|
||||
{
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"invalidObjectType"),
|
||||
["value"]));
|
||||
}
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
private static Guid? GetObjectType(string objectType) =>
|
||||
objectType switch
|
||||
{
|
||||
DocumentObjectType => Constants.ObjectTypes.Document,
|
||||
MediaObjectType => Constants.ObjectTypes.Media,
|
||||
MemberObjectType => Constants.ObjectTypes.Member,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Validates the selected content type for the multi-node tree picker property editor.
|
||||
/// </summary>
|
||||
internal class ContentTypeValidator : ITypedJsonValidator<EditorEntityReference[], MultiNodePickerConfiguration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMemberService _memberService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentTypeValidator"/> class.
|
||||
/// </summary>
|
||||
public ContentTypeValidator(
|
||||
ILocalizedTextService localizedTextService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IContentService contentService,
|
||||
IMediaService mediaService,
|
||||
IMemberService memberService)
|
||||
{
|
||||
_localizedTextService = localizedTextService;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
_contentService = contentService;
|
||||
_mediaService = mediaService;
|
||||
_memberService = memberService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
EditorEntityReference[]? entityReferences,
|
||||
MultiNodePickerConfiguration? configuration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
var validationResults = new List<ValidationResult>();
|
||||
|
||||
Guid[] allowedTypes = configuration?.Filter?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(Guid.Parse).ToArray() ?? [];
|
||||
|
||||
// We can't validate if there is no object type, and we don't need to if there's no filter.
|
||||
if (entityReferences is null || allowedTypes.Length == 0 || configuration?.TreeSource?.ObjectType is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
|
||||
Guid?[] uniqueContentTypeKeys = entityReferences
|
||||
.Select(x => x.Unique)
|
||||
.Distinct()
|
||||
.Select(x => GetContent(configuration.TreeSource.ObjectType, x))
|
||||
.Select(x => x?.ContentType.Key)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
scope.Complete();
|
||||
|
||||
foreach (Guid? key in uniqueContentTypeKeys)
|
||||
{
|
||||
if (key is null)
|
||||
{
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"missingContent"),
|
||||
["value"]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (allowedTypes.Contains(key.Value) is false)
|
||||
{
|
||||
validationResults.Add(new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"invalidObjectType"),
|
||||
["value"]));
|
||||
}
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
private IContentBase? GetContent(string objectType, Guid key) =>
|
||||
objectType switch
|
||||
{
|
||||
DocumentObjectType => _contentService.GetById(key),
|
||||
MediaObjectType => _mediaService.GetById(key),
|
||||
MemberObjectType => _memberService.GetById(key),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
@@ -8,10 +9,13 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -41,6 +45,9 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_contentService = contentService;
|
||||
_mediaService = mediaService;
|
||||
Validators.Add(new TypedJsonValidatorRunner<LinkDisplay[], MultiUrlPickerConfiguration>(
|
||||
_jsonSerializer,
|
||||
new MinMaxValidator(localizedTextService)));
|
||||
}
|
||||
|
||||
public IEnumerable<UmbracoEntityReference> GetReferences(object? value)
|
||||
@@ -209,4 +216,51 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference
|
||||
[DataMember(Name = "queryString")]
|
||||
public string? QueryString { get; set; }
|
||||
}
|
||||
|
||||
internal class MinMaxValidator : ITypedJsonValidator<LinkDisplay[], MultiUrlPickerConfiguration>
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(
|
||||
LinkDisplay[]? linksDtos,
|
||||
MultiUrlPickerConfiguration? multiUrlPickerConfiguration,
|
||||
string? valueType,
|
||||
PropertyValidationContext validationContext)
|
||||
{
|
||||
if (multiUrlPickerConfiguration is null || (linksDtos is null && multiUrlPickerConfiguration.MinNumber == 0))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (linksDtos is null || linksDtos.Length < multiUrlPickerConfiguration.MinNumber)
|
||||
{
|
||||
return [new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesShort",
|
||||
[multiUrlPickerConfiguration.MinNumber.ToString(), (multiUrlPickerConfiguration.MinNumber - (linksDtos?.Length ?? 0)).ToString()]),
|
||||
["value"])];
|
||||
}
|
||||
|
||||
if (linksDtos.Length > multiUrlPickerConfiguration.MaxNumber && multiUrlPickerConfiguration.MaxNumber > 0)
|
||||
{
|
||||
return
|
||||
[
|
||||
new ValidationResult(
|
||||
_localizedTextService.Localize(
|
||||
"validation",
|
||||
"entriesExceed",
|
||||
[
|
||||
multiUrlPickerConfiguration.MaxNumber.ToString(),
|
||||
(linksDtos.Length - multiUrlPickerConfiguration.MaxNumber).ToString()
|
||||
]),
|
||||
["value"])
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@ using Umbraco.Cms.Core.Exceptions;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a multiple text string property editor.
|
||||
/// Represents a multiple text string property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.MultipleTextstring,
|
||||
@@ -24,7 +27,7 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class.
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyEditor" /> class.
|
||||
/// </summary>
|
||||
public MultipleTextStringPropertyEditor(IIOHelper ioHelper, IDataValueEditorFactory dataValueEditorFactory)
|
||||
: base(dataValueEditorFactory)
|
||||
@@ -42,37 +45,42 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
new MultipleTextStringConfigurationEditor(_ioHelper);
|
||||
|
||||
/// <summary>
|
||||
/// Custom value editor so we can format the value for the editor and the database
|
||||
/// Defines the value editor for the multiple text string property editor.
|
||||
/// </summary>
|
||||
internal class MultipleTextStringPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
private static readonly string NewLine = "\n";
|
||||
private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" };
|
||||
private static readonly string _newLine = "\n";
|
||||
private static readonly string[] _newLineDelimiters = { "\r\n", "\r", "\n" };
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleTextStringPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public MultipleTextStringPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
Validators.AddRange(new MinMaxValidator(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom FormatValidator is used as for multiple text strings, each string should individually be checked
|
||||
/// against the configured regular expression, rather than the JSON representing all the strings as a whole.
|
||||
/// A custom <see href="IValueFormatValidator" /> is used as for multiple text strings, each string should individually
|
||||
/// be checked against the configured regular expression, rather than the JSON representing all the strings as a whole.
|
||||
/// </summary>
|
||||
public override IValueFormatValidator FormatValidator => new MultipleTextStringFormatValidator();
|
||||
|
||||
/// <summary>
|
||||
/// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the
|
||||
/// string
|
||||
/// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the
|
||||
/// string.
|
||||
/// </summary>
|
||||
/// <param name="editorValue"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end
|
||||
/// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end.
|
||||
/// </remarks>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
@@ -93,30 +101,35 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
// only allow the max if over 0
|
||||
if (max > 0)
|
||||
{
|
||||
return string.Join(NewLine, value.Take(max));
|
||||
return string.Join(_newLine, value.Take(max));
|
||||
}
|
||||
|
||||
return string.Join(NewLine, value);
|
||||
return string.Join(_newLine, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
{
|
||||
var value = property.GetValue(culture, segment);
|
||||
|
||||
// The legacy property editor saved this data as new line delimited! strange but we have to maintain that.
|
||||
return value is string stringValue
|
||||
? stringValue.Split(NewLineDelimiters, StringSplitOptions.None)
|
||||
? stringValue.Split(_newLineDelimiters, StringSplitOptions.None)
|
||||
: Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom <see href="IValueFormatValidator" /> to check each string against the configured format.
|
||||
/// </summary>
|
||||
internal class MultipleTextStringFormatValidator : IValueFormatValidator
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> ValidateFormat(object? value, string valueType, string format)
|
||||
{
|
||||
if (value is not IEnumerable<string> textStrings)
|
||||
{
|
||||
return Enumerable.Empty<ValidationResult>();
|
||||
return [];
|
||||
}
|
||||
|
||||
var textStringValidator = new RegexValidator();
|
||||
@@ -129,7 +142,60 @@ public class MultipleTextStringPropertyEditor : DataEditor
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ValidationResult>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the min/max configuration for the multiple text strings property editor.
|
||||
/// </summary>
|
||||
internal class MinMaxValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MinMaxValidator"/> class.
|
||||
/// </summary>
|
||||
public MinMaxValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
if (dataTypeConfiguration is not MultipleTextStringConfiguration multipleTextStringConfiguration)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// If we have a null value, treat as an empty collection for minimum number validation.
|
||||
if (value is not IEnumerable<string> stringValues)
|
||||
{
|
||||
stringValues = [];
|
||||
}
|
||||
|
||||
var stringCount = stringValues.Count();
|
||||
|
||||
if (stringCount < multipleTextStringConfiguration.Min)
|
||||
{
|
||||
if (stringCount == 1)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeSingleItemMinimum", [multipleTextStringConfiguration.Min.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeMultipleItemsMinimum", [stringCount.ToString(), multipleTextStringConfiguration.Min.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
if (multipleTextStringConfiguration.Max > 0 && stringCount > multipleTextStringConfiguration.Max)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "outOfRangeMultipleItemsMaximum", [stringCount.ToString(), multipleTextStringConfiguration.Max.ToString()]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,33 +11,34 @@ using Umbraco.Cms.Core.Strings;
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// A value editor to handle posted json array data and to return array data for the multiple selected csv items
|
||||
/// A value editor to handle posted JSON array data and to return array data for the multiple selected CSV items.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is re-used by editors such as the multiple drop down list or check box list
|
||||
/// This is re-used by editors such as the multiple drop down list or check box list.
|
||||
/// </remarks>
|
||||
public class MultipleValueEditor : DataValueEditor
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MultipleValueEditor"/> class.
|
||||
/// </summary>
|
||||
public MultipleValueEditor(
|
||||
ILocalizedTextService localizedTextService,
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
_jsonSerializer = jsonSerializer;
|
||||
Validators.Add(new MultipleValueValidator());
|
||||
Validators.Add(new MultipleValueValidator(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When multiple values are selected a json array will be posted back so we need to format for storage in
|
||||
/// the database which is a comma separated string value
|
||||
/// When multiple values are selected a JSON array will be posted back so we need to format for storage in
|
||||
/// the database which is a comma separated string value.
|
||||
/// </summary>
|
||||
/// <param name="editorValue"></param>
|
||||
/// <param name="currentValue"></param>
|
||||
/// <returns></returns>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
@@ -46,7 +47,6 @@ public class MultipleValueEditor : DataValueEditor
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = _jsonSerializer.Serialize(stringValues);
|
||||
return result;
|
||||
return _jsonSerializer.Serialize(stringValues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -20,7 +25,7 @@ public class RadioButtonsPropertyEditor : DataEditor
|
||||
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// The constructor will setup the property editor based on the attribute if one is found
|
||||
/// Initializes a new instance of the <see cref="RadioButtonsPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public RadioButtonsPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
|
||||
: base(dataValueEditorFactory)
|
||||
@@ -30,13 +35,71 @@ public class RadioButtonsPropertyEditor : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a custom pre-value editor
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <inheritdoc/>
|
||||
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new ValueListConfigurationEditor(_ioHelper, _configurationEditorJsonSerializer);
|
||||
|
||||
protected override IDataValueEditor CreateValueEditor() =>
|
||||
DataValueEditorFactory.Create<RadioValueEditor>(Attribute!);
|
||||
/// <inheritdoc/>
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
=> DataValueEditorFactory.Create<RadioButtonsPropertyValueEditor>(Attribute!);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the radio buttons property editor.
|
||||
/// </summary>
|
||||
internal class RadioButtonsPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadioButtonsPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public RadioButtonsPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
=> Validators.AddRange([new RadioButtonValueValidator(localizedTextService)]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the prevalue configuration for the radio buttons property editor.
|
||||
/// </summary>
|
||||
internal class RadioButtonValueValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RadioButtonValueValidator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="localizedTextService"></param>
|
||||
public RadioButtonValueValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
if (value == null || value.ToString().IsNullOrWhiteSpace())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (dataTypeConfiguration is not ValueListConfiguration valueListConfiguration)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (value is not string valueAsString)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (valueListConfiguration.Items.Contains(valueAsString) is false)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "notOneOfOptions", [valueAsString]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a checkbox property and parameter editor.
|
||||
/// Represents a true/false (toggle) property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.Boolean,
|
||||
@@ -29,8 +30,14 @@ public class TrueFalsePropertyEditor : DataEditor
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
=> DataValueEditorFactory.Create<TrueFalsePropertyValueEditor>(Attribute!);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the true/false (toggle) property editor.
|
||||
/// </summary>
|
||||
internal class TrueFalsePropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TrueFalsePropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public TrueFalsePropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
@@ -40,10 +47,17 @@ public class TrueFalsePropertyEditor : DataEditor
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IValueRequiredValidator RequiredValidator => new TrueFalseValueRequiredValidator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
|
||||
=> ParsePropertyValue(property.GetValue(culture, segment));
|
||||
|
||||
// NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1.
|
||||
/// </remarks>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
=> ParsePropertyValue(editorValue.Value) ? 1 : 0;
|
||||
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Blocks;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Custom validator for block value required validation.
|
||||
/// </summary>
|
||||
internal class BlockListValueRequiredValidator : RequiredValidator
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListValueRequiredValidator"/> class.
|
||||
/// </summary>
|
||||
public BlockListValueRequiredValidator(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
{
|
||||
IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (_jsonSerializer.TryDeserialize(value, out BlockListValue? blockListValue) &&
|
||||
blockListValue.ContentData.Count == 0 &&
|
||||
blockListValue.Layout.Count == 0)
|
||||
{
|
||||
validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"]));
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Custom validator for true/false (toggle) required validation.
|
||||
/// </summary>
|
||||
internal class TrueFalseValueRequiredValidator : RequiredValidator
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
|
||||
{
|
||||
IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType);
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
return validationResults;
|
||||
}
|
||||
|
||||
if (value is bool valueAsBool && valueAsBool is false)
|
||||
{
|
||||
validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"]));
|
||||
}
|
||||
|
||||
return validationResults;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
|
||||
@@ -22,33 +20,9 @@ internal class ReservedFieldNamesService : IReservedFieldNamesService
|
||||
_mediaPropertySettings = mediaPropertySettings.Value;
|
||||
}
|
||||
|
||||
public ISet<string> GetDocumentReservedFieldNames()
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
reservedProperties.UnionWith(reservedMethods);
|
||||
reservedProperties.UnionWith(_contentPropertySettings.ReservedFieldNames);
|
||||
public ISet<string> GetDocumentReservedFieldNames() => _contentPropertySettings.ReservedFieldNames;
|
||||
|
||||
return reservedProperties;
|
||||
}
|
||||
public ISet<string> GetMediaReservedFieldNames() => _mediaPropertySettings.ReservedFieldNames;
|
||||
|
||||
public ISet<string> GetMediaReservedFieldNames()
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedContent).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedContent).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
reservedProperties.UnionWith(reservedMethods);
|
||||
reservedProperties.UnionWith(_mediaPropertySettings.ReservedFieldNames);
|
||||
|
||||
return reservedProperties;
|
||||
}
|
||||
|
||||
public ISet<string> GetMemberReservedFieldNames()
|
||||
{
|
||||
var reservedProperties = typeof(IPublishedMember).GetPublicProperties().Select(x => x.Name).ToHashSet();
|
||||
var reservedMethods = typeof(IPublishedMember).GetPublicMethods().Select(x => x.Name).ToHashSet();
|
||||
reservedProperties.UnionWith(reservedMethods);
|
||||
reservedProperties.UnionWith(_memberPropertySettings.ReservedFieldNames);
|
||||
|
||||
return reservedProperties;
|
||||
}
|
||||
public ISet<string> GetMemberReservedFieldNames() => _memberPropertySettings.ReservedFieldNames;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public class UmbracoTreeSearcherFields : IUmbracoTreeSearcherFields
|
||||
UmbracoExamineFieldNames.CategoryFieldName,
|
||||
"parentID",
|
||||
UmbracoExamineFieldNames.ItemTypeFieldName,
|
||||
UmbracoExamineFieldNames.IndexPathFieldName,
|
||||
};
|
||||
|
||||
private readonly ISet<string> _backOfficeMediaFieldsToLoad =
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Services;
|
||||
|
||||
internal sealed class ContentTypeSearchService : IContentTypeSearchService
|
||||
{
|
||||
private readonly ISqlContext _sqlContext;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public ContentTypeSearchService(ISqlContext sqlContext, IContentTypeService contentTypeService)
|
||||
{
|
||||
_sqlContext = sqlContext;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public async Task<PagedModel<IContentType>> SearchAsync(string query, bool? isElement, CancellationToken cancellationToken, int skip = 0, int take = 100)
|
||||
{
|
||||
// if the query is a GUID, search for that explicitly
|
||||
Guid.TryParse(query, out Guid guidQuery);
|
||||
|
||||
IQuery<IContentType> nameQuery = isElement is not null ?
|
||||
_sqlContext.Query<IContentType>().Where(x => (x.Name!.Contains(query) || x.Key == guidQuery) && x.IsElement == isElement) :
|
||||
_sqlContext.Query<IContentType>().Where(x => x.Name!.Contains(query) || x.Key == guidQuery);
|
||||
|
||||
IContentType[] contentTypes = (await _contentTypeService.GetByQueryAsync(nameQuery, cancellationToken)).ToArray();
|
||||
|
||||
return new PagedModel<IContentType>
|
||||
{
|
||||
Items = contentTypes.Skip(skip).Take(take),
|
||||
Total = contentTypes.Count()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType, query, parentId, null, skip, take, ignoreUserStartNodes);
|
||||
=> Search(objectType, query, parentId, null, null, skip, take, ignoreUserStartNodes);
|
||||
|
||||
public PagedModel<IEntitySlim> Search(
|
||||
UmbracoObjectTypes objectType,
|
||||
@@ -62,6 +62,17 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool ignoreUserStartNodes = false)
|
||||
=> Search(objectType, query, parentId, contentTypeIds, null, skip, take, ignoreUserStartNodes);
|
||||
|
||||
public PagedModel<IEntitySlim> Search(
|
||||
UmbracoObjectTypes objectType,
|
||||
string query,
|
||||
Guid? parentId,
|
||||
IEnumerable<Guid>? contentTypeIds,
|
||||
bool? trashed,
|
||||
int skip = 0,
|
||||
int take = 100,
|
||||
bool ignoreUserStartNodes = false)
|
||||
{
|
||||
UmbracoEntityTypes entityType = objectType switch
|
||||
{
|
||||
@@ -91,6 +102,7 @@ internal sealed class IndexedEntitySearchService : IIndexedEntitySearchService
|
||||
pageNumber,
|
||||
out var totalFound,
|
||||
contentTypeAliases,
|
||||
trashed,
|
||||
ignoreUserStartNodes: ignoreUserStartNodes,
|
||||
searchFrom: parentId?.ToString());
|
||||
|
||||
|
||||
@@ -353,7 +353,7 @@ public class PackagingService : IPackagingService
|
||||
}
|
||||
|
||||
// Set additional values
|
||||
installedPackage.AllowPackageTelemetry = packageManifest.AllowTelemetry;
|
||||
installedPackage.AllowPackageTelemetry = packageManifest is { AllowTelemetry: true, AllowPackageTelemetry: true };
|
||||
|
||||
if (!string.IsNullOrEmpty(packageManifest.Version))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Examine;
|
||||
using Examine;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
@@ -7,7 +7,8 @@ namespace Umbraco.Cms.Infrastructure.Services;
|
||||
|
||||
public class IndexingRebuilderService : IIndexingRebuilderService
|
||||
{
|
||||
private const string TempKey = "temp_indexing_op_";
|
||||
private const string IsRebuildingIndexRuntimeCacheKeyPrefix = "temp_indexing_op_";
|
||||
|
||||
private readonly IAppPolicyCache _runtimeCache;
|
||||
private readonly IIndexRebuilder _indexRebuilder;
|
||||
private readonly ILogger<IndexingRebuilderService> _logger;
|
||||
@@ -49,7 +50,7 @@ public class IndexingRebuilderService : IIndexingRebuilderService
|
||||
|
||||
private void Set(string indexName)
|
||||
{
|
||||
var cacheKey = TempKey + indexName;
|
||||
var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName;
|
||||
|
||||
// put temp val in cache which is used as a rudimentary way to know when the indexing is done
|
||||
_runtimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5));
|
||||
@@ -57,13 +58,13 @@ public class IndexingRebuilderService : IIndexingRebuilderService
|
||||
|
||||
private void Clear(string? indexName)
|
||||
{
|
||||
var cacheKey = TempKey + indexName;
|
||||
var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName;
|
||||
_runtimeCache.Clear(cacheKey);
|
||||
}
|
||||
|
||||
public bool IsRebuilding(string indexName)
|
||||
{
|
||||
var cacheKey = "temp_indexing_op_" + indexName;
|
||||
var cacheKey = IsRebuildingIndexRuntimeCacheKeyPrefix + indexName;
|
||||
return _runtimeCache.Get(cacheKey) is not null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,45 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.HostedServices;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Persistence;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.HybridCache;
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the published content cache in the database.
|
||||
/// </summary>
|
||||
internal class DatabaseCacheRebuilder : IDatabaseCacheRebuilder
|
||||
{
|
||||
private const string NuCacheSerializerKey = "Umbraco.Web.PublishedCache.NuCache.Serializer";
|
||||
private const string IsRebuildingDatabaseCacheRuntimeCacheKey = "temp_database_cache_rebuild_op";
|
||||
|
||||
private readonly IDatabaseCacheRepository _databaseCacheRepository;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IOptions<NuCacheSettings> _nucacheSettings;
|
||||
private readonly IKeyValueService _keyValueService;
|
||||
private readonly ILogger<DatabaseCacheRebuilder> _logger;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
private readonly IBackgroundTaskQueue _backgroundTaskQueue;
|
||||
private readonly IAppPolicyCache _runtimeCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DatabaseCacheRebuilder"/> class.
|
||||
/// </summary>
|
||||
public DatabaseCacheRebuilder(
|
||||
IDatabaseCacheRepository databaseCacheRepository,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IOptions<NuCacheSettings> nucacheSettings,
|
||||
IKeyValueService keyValueService,
|
||||
ILogger<DatabaseCacheRebuilder> logger, IProfilingLogger profilingLogger)
|
||||
ILogger<DatabaseCacheRebuilder> logger,
|
||||
IProfilingLogger profilingLogger,
|
||||
IBackgroundTaskQueue backgroundTaskQueue,
|
||||
IAppPolicyCache runtimeCache)
|
||||
{
|
||||
_databaseCacheRepository = databaseCacheRepository;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
@@ -32,18 +47,63 @@ internal class DatabaseCacheRebuilder : IDatabaseCacheRebuilder
|
||||
_keyValueService = keyValueService;
|
||||
_logger = logger;
|
||||
_profilingLogger = profilingLogger;
|
||||
_backgroundTaskQueue = backgroundTaskQueue;
|
||||
_runtimeCache = runtimeCache;
|
||||
}
|
||||
|
||||
public void Rebuild()
|
||||
/// <inheritdoc/>
|
||||
public bool IsRebuilding() => _runtimeCache.Get(IsRebuildingDatabaseCacheRuntimeCacheKey) is not null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Rebuild() => Rebuild(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Rebuild(bool useBackgroundThread)
|
||||
{
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
_databaseCacheRepository.Rebuild();
|
||||
scope.Complete();
|
||||
if (useBackgroundThread)
|
||||
{
|
||||
_logger.LogInformation("Starting async background thread for rebuilding database cache.");
|
||||
|
||||
_backgroundTaskQueue.QueueBackgroundWorkItem(
|
||||
cancellationToken =>
|
||||
{
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
Task.Run(() => PerformRebuild());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformRebuild()
|
||||
{
|
||||
try
|
||||
{
|
||||
SetIsRebuilding();
|
||||
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
_databaseCacheRepository.Rebuild();
|
||||
scope.Complete();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClearIsRebuilding();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetIsRebuilding() => _runtimeCache.Insert(IsRebuildingDatabaseCacheRuntimeCacheKey, () => "tempValue", TimeSpan.FromMinutes(10));
|
||||
|
||||
private void ClearIsRebuilding() => _runtimeCache.Clear(IsRebuildingDatabaseCacheRuntimeCacheKey);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RebuildDatabaseCacheIfSerializerChanged()
|
||||
{
|
||||
using var scope = _coreScopeProvider.CreateCoreScope();
|
||||
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
|
||||
NuCacheSerializerType serializer = _nucacheSettings.Value.NuCacheSerializerType;
|
||||
var currentSerializerValue = _keyValueService.GetValue(NuCacheSerializerKey);
|
||||
|
||||
@@ -54,11 +114,12 @@ internal class DatabaseCacheRebuilder : IDatabaseCacheRebuilder
|
||||
|
||||
_logger.LogWarning(
|
||||
"Database cache was serialized using {CurrentSerializer}. Currently configured cache serializer {Serializer}. Rebuilding database cache.",
|
||||
currentSerializer, serializer);
|
||||
currentSerializer,
|
||||
serializer);
|
||||
|
||||
using (_profilingLogger.TraceDuration<DatabaseCacheRebuilder>($"Rebuilding database cache with {serializer} serializer"))
|
||||
{
|
||||
Rebuild();
|
||||
Rebuild(false);
|
||||
_keyValueService.SetValue(NuCacheSerializerKey, serializer.ToString());
|
||||
}
|
||||
|
||||
|
||||
@@ -291,8 +291,8 @@ internal class MediaCacheService : IMediaCacheService
|
||||
|
||||
private HybridCacheEntryOptions GetSeedEntryOptions() => new()
|
||||
{
|
||||
Expiration = _cacheSettings.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheSettings.SeedCacheDuration,
|
||||
Expiration = _cacheSettings.Entry.Media.SeedCacheDuration,
|
||||
LocalCacheExpiration = _cacheSettings.Entry.Media.SeedCacheDuration,
|
||||
};
|
||||
|
||||
private string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}";
|
||||
|
||||
@@ -68,7 +68,13 @@ public static class HttpContextExtensions
|
||||
// Otherwise we can't log in as both a member and a backoffice user
|
||||
// For instance if you've enabled basic auth.
|
||||
ClaimsPrincipal? authenticatedPrincipal = result.Principal;
|
||||
IEnumerable<ClaimsIdentity> existingIdentities = httpContext.User.Identities.Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType);
|
||||
|
||||
// Make sure to copy into a list before attempting to update the authenticated principal, so we don't attempt to modify
|
||||
// the collection while iterating it.
|
||||
// See: https://github.com/umbraco/Umbraco-CMS/issues/18509
|
||||
var existingIdentities = httpContext.User.Identities
|
||||
.Where(x => x.IsAuthenticated && x.AuthenticationType != authenticatedPrincipal.Identity.AuthenticationType)
|
||||
.ToList();
|
||||
authenticatedPrincipal.AddIdentities(existingIdentities);
|
||||
|
||||
httpContext.User = authenticatedPrincipal;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user