diff --git a/Directory.Build.props b/Directory.Build.props index 4c315660e6e6..b9439a50eae4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -49,4 +49,13 @@ $(MSBuildThisFileDirectory) + + + + + <_ProjectReferencesWithVersions Condition="'%(ProjectVersion)' != ''"> + [%(ProjectVersion), $([MSBuild]::Add($([System.Text.RegularExpressions.Regex]::Match('%(ProjectVersion)', '^\d+').Value), 1))) + + + diff --git a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs index 73e5bb4a53b8..5b191b526215 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs @@ -1,16 +1,14 @@ using Examine; -using Examine.Lucene.Providers; -using Examine.Lucene.Search; using Examine.Search; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Delivery.Services.QueryBuilders; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Infrastructure.Examine; -using Umbraco.Extensions; namespace Umbraco.Cms.Api.Delivery.Services; @@ -21,10 +19,10 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider { private const string ItemIdFieldName = "itemId"; private readonly IExamineManager _examineManager; - private readonly DeliveryApiSettings _deliveryApiSettings; private readonly ILogger _logger; - private readonly string _fallbackGuidValue; - private readonly Dictionary _fieldTypes; + private readonly ApiContentQuerySelectorBuilder _selectorBuilder; + private readonly ApiContentQueryFilterBuilder _filterBuilder; + private readonly ApiContentQuerySortBuilder _sortBuilder; public ApiContentQueryProvider( IExamineManager examineManager, @@ -33,18 +31,20 @@ public ApiContentQueryProvider( ILogger logger) { _examineManager = examineManager; - _deliveryApiSettings = deliveryApiSettings.Value; _logger = logger; - // A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string - // It is set to a random guid since this would be highly unlikely to yield any results - _fallbackGuidValue = Guid.NewGuid().ToString("D"); - // build a look-up dictionary of field types by field name - _fieldTypes = indexHandlers + var fieldTypes = indexHandlers .SelectMany(handler => handler.GetFields()) .DistinctBy(field => field.FieldName) .ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase); + + // for the time being we're going to keep these as internal implementation details. + // perhaps later on it will make sense to expose them through the DI. + _selectorBuilder = new ApiContentQuerySelectorBuilder(deliveryApiSettings.Value); + _filterBuilder = new ApiContentQueryFilterBuilder(fieldTypes, _logger); + _sortBuilder = new ApiContentQuerySortBuilder(fieldTypes, _logger); + } [Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")] @@ -75,10 +75,9 @@ public PagedModel ExecuteQuery( return new PagedModel(); } - IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture, protectedAccess, preview); - - ApplyFiltering(filterOptions, queryOperation); - ApplySorting(sortOptions, queryOperation); + IBooleanOperation queryOperation = _selectorBuilder.Build(selectorOption, index, culture, protectedAccess, preview); + _filterBuilder.Append(filterOptions, queryOperation); + _sortBuilder.Append(sortOptions, queryOperation); ISearchResults? results = queryOperation .SelectField(ItemIdFieldName) @@ -102,162 +101,4 @@ public PagedModel ExecuteQuery( { FieldName = UmbracoExamineFieldNames.CategoryFieldName, Values = new[] { "content" } }; - - private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture, ProtectedAccess protectedAccess, bool preview) - { - // Needed for enabling leading wildcards searches - BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}."); - - IQuery query = searcher.CreateQuery( - IndexTypes.Content, - BooleanOperation.And, - searcher.LuceneAnalyzer, - new LuceneSearchOptions { AllowLeadingWildcard = true }); - - IBooleanOperation selectorOperation = selectorOption.Values.Length == 1 - ? query.Field(selectorOption.FieldName, selectorOption.Values.First()) - : query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values); - - AddCultureQuery(culture, selectorOperation); - - if (_deliveryApiSettings.MemberAuthorizationIsEnabled()) - { - AddProtectedAccessQuery(protectedAccess, selectorOperation); - } - - // when not fetching for preview, make sure the "published" field is "y" - if (preview is false) - { - selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Published, "y"); - } - - return selectorOperation; - } - - private void AddCultureQuery(string culture, IBooleanOperation selectorOperation) => - selectorOperation - .And() - .GroupedOr( - // Item culture must be either the requested culture or "none" - new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, - culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), - "none"); - - private void AddProtectedAccessQuery(ProtectedAccess protectedAccess, IBooleanOperation selectorOperation) - { - var protectedAccessValues = new List(); - if (protectedAccess.MemberKey is not null) - { - protectedAccessValues.Add($"u:{protectedAccess.MemberKey}"); - } - - if (protectedAccess.MemberRoles?.Any() is true) - { - protectedAccessValues.AddRange(protectedAccess.MemberRoles.Select(r => $"r:{r}")); - } - - if (protectedAccessValues.Any()) - { - selectorOperation.And( - inner => inner - .Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n") - .Or(protectedAccessInner => protectedAccessInner - .GroupedOr( - new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess }, - protectedAccessValues.ToArray())), - BooleanOperation.Or); - } - else - { - selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n"); - } - } - - private void ApplyFiltering(IList filterOptions, IBooleanOperation queryOperation) - { - void HandleExact(IQuery query, string fieldName, string[] values) - { - if (values.Length == 1) - { - query.Field(fieldName, values[0]); - } - else - { - query.GroupedOr(new[] { fieldName }, values); - } - } - - void HandleContains(IQuery query, string fieldName, string[] values) - { - if (values.Length == 1) - { - // The trailing wildcard is added automatically - query.Field(fieldName, (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{values[0]}")); - } - else - { - // The trailing wildcard is added automatically - IExamineValue[] examineValues = values - .Select(value => (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{value}")) - .ToArray(); - query.GroupedOr(new[] { fieldName }, examineValues); - } - } - - foreach (FilterOption filterOption in filterOptions) - { - var values = filterOption.Values.Any() - ? filterOption.Values - : new[] { _fallbackGuidValue }; - - switch (filterOption.Operator) - { - case FilterOperation.Is: - HandleExact(queryOperation.And(), filterOption.FieldName, values); - break; - case FilterOperation.IsNot: - HandleExact(queryOperation.Not(), filterOption.FieldName, values); - break; - case FilterOperation.Contains: - HandleContains(queryOperation.And(), filterOption.FieldName, values); - break; - case FilterOperation.DoesNotContain: - HandleContains(queryOperation.Not(), filterOption.FieldName, values); - break; - default: - continue; - } - } - } - - private void ApplySorting(IList sortOptions, IOrdering ordering) - { - foreach (SortOption sort in sortOptions) - { - if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false) - { - _logger.LogWarning( - "Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.", - sort.FieldName); - continue; - } - - SortType sortType = fieldType switch - { - FieldType.Number => SortType.Int, - FieldType.Date => SortType.Long, - FieldType.StringRaw => SortType.String, - FieldType.StringAnalyzed => SortType.String, - FieldType.StringSortable => SortType.String, - _ => throw new ArgumentOutOfRangeException(nameof(fieldType)) - }; - - ordering = sort.Direction switch - { - Direction.Ascending => ordering.OrderBy(new SortableField(sort.FieldName, sortType)), - Direction.Descending => ordering.OrderByDescending(new SortableField(sort.FieldName, sortType)), - _ => ordering - }; - } - } } diff --git a/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs new file mode 100644 index 000000000000..a54965f11608 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQueryFilterBuilder.cs @@ -0,0 +1,177 @@ +using Examine.Search; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.DeliveryApi; + +namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders; + +internal sealed class ApiContentQueryFilterBuilder +{ + private readonly IDictionary _fieldTypes; + private readonly ILogger _logger; + private readonly string _fallbackGuidValue; + + public ApiContentQueryFilterBuilder(IDictionary fieldTypes, ILogger logger) + { + _fieldTypes = fieldTypes; + _logger = logger; + + // A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string + // It is set to a random guid since this would be highly unlikely to yield any results + _fallbackGuidValue = Guid.NewGuid().ToString("D"); + } + + public void Append(IList filterOptions, IBooleanOperation queryOperation) + { + foreach (FilterOption filterOption in filterOptions) + { + if (_fieldTypes.TryGetValue(filterOption.FieldName, out FieldType fieldType) is false) + { + _logger.LogWarning( + "Filter implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.", + filterOption.FieldName); + continue; + } + + var values = filterOption.Values.Any() + ? filterOption.Values + : new[] { _fallbackGuidValue }; + + switch (filterOption.Operator) + { + case FilterOperation.Is: + ApplyExactFilter(queryOperation.And(), filterOption.FieldName, values, fieldType); + break; + case FilterOperation.IsNot: + ApplyExactFilter(queryOperation.Not(), filterOption.FieldName, values, fieldType); + break; + case FilterOperation.Contains: + ApplyContainsFilter(queryOperation.And(), filterOption.FieldName, values); + break; + case FilterOperation.DoesNotContain: + ApplyContainsFilter(queryOperation.Not(), filterOption.FieldName, values); + break; + default: + continue; + } + } + } + + private void ApplyExactFilter(IQuery query, string fieldName, string[] values, FieldType fieldType) + { + switch (fieldType) + { + case FieldType.Number: + ApplyExactNumberFilter(query, fieldName, values); + break; + case FieldType.Date: + ApplyExactDateFilter(query, fieldName, values); + break; + default: + ApplyExactStringFilter(query, fieldName, values); + break; + } + } + + private void ApplyExactNumberFilter(IQuery query, string fieldName, string[] values) + { + if (values.Length == 1) + { + if (TryParseIntFilterValue(values.First(), out int intValue)) + { + query.Field(fieldName, intValue); + } + } + else + { + int[] intValues = values + .Select(value => TryParseIntFilterValue(value, out int intValue) ? intValue : (int?)null) + .Where(intValue => intValue.HasValue) + .Select(intValue => intValue!.Value) + .ToArray(); + + AddGroupedOrFilter(query, fieldName, intValues); + } + } + + private void ApplyExactDateFilter(IQuery query, string fieldName, string[] values) + { + if (values.Length == 1) + { + if (TryParseDateTimeFilterValue(values.First(), out DateTime dateValue)) + { + query.Field(fieldName, dateValue); + } + } + else + { + DateTime[] dateValues = values + .Select(value => TryParseDateTimeFilterValue(value, out DateTime dateValue) ? dateValue : (DateTime?)null) + .Where(dateValue => dateValue.HasValue) + .Select(dateValue => dateValue!.Value) + .ToArray(); + + AddGroupedOrFilter(query, fieldName, dateValues); + } + } + + private void ApplyExactStringFilter(IQuery query, string fieldName, string[] values) + { + if (values.Length == 1) + { + query.Field(fieldName, values.First()); + } + else + { + AddGroupedOrFilter(query, fieldName, values); + } + } + + private void ApplyContainsFilter(IQuery query, string fieldName, string[] values) + { + if (values.Length == 1) + { + // The trailing wildcard is added automatically + query.Field(fieldName, (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{values[0]}")); + } + else + { + // The trailing wildcard is added automatically + IExamineValue[] examineValues = values + .Select(value => (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{value}")) + .ToArray(); + query.GroupedOr(new[] { fieldName }, examineValues); + } + } + + private void AddGroupedOrFilter(IQuery query, string fieldName, params T[] values) + where T : struct + { + if (values.Length == 0) + { + return; + } + + var fields = new[] { fieldName }; + query.Group( + nestedQuery => + { + INestedBooleanOperation nestedQueryOperation = + nestedQuery.RangeQuery(fields, values[0], values[0]); + foreach (T value in values.Skip(1)) + { + nestedQueryOperation = nestedQueryOperation.Or().RangeQuery(fields, value, value); + } + + return nestedQueryOperation; + }); + } + + private void AddGroupedOrFilter(IQuery query, string fieldName, params string[] values) + => query.GroupedOr(new[] { fieldName }, values); + + private bool TryParseIntFilterValue(string value, out int intValue) + => int.TryParse(value, out intValue); + + private bool TryParseDateTimeFilterValue(string value, out DateTime dateValue) + => DateTime.TryParse(value, out dateValue); +} diff --git a/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySelectorBuilder.cs b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySelectorBuilder.cs new file mode 100644 index 000000000000..c39f6f3f51b2 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySelectorBuilder.cs @@ -0,0 +1,96 @@ +using Examine; +using Examine.Lucene.Providers; +using Examine.Lucene.Search; +using Examine.Search; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders; + +internal sealed class ApiContentQuerySelectorBuilder +{ + private readonly DeliveryApiSettings _deliveryApiSettings; + private readonly string _fallbackGuidValue; + + public ApiContentQuerySelectorBuilder(DeliveryApiSettings deliveryApiSettings) + { + _deliveryApiSettings = deliveryApiSettings; + + // A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string + // It is set to a random guid since this would be highly unlikely to yield any results + _fallbackGuidValue = Guid.NewGuid().ToString("D"); + } + + public IBooleanOperation Build(SelectorOption selectorOption, IIndex index, string culture, ProtectedAccess protectedAccess, bool preview) + { + // Needed for enabling leading wildcards searches + BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}."); + + IQuery query = searcher.CreateQuery( + IndexTypes.Content, + BooleanOperation.And, + searcher.LuceneAnalyzer, + new LuceneSearchOptions { AllowLeadingWildcard = true }); + + IBooleanOperation selectorOperation = selectorOption.Values.Length == 1 + ? query.Field(selectorOption.FieldName, selectorOption.Values.First()) + : query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values); + + AddCultureQuery(culture, selectorOperation); + + if (_deliveryApiSettings.MemberAuthorizationIsEnabled()) + { + AddProtectedAccessQuery(protectedAccess, selectorOperation); + } + + // when not fetching for preview, make sure the "published" field is "y" + if (preview is false) + { + selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Published, "y"); + } + + return selectorOperation; + } + + private void AddCultureQuery(string culture, IBooleanOperation selectorOperation) => + selectorOperation + .And() + .GroupedOr( + // Item culture must be either the requested culture or "none" + new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, + culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), + "none"); + + private void AddProtectedAccessQuery(ProtectedAccess protectedAccess, IBooleanOperation selectorOperation) + { + var protectedAccessValues = new List(); + if (protectedAccess.MemberKey is not null) + { + protectedAccessValues.Add($"u:{protectedAccess.MemberKey}"); + } + + if (protectedAccess.MemberRoles?.Any() is true) + { + protectedAccessValues.AddRange(protectedAccess.MemberRoles.Select(r => $"r:{r}")); + } + + if (protectedAccessValues.Any()) + { + selectorOperation.And( + inner => inner + .Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n") + .Or(protectedAccessInner => protectedAccessInner + .GroupedOr( + new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess }, + protectedAccessValues.ToArray())), + BooleanOperation.Or); + } + else + { + selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n"); + } + } +} diff --git a/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySortBuilder.cs b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySortBuilder.cs new file mode 100644 index 000000000000..92ed501597a3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Delivery/Services/QueryBuilders/ApiContentQuerySortBuilder.cs @@ -0,0 +1,49 @@ +using Examine.Search; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; + +namespace Umbraco.Cms.Api.Delivery.Services.QueryBuilders; + +internal sealed class ApiContentQuerySortBuilder +{ + private readonly Dictionary _fieldTypes; + private readonly ILogger _logger; + + public ApiContentQuerySortBuilder(Dictionary fieldTypes, ILogger logger) + { + _fieldTypes = fieldTypes; + _logger = logger; + } + + public void Append(IList sortOptions, IOrdering queryOperation) + { + foreach (SortOption sort in sortOptions) + { + if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false) + { + _logger.LogWarning( + "Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.", + sort.FieldName); + continue; + } + + SortType sortType = fieldType switch + { + FieldType.Number => SortType.Int, + FieldType.Date => SortType.Long, + FieldType.StringRaw => SortType.String, + FieldType.StringAnalyzed => SortType.String, + FieldType.StringSortable => SortType.String, + _ => throw new ArgumentOutOfRangeException(nameof(fieldType)) + }; + + queryOperation = sort.Direction switch + { + Direction.Ascending => queryOperation.OrderBy(new SortableField(sort.FieldName, sortType)), + Direction.Descending => queryOperation.OrderByDescending(new SortableField(sort.FieldName, sortType)), + _ => queryOperation + }; + } + } +} diff --git a/src/Umbraco.Core/Constants-WebhookEvents.cs b/src/Umbraco.Core/Constants-WebhookEvents.cs index afd57b5188ef..41b9849f08f1 100644 --- a/src/Umbraco.Core/Constants-WebhookEvents.cs +++ b/src/Umbraco.Core/Constants-WebhookEvents.cs @@ -6,6 +6,57 @@ public static class WebhookEvents { public static class Aliases { + + /// + /// Webhook event alias for content versions deleted + /// + public const string ContentDeletedVersions = "Umbraco.ContentDeletedVersions"; + + /// + /// Webhook event alias for content blueprint saved + /// + public const string ContentSavedBlueprint = "Umbraco.ContentSavedBlueprint"; + + /// + /// Webhook event alias for content blueprint deleted + /// + public const string ContentDeletedBlueprint = "Umbraco.ContentDeletedBlueprint"; + + /// + /// Webhook event alias for content moved into the recycle bin. + /// + public const string ContentMovedToRecycleBin = "Umbraco.ContentMovedToRecycleBin"; + + /// + /// Webhook event alias for content sorted. + /// + public const string ContentSorted = "Umbraco.ContentSorted"; + + /// + /// Webhook event alias for content moved. + /// + public const string ContentMoved = "Umbraco.ContentMoved"; + + /// + /// Webhook event alias for content copied. + /// + public const string ContentCopied = "Umbraco.ContentCopied"; + + /// + /// Webhook event alias for content emptied recycle bin. + /// + public const string ContentEmptiedRecycleBin = "Umbraco.ContentEmptiedRecycleBin"; + + /// + /// Webhook event alias for content rolled back. + /// + public const string ContentRolledBack = "Umbraco.ContentRolledBack"; + + /// + /// Webhook event alias for content saved. + /// + public const string ContentSaved = "Umbraco.ContentSaved"; + /// /// Webhook event alias for content publish. /// diff --git a/src/Umbraco.Core/DeliveryApi/DeliveryApiCompositeIdHandler.cs b/src/Umbraco.Core/DeliveryApi/DeliveryApiCompositeIdHandler.cs new file mode 100644 index 000000000000..a3c1f0fceb77 --- /dev/null +++ b/src/Umbraco.Core/DeliveryApi/DeliveryApiCompositeIdHandler.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Cms.Core.DeliveryApi; + +public class DeliveryApiCompositeIdHandler : IDeliveryApiCompositeIdHandler +{ + public string IndexId(int id, string culture) => $"{id}|{culture}"; + + public DeliveryApiIndexCompositeIdModel Decompose(string indexId) + { + var parts = indexId.Split(Constants.CharArrays.VerticalTab); + if (parts.Length == 2 && int.TryParse(parts[0], out var id)) + { + return new DeliveryApiIndexCompositeIdModel + { + Id = id, + Culture = parts[1], + }; + } + + return new DeliveryApiIndexCompositeIdModel(); + } +} diff --git a/src/Umbraco.Core/DeliveryApi/DeliveryApiIndexCompositeIdModel.cs b/src/Umbraco.Core/DeliveryApi/DeliveryApiIndexCompositeIdModel.cs new file mode 100644 index 000000000000..d3b876b5aac8 --- /dev/null +++ b/src/Umbraco.Core/DeliveryApi/DeliveryApiIndexCompositeIdModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.DeliveryApi; + +public class DeliveryApiIndexCompositeIdModel +{ + public int? Id { get; set; } + + public string? Culture { get; set; } +} diff --git a/src/Umbraco.Core/DeliveryApi/IDeliveryApiCompositeIdHandler.cs b/src/Umbraco.Core/DeliveryApi/IDeliveryApiCompositeIdHandler.cs new file mode 100644 index 000000000000..a472d0f9ffc2 --- /dev/null +++ b/src/Umbraco.Core/DeliveryApi/IDeliveryApiCompositeIdHandler.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.DeliveryApi; + +public interface IDeliveryApiCompositeIdHandler +{ + string IndexId(int id, string culture); + + DeliveryApiIndexCompositeIdModel Decompose(string indexId); +} diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index 0d354b65de80..994297990d6b 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -1,49 +1,69 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides a base class to all artifacts. +/// Provides a base class for all artifacts. /// +/// The UDI type. public abstract class ArtifactBase : IArtifact where TUdi : Udi { + private IEnumerable _dependencies; + private readonly Lazy _checksum; + + /// + /// Initializes a new instance of the class. + /// + /// The UDI. + /// The dependencies. protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) { - Udi = udi ?? throw new ArgumentNullException("udi"); + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); Name = Udi.ToString(); _dependencies = dependencies ?? Enumerable.Empty(); _checksum = new Lazy(GetChecksum); } - private readonly Lazy _checksum; - - private IEnumerable _dependencies; - - protected abstract string GetChecksum(); - + /// Udi IArtifactSignature.Udi => Udi; + /// public TUdi Udi { get; set; } + /// + public IEnumerable Dependencies + { + get => _dependencies; + set => _dependencies = value.OrderBy(x => x.Udi); + } + + /// public string Checksum => _checksum.Value; + /// + public string Name { get; set; } + + /// + public string Alias { get; set; } = string.Empty; + + /// + /// Gets the checksum. + /// + /// + /// The checksum. + /// + protected abstract string GetChecksum(); + /// /// Prevents the property from being serialized. /// + /// + /// Returns false to prevent the property from being serialized. + /// /// - /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] + /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] /// as that would require an external dependency in Umbraco.Cms.Core. /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm /// public bool ShouldSerializeChecksum() => false; - - public IEnumerable Dependencies - { - get => _dependencies; - set => _dependencies = value.OrderBy(x => x.Udi); - } - - public string Name { get; set; } - - public string Alias { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 31c8025ddbac..80a77740d0c9 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,27 +1,17 @@ -using System.Text.Json.Serialization; - namespace Umbraco.Cms.Core.Deploy; /// /// Represents an artifact dependency. /// -/// -/// -/// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. -/// -/// -/// Dependencies have a mode which can be or depending on whether the checksum should match. -/// -/// public class ArtifactDependency { /// /// Initializes a new instance of the class. /// /// The entity identifier of the artifact dependency. - /// A value indicating whether the dependency is ordering. - /// The dependency mode. - /// The checksum. + /// A value indicating whether the dependency must be included when building a dependency tree and ensure the artifact gets deployed in the correct order. + /// A value indicating whether the checksum must match or the artifact just needs to exist. + /// The checksum of the dependency. public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode, string? checksum = null) { Udi = udi; @@ -39,10 +29,10 @@ public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode, s public Udi Udi { get; } /// - /// Gets a value indicating whether the dependency is ordering. + /// Gets a value indicating whether the dependency is included when building a dependency tree and gets deployed in the correct order. /// /// - /// true if the dependency is ordering; otherwise, false. + /// true if the dependency is included when building a dependency tree and gets deployed in the correct order; otherwise, false. /// public bool Ordering { get; } @@ -55,10 +45,10 @@ public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode, s public ArtifactDependencyMode Mode { get; } /// - /// Gets the checksum. + /// Gets or sets the checksum. /// /// /// The checksum. /// - public string? Checksum { get; } + public string? Checksum { get; set; } } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs index 1be524c86f5e..7f2b05eaad01 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -3,42 +3,53 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a collection of distinct . +/// Represents a collection of distinct . /// -/// The collection cannot contain duplicates and modes are properly managed. +/// +/// The collection cannot contain duplicates and modes are properly managed. +/// public class ArtifactDependencyCollection : ICollection { private readonly Dictionary _dependencies = new(); + /// public int Count => _dependencies.Count; - public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public bool IsReadOnly => false; + /// public void Add(ArtifactDependency item) { - if (_dependencies.ContainsKey(item.Udi)) + if (item.Mode == ArtifactDependencyMode.Exist && + _dependencies.TryGetValue(item.Udi, out ArtifactDependency? existingItem) && + existingItem.Mode == ArtifactDependencyMode.Match) { - ArtifactDependency exist = _dependencies[item.Udi]; - if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode) - { - return; - } + // Don't downgrade dependency mode from Match to Exist + return; } _dependencies[item.Udi] = item; } + /// public void Clear() => _dependencies.Clear(); - public bool Contains(ArtifactDependency item) => - _dependencies.ContainsKey(item.Udi) && - (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match); + /// + public bool Contains(ArtifactDependency item) + => _dependencies.TryGetValue(item.Udi, out ArtifactDependency? existingItem) && + // Check whether it has the same or higher dependency mode + (existingItem.Mode == item.Mode || existingItem.Mode == ArtifactDependencyMode.Match); + /// public void CopyTo(ArtifactDependency[] array, int arrayIndex) => _dependencies.Values.CopyTo(array, arrayIndex); - public bool Remove(ArtifactDependency item) => throw new NotSupportedException(); + /// + public bool Remove(ArtifactDependency item) => _dependencies.Remove(item.Udi); - public bool IsReadOnly => false; + /// + public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs index b997b9c75970..6ea6a5e2c29e 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -1,17 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Indicates the mode of the dependency. +/// Indicates the mode of the dependency. /// public enum ArtifactDependencyMode { /// - /// The dependency must match exactly. + /// The dependency must match exactly. /// Match, - /// - /// The dependency must exist. + /// The dependency must exist. /// Exist, } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index e2dd343af1aa..b6a31f2da236 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -1,27 +1,36 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represent the state of an artifact being deployed. +/// Represent the state of an artifact being deployed. /// public abstract class ArtifactDeployState { /// - /// Gets the artifact. + /// Gets the artifact. /// + /// + /// The artifact. + /// public IArtifact Artifact => GetArtifactAsIArtifact(); /// - /// Gets or sets the service connector in charge of deploying the artifact. + /// Gets or sets the service connector in charge of deploying the artifact. /// + /// + /// The connector. + /// public IServiceConnector? Connector { get; set; } /// - /// Gets or sets the next pass number. + /// Gets or sets the next pass number. /// + /// + /// The next pass. + /// public int NextPass { get; set; } /// - /// Creates a new instance of the class from an artifact and an entity. + /// Creates a new instance of the class from an artifact and an entity. /// /// The type of the artifact. /// The type of the entity. @@ -29,24 +38,28 @@ public abstract class ArtifactDeployState /// The entity. /// The service connector deploying the artifact. /// The next pass number. - /// A deploying artifact. + /// + /// A deploying artifact. + /// public static ArtifactDeployState Create(TArtifact art, TEntity? entity, IServiceConnector connector, int nextPass) where TArtifact : IArtifact => new ArtifactDeployState(art, entity, connector, nextPass); /// - /// Gets the artifact as an . + /// Gets the artifact as an . /// - /// The artifact, as an . + /// + /// The artifact, as an . + /// /// - /// This is because classes that inherit from this class cannot override the Artifact property - /// with a property that specializes the return type, and so they need to 'new' the property. + /// This is because classes that inherit from this class cannot override the Artifact property + /// with a property that specializes the return type, and so they need to 'new' the property. /// protected abstract IArtifact GetArtifactAsIArtifact(); } /// -/// Represent the state of an artifact being deployed. +/// Represent the state of an artifact being deployed. /// /// The type of the artifact. /// The type of the entity. @@ -54,7 +67,7 @@ public class ArtifactDeployState : ArtifactDeployState where TArtifact : IArtifact { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The artifact. /// The entity. @@ -69,13 +82,19 @@ public ArtifactDeployState(TArtifact art, TEntity? entity, IServiceConnector con } /// - /// Gets or sets the artifact. + /// Gets or sets the artifact. /// + /// + /// The artifact. + /// public new TArtifact Artifact { get; set; } /// - /// Gets or sets the entity. + /// Gets or sets the entity. /// + /// + /// The entity. + /// public TEntity? Entity { get; set; } /// diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs index 3dccddba2935..c91897355ec4 100644 --- a/src/Umbraco.Core/Deploy/ArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -1,17 +1,27 @@ namespace Umbraco.Cms.Core.Deploy; +/// public sealed class ArtifactSignature : IArtifactSignature { + /// + /// Initializes a new instance of the class. + /// + /// The UDI. + /// The checksum. + /// The artifact dependencies. public ArtifactSignature(Udi udi, string checksum, IEnumerable? dependencies = null) { Udi = udi; Checksum = checksum; - Dependencies = dependencies ?? Enumerable.Empty(); + Dependencies = dependencies ?? Array.Empty(); } + /// public Udi Udi { get; } + /// public string Checksum { get; } + /// public IEnumerable Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs index d704642a9f9d..7bc3ff3055b3 100644 --- a/src/Umbraco.Core/Deploy/Difference.cs +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -1,7 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a difference between two artifacts. +/// public class Difference { + /// + /// Initializes a new instance of the class. + /// + /// The title. + /// The text. + /// The category. public Difference(string title, string? text = null, string? category = null) { Title = title; @@ -9,12 +18,36 @@ public Difference(string title, string? text = null, string? category = null) Category = category; } + /// + /// Gets or sets the title. + /// + /// + /// The title. + /// public string Title { get; set; } + /// + /// Gets or sets the text. + /// + /// + /// The text. + /// public string? Text { get; set; } + /// + /// Gets or sets the category. + /// + /// + /// The category. + /// public string? Category { get; set; } + /// + /// Converts the difference to a . + /// + /// + /// A that represents the difference. + /// public override string ToString() { var s = Title; diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs index 30439380f222..a1129e2e4ce7 100644 --- a/src/Umbraco.Core/Deploy/Direction.cs +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -1,7 +1,16 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents the direction when replacing an attribute value while parsing macros. +/// public enum Direction { + /// + /// Replacing an attribute value while converting to an artifact. + /// ToArtifact, + /// + /// Replacing an attribute value while converting from an artifact. + /// FromArtifact, } diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs index faea983dee8a..6b041b7f82f6 100644 --- a/src/Umbraco.Core/Deploy/IArtifact.cs +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -1,11 +1,23 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents an artifact ie an object that can be transfered between environments. +/// Represents an artifact ie an object that can be transfered between environments. /// public interface IArtifact : IArtifactSignature { + /// + /// Gets the name. + /// + /// + /// The name. + /// string Name { get; } + /// + /// Gets the alias. + /// + /// + /// The alias. + /// string? Alias { get; } } diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs index f1dd35295fcc..ca34fdb894c3 100644 --- a/src/Umbraco.Core/Deploy/IArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -1,46 +1,55 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents the signature of an artifact. +/// Represents the signature of an artifact. /// public interface IArtifactSignature { /// - /// Gets the entity unique identifier of this artifact. + /// Gets the entity unique identifier of this artifact. /// + /// + /// The udi. + /// /// - /// - /// The project identifier is independent from the state of the artifact, its data - /// values, dependencies, anything. It never changes and fully identifies the artifact. - /// - /// - /// What an entity uses as a unique identifier will influence what we can transfer - /// between environments. Eg content type "Foo" on one environment is not necessarily the - /// same as "Foo" on another environment, if guids are used as unique identifiers. What is - /// used should be documented for each entity, along with the consequences of the choice. - /// + /// + /// The project identifier is independent from the state of the artifact, its data + /// values, dependencies, anything. It never changes and fully identifies the artifact. + /// + /// + /// What an entity uses as a unique identifier will influence what we can transfer + /// between environments. Eg content type "Foo" on one environment is not necessarily the + /// same as "Foo" on another environment, if guids are used as unique identifiers. What is + /// used should be documented for each entity, along with the consequences of the choice. + /// /// Udi Udi { get; } /// - /// Gets the checksum of this artifact. + /// Gets the checksum of this artifact. /// + /// + /// The checksum. + /// /// - /// - /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, - /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, - /// or when the list of dependencies changes. But not if one of these dependencies change. - /// - /// - /// It is assumed that checksum collisions cannot happen ie that no two different artifact's - /// states will ever produce the same checksum, so that if two artifacts have the same checksum then - /// they are identical. - /// + /// + /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, + /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, + /// or when the list of dependencies changes. But not if one of these dependencies change. + /// + /// + /// It is assumed that checksum collisions cannot happen ie that no two different artifact's + /// states will ever produce the same checksum, so that if two artifacts have the same checksum then + /// they are identical. + /// /// string Checksum { get; } /// - /// Gets the dependencies of this artifact. + /// Gets the dependencies of this artifact. /// + /// + /// The dependencies. + /// IEnumerable Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index bdc8fd8d61d4..f706cbd3ae19 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,39 +1,56 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a deployment context. +/// Represents a deployment context. /// public interface IDeployContext { /// - /// Gets the unique identifier of the deployment. + /// Gets the unique identifier of the deployment. /// + /// + /// The session identifier. + /// Guid SessionId { get; } /// - /// Gets the file source. + /// Gets the file source. /// - /// The file source is used to obtain files from the source environment. + /// + /// The file source. + /// + /// + /// The file source is used to obtain files from the source environment. + /// IFileSource FileSource { get; } /// - /// Gets items. + /// Gets items. /// + /// + /// The items. + /// IDictionary Items { get; } /// - /// Gets the next number in a numerical sequence. + /// Gets the next number in a numerical sequence. /// - /// The next sequence number. - /// Can be used to uniquely number things during a deployment. + /// + /// The next sequence number. + /// + /// + /// Can be used to uniquely number things during a deployment. + /// int NextSeq(); /// - /// Gets item. + /// Gets item. /// /// The type of the item. /// The key of the item. - /// The item with the specified key and type, if any, else null. + /// + /// The item with the specified key and type, if any, else null. + /// T? Item(string key) where T : class; diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index d3f6ebe77072..92eb125d9e23 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -1,70 +1,86 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Represents a file source, ie a mean for a target environment involved in a -/// deployment to obtain the content of files being deployed. +/// Represents a file source, ie a mean for a target environment involved in a +/// deployment to obtain the content of files being deployed. /// public interface IFileSource { /// - /// Gets the content of a file as a stream. + /// Gets the content of a file as a stream. /// /// A file entity identifier. - /// A stream with read access to the file content. + /// + /// A stream with read access to the file content. + /// /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. /// Stream GetFileStream(StringUdi udi); /// - /// Gets the content of a file as a stream. + /// Gets the content of a file as a stream. /// /// A file entity identifier. /// A cancellation token. - /// A stream with read access to the file content. + /// + /// A stream with read access to the file content. + /// /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. /// Task GetFileStreamAsync(StringUdi udi, CancellationToken token); /// - /// Gets the content of a file as a string. + /// Gets the content of a file as a string. /// /// A file entity identifier. - /// A string containing the file content. - /// Returns null if no content could be read. + /// + /// A string containing the file content. + /// + /// + /// Returns null if no content could be read. + /// string GetFileContent(StringUdi udi); /// - /// Gets the content of a file as a string. + /// Gets the content of a file as a string. /// /// A file entity identifier. /// A cancellation token. - /// A string containing the file content. - /// Returns null if no content could be read. + /// + /// A string containing the file content. + /// + /// + /// Returns null if no content could be read. + /// Task GetFileContentAsync(StringUdi udi, CancellationToken token); /// - /// Gets the length of a file. + /// Gets the length of a file. /// /// A file entity identifier. - /// The length of the file, or -1 if the file does not exist. + /// + /// The length of the file, or -1 if the file does not exist. + /// long GetFileLength(StringUdi udi); /// - /// Gets the length of a file. + /// Gets the length of a file. /// /// A file entity identifier. /// A cancellation token. - /// The length of the file, or -1 if the file does not exist. + /// + /// The length of the file, or -1 if the file does not exist. + /// Task GetFileLengthAsync(StringUdi udi, CancellationToken token); // TODO (V14): Remove obsolete methods and default implementations for GetFiles and GetFilesAsync overloads. /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. @@ -72,32 +88,38 @@ public interface IFileSource void GetFiles(IEnumerable udis, IFileTypeCollection fileTypes); /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. - /// A collection of file types which can store the files. /// A flag indicating whether to continue if a file isn't found or to stop and throw a FileNotFoundException. + /// A collection of file types which can store the files. void GetFiles(IEnumerable udis, bool continueOnFileNotFound, IFileTypeCollection fileTypes) #pragma warning disable CS0618 // Type or member is obsolete => GetFiles(udis, fileTypes); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. /// A cancellation token. + /// + /// The task object representing the asynchronous operation. + /// [Obsolete("Please use the method overload taking all parameters. This method overload will be removed in Umbraco 14.")] Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, CancellationToken token); /// - /// Gets files and store them using a file store. + /// Gets files and store them using a file store. /// /// The UDIs of the files to get. /// A collection of file types which can store the files. /// A flag indicating whether to continue if a file isn't found or to stop and throw a FileNotFoundException. /// A cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, bool continueOnFileNotFound, CancellationToken token) #pragma warning disable CS0618 // Type or member is obsolete => GetFilesAsync(udis, fileTypes, token); diff --git a/src/Umbraco.Core/Deploy/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs index 466c87a3edba..dfd80e2c1e45 100644 --- a/src/Umbraco.Core/Deploy/IFileType.cs +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -1,27 +1,101 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a deployable file type. +/// public interface IFileType { + /// + /// Gets a value indicating whether the file can be set using a physical path. + /// + /// + /// true if the file can be set using a physical path; otherwise, false. + /// bool CanSetPhysical { get; } + /// + /// Gets the stream. + /// + /// The UDI. + /// + /// The stream. + /// Stream GetStream(StringUdi udi); + /// + /// Gets the stream as an asynchronous operation. + /// + /// The UDI. + /// The cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task GetStreamAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the checksum stream. + /// + /// The UDI. + /// + /// The checksum stream. + /// Stream GetChecksumStream(StringUdi udi); + /// + /// Gets the file length in bytes or -1 if not found. + /// + /// The UDI. + /// + /// The file length in bytes or -1 if not found. + /// long GetLength(StringUdi udi); + /// + /// Sets the stream. + /// + /// The UDI. + /// The stream. void SetStream(StringUdi udi, Stream stream); + /// + /// Sets the stream as an asynchronous operation. + /// + /// The UDI. + /// The stream. + /// The cancellation token. + /// + /// The task object representing the asynchronous operation. + /// Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); + /// + /// Sets the physical path of the file. + /// + /// The UDI. + /// The physical path. + /// If set to true copies the file instead of moving. void Set(StringUdi udi, string physicalPath, bool copy = false); - // this is not pretty as *everywhere* in Deploy we take care of ignoring - // the physical path and always rely on Core's virtual IFileSystem but - // Cloud wants to add some of these files to Git and needs the path... + /// + /// Gets the physical path or if not found. + /// + /// The UDI. + /// + /// The physical path or if not found. + /// + /// + /// This is not pretty as *everywhere* in Deploy we take care of ignoring + /// the physical path and always rely on the virtual IFileSystem, + /// but Cloud wants to add some of these files to Git and needs the path... + /// string GetPhysicalPath(StringUdi udi); + /// + /// Gets the virtual path or if not found. + /// + /// The UDI. + /// + /// The virtual path or if not found. + /// string GetVirtualPath(StringUdi udi); } diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs index 2ae2bb4bb919..3fc22192b6aa 100644 --- a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -1,8 +1,48 @@ +using System.Diagnostics.CodeAnalysis; + namespace Umbraco.Cms.Core.Deploy; +/// +/// Represents a collection of deployable file types used for each specific entity type. +/// public interface IFileTypeCollection { + /// + /// Gets the for the specified entity type. + /// + /// + /// The . + /// + /// The entity type. + /// + /// The for the specified entity type. + /// IFileType this[string entityType] { get; } + /// + /// Gets the for the specified entity type. + /// + /// The entity type. + /// When this method returns, contains the file type associated with the specified entity type, if the item is found; otherwise, null. + /// + /// true if the file type associated with the specified entity type was found; otherwise, false. + /// + bool TryGetValue(string entityType, [NotNullWhen(true)] out IFileType? fileType); + + /// + /// Determines whether this collection contains a file type for the specified entity type. + /// + /// The entity type. + /// + /// true if this collection contains a file type for the specified entity type; otherwise, false. + /// bool Contains(string entityType); + + /// + /// Gets the entity types. + /// + /// + /// The entity types. + /// + ICollection GetEntityTypes(); } diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index cbbaa6bc9ab9..861e15dc1b99 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -1,49 +1,67 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides methods to parse image tag sources in property values. +/// Provides methods to parse image tag sources in property values. /// public interface IImageSourceParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. - /// The parsed value. - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + /// The parsed value. + /// + /// + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. /// The context cache. - /// The parsed value. - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + /// The parsed value. + /// + /// + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. - /// The parsed value. - /// Turns umb://media/... into /media/.... + /// + /// The parsed value. + /// + /// + /// Turns umb://media/... into /media/.... + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. /// The context cache. - /// The parsed value. - /// Turns umb://media/... into /media/.... + /// + /// The parsed value. + /// + /// + /// Turns umb://media/... into /media/.... + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index d84bf35af199..9273faa05483 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -1,55 +1,69 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides methods to parse local link tags in property values. +/// Provides methods to parse local link tags in property values. /// public interface ILocalLinkParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. - /// The parsed value. + /// + /// The parsed value. + /// /// - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the - /// dependencies. + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the + /// dependencies. /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// The property value. /// A list of dependencies. /// The context cache. - /// The parsed value. + /// + /// The parsed value. + /// /// - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the - /// dependencies. + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the + /// dependencies. /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. - /// The parsed value. - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + /// The parsed value. + /// + /// + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// The artifact property value. /// The context cache. - /// The parsed value. - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + /// The parsed value. + /// + /// + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs index 17f06992d52a..de14bb528fa6 100644 --- a/src/Umbraco.Core/Deploy/IMacroParser.cs +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -1,65 +1,82 @@ namespace Umbraco.Cms.Core.Deploy; +/// +/// Provides methods to parse macro tags in property values. +/// public interface IMacroParser { /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// Property value. /// A list of dependencies. - /// Parsed value. + /// + /// Parsed value. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ToArtifact(string value, ICollection dependencies); /// - /// Parses an Umbraco property value and produces an artifact property value. + /// Parses an Umbraco property value and produces an artifact property value. /// /// Property value. /// A list of dependencies. /// The context cache. - /// Parsed value. + /// + /// Parsed value. + /// + string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache) => ToArtifact(value, dependencies); + => ToArtifact(value, dependencies); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// Artifact property value. - /// Parsed value. + /// + /// Parsed value. + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string FromArtifact(string value); /// - /// Parses an artifact property value and produces an Umbraco property value. + /// Parses an artifact property value and produces an Umbraco property value. /// /// Artifact property value. /// The context cache. - /// Parsed value. + /// + /// Parsed value. + /// + string FromArtifact(string value, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete - string FromArtifact(string value, IContextCache contextCache) => FromArtifact(value); + => FromArtifact(value); #pragma warning restore CS0618 // Type or member is obsolete /// - /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. /// /// Value to attempt to convert /// Alias of the editor used for the parameter /// Collection to add dependencies to when performing ToArtifact /// Indicates which action is being performed (to or from artifact) - /// Value with converted identifiers + /// + /// Value with converted identifiers + /// [Obsolete("Please use the overload taking all parameters. This method will be removed in Umbraco 14.")] string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); /// - /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. /// /// Value to attempt to convert /// Alias of the editor used for the parameter /// Collection to add dependencies to when performing ToArtifact /// Indicates which action is being performed (to or from artifact) /// The context cache. - /// Value with converted identifiers + /// + /// Value with converted identifiers + /// string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction, IContextCache contextCache) #pragma warning disable CS0618 // Type or member is obsolete => ReplaceAttributeValue(value, editorAlias, dependencies, direction); diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 84617943c6d3..e831ed9e082e 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -5,7 +5,6 @@ namespace Umbraco.Cms.Core.Deploy; /// /// Connects to an Umbraco service. /// -/// public interface IServiceConnector : IDiscoverable { /// diff --git a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs index c68906bbbf1d..3178328e033e 100644 --- a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs @@ -1,24 +1,26 @@ namespace Umbraco.Cms.Core.Deploy; /// -/// Provides a method to retrieve an artifact's unique identifier. +/// Provides a method to retrieve an artifact's unique identifier. /// /// -/// Artifacts are uniquely identified by their , however they represent -/// elements in Umbraco that may be uniquely identified by another value. For example, -/// a content type is uniquely identified by its alias. If someone creates a new content -/// type, and tries to deploy it to a remote environment where a content type with the -/// same alias already exists, both content types end up having different -/// but the same alias. By default, Deploy would fail and throw when trying to save the -/// new content type (duplicate alias). However, if the connector also implements this -/// interface, the situation can be detected beforehand and reported in a nicer way. +/// Artifacts are uniquely identified by their , however they represent +/// elements in Umbraco that may be uniquely identified by another value. For example, +/// a content type is uniquely identified by its alias. If someone creates a new content +/// type, and tries to deploy it to a remote environment where a content type with the +/// same alias already exists, both content types end up having different +/// but the same alias. By default, Deploy would fail and throw when trying to save the +/// new content type (duplicate alias). However, if the connector also implements this +/// interface, the situation can be detected beforehand and reported in a nicer way. /// public interface IUniqueIdentifyingServiceConnector { /// - /// Gets the unique identifier of the specified artifact. + /// Gets the unique identifier of the specified artifact. /// /// The artifact. - /// The unique identifier. + /// + /// The unique identifier. + /// string GetUniqueIdentifier(IArtifact artifact); } diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 572fe05dd4bc..00cb113ddf81 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1941,6 +1941,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create header Logs No webhook headers have been added + No events were found. + Enabled + Events + Event + Url + Types + Webhook key + Retry count Add language diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 33901735eec1..c6c028099742 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2023,6 +2023,14 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Create header Logs No webhook headers have been added + No events were found. + Enabled + Events + Event + Url + Types + Webhook key + Retry count Add language diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml index b30dba4a6910..24da7d0066fa 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml @@ -1585,6 +1585,13 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Ceci n'est pas d'application pour un Type d'Elément Vous avez apporté des modifications à cette propriété. Etes-vous certain.e de vouloir les annuler? + + Créer un webhook + Ajouter un header au webhook + Logs + Ajouter un Type de Document + Ajouter un Type de Media + Ajouter une langue Langue obligatoire @@ -1715,6 +1722,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Configuration Modélisation Parties Tierces + Webhooks Nouvelle mise à jour disponible diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index afeb8ba9fa3b..f88b96c9d662 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -53,6 +53,8 @@ public static async Task Create(IEnumerable che return new HealthCheckResults(results, allChecksSuccessful); } + public static async Task Create(HealthCheck check) => await Create(new List() { check }); + public void LogResults() { Logger.LogInformation("Scheduled health check results:"); diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 4cf5bdd6af87..9bced05ff5a0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -71,6 +71,14 @@ public interface IPublishedPropertyType /// Type ModelClrType { get; } + /// + /// Gets the property model Delivery Api CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// + Type DeliveryApiModelClrType => ModelClrType; + /// /// Gets the property CLR type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 52e33717673e..ed2d19cd6fc8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -24,6 +24,7 @@ public class PublishedPropertyType : IPublishedPropertyType private PropertyCacheLevel _deliveryApiCacheLevelForExpansion; private Type? _modelClrType; + private Type? _deliveryApiModelClrType; private Type? _clrType; #region Constructors @@ -192,17 +193,13 @@ private void InitializeLocked() } } + var deliveryApiPropertyValueConverter = _converter as IDeliveryApiPropertyValueConverter; + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - if (_converter is IDeliveryApiPropertyValueConverter deliveryApiPropertyValueConverter) - { - _deliveryApiCacheLevel = deliveryApiPropertyValueConverter.GetDeliveryApiPropertyCacheLevel(this); - _deliveryApiCacheLevelForExpansion = deliveryApiPropertyValueConverter.GetDeliveryApiPropertyCacheLevelForExpansion(this); - } - else - { - _deliveryApiCacheLevel = _deliveryApiCacheLevelForExpansion = _cacheLevel; - } + _deliveryApiCacheLevel = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyCacheLevel(this) ?? _cacheLevel; + _deliveryApiCacheLevelForExpansion = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyCacheLevelForExpansion(this) ?? _cacheLevel; _modelClrType = _converter?.GetPropertyValueType(this) ?? typeof(object); + _deliveryApiModelClrType = deliveryApiPropertyValueConverter?.GetDeliveryApiPropertyValueType(this) ?? _modelClrType; } /// @@ -352,6 +349,20 @@ public Type ModelClrType } } + /// + public Type DeliveryApiModelClrType + { + get + { + if (!_initialized) + { + Initialize(); + } + + return _deliveryApiModelClrType!; + } + } + /// public Type? ClrType { diff --git a/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs b/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs new file mode 100644 index 000000000000..67df86deb169 --- /dev/null +++ b/src/Umbraco.Core/Notifications/HealthCheckCompletedNotification.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.HealthChecks; + +namespace Umbraco.Cms.Core.Notifications; + +public class HealthCheckCompletedNotification : INotification +{ + public HealthCheckCompletedNotification(HealthCheckResults healthCheckResults) + { + HealthCheckResults = healthCheckResults; + } + + public HealthCheckResults HealthCheckResults { get; } +} diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs index 0f9cc8fca9ba..8abc0906cedc 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace Umbraco.Cms.Core.PublishedCache; /// @@ -6,5 +8,5 @@ namespace Umbraco.Cms.Core.PublishedCache; /// public interface IPublishedSnapshotAccessor { - bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot); + bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot); } diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs index 8f3e4fe8271b..91e32e6db4e8 100644 --- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Web; namespace Umbraco.Cms.Core.PublishedCache; @@ -29,7 +30,7 @@ public IPublishedSnapshot? PublishedSnapshot set => throw new NotSupportedException(); // not ok to set } - public bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) + public bool TryGetPublishedSnapshot([NotNullWhen(true)] out IPublishedSnapshot? publishedSnapshot) { if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs new file mode 100644 index 000000000000..3d6f0163d70b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentCopiedWebhookEvent.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Copied", Constants.WebhookEvents.Types.Content)] +public class ContentCopiedWebhookEvent : WebhookEventBase +{ + public ContentCopiedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentCopied; + + public override object? ConvertNotificationToRequestPayload(ContentCopiedNotification notification) + { + return new + { + notification.Copy, + notification.Original, + notification.ParentId, + notification.RelateToOriginal + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs new file mode 100644 index 000000000000..996b8abd15d9 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedBlueprintWebhookEvent.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Template [Blueprint] Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedBlueprintWebhookEvent : WebhookEventContentBase +{ + public ContentDeletedBlueprintWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentDeletedBlueprint; + + protected override IEnumerable GetEntitiesFromNotification(ContentDeletedBlueprintNotification notification) => + notification.DeletedBlueprints; + + protected override object ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs new file mode 100644 index 000000000000..c543bfe3cad7 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedVersionsWebhookEvent.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Versions Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedVersionsWebhookEvent : WebhookEventBase +{ + public ContentDeletedVersionsWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentDeletedVersions; + + public override object? ConvertNotificationToRequestPayload(ContentDeletedVersionsNotification notification) + { + return new + { + notification.Id, + notification.DeletePriorVersions, + notification.SpecificVersion, + notification.DateToRetain + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs similarity index 77% rename from src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs index 85a1f39ba9d4..128282299566 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentDeleteWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentDeletedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was deleted", Constants.WebhookEvents.Types.Content)] -public class ContentDeleteWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Deleted", Constants.WebhookEvents.Types.Content)] +public class ContentDeletedWebhookEvent : WebhookEventContentBase { - public ContentDeleteWebhookEvent( + public ContentDeletedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs new file mode 100644 index 000000000000..8670d23c49e5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentEmptiedRecycleBinWebhookEvent.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Recycle Bin Emptied", Constants.WebhookEvents.Types.Content)] +public class ContentEmptiedRecycleBinWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentEmptiedRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentEmptiedRecycleBin; + + protected override IEnumerable GetEntitiesFromNotification(ContentEmptiedRecycleBinNotification notification) => + notification.DeletedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs new file mode 100644 index 000000000000..6ed74d8c66e4 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedToRecycleBinWebhookEvent.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Moved to Recycle Bin", Constants.WebhookEvents.Types.Content)] +public class ContentMovedToRecycleBinWebhookEvent : WebhookEventBase +{ + public ContentMovedToRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentMovedToRecycleBin; + + public override object? ConvertNotificationToRequestPayload(ContentMovedToRecycleBinNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs new file mode 100644 index 000000000000..46d487c499e0 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentMovedWebhookEvent.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Moved", Constants.WebhookEvents.Types.Content)] +public class ContentMovedWebhookEvent : WebhookEventBase +{ + public ContentMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentMoved; + + public override object? ConvertNotificationToRequestPayload(ContentMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs similarity index 86% rename from src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs index e1ba7125ec78..5847fb450e6c 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentPublishWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentPublishedWebhookEvent.cs @@ -8,15 +8,15 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was published", Constants.WebhookEvents.Types.Content)] -public class ContentPublishWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Published", Constants.WebhookEvents.Types.Content)] +public class ContentPublishedWebhookEvent : WebhookEventContentBase { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IApiContentBuilder _apiContentBuilder; - public ContentPublishWebhookEvent( + public ContentPublishedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs new file mode 100644 index 000000000000..f38ff571f908 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentRolledBack.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Rolled Back", Constants.WebhookEvents.Types.Content)] +public class ContentRolledBackWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentRolledBackWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentRolledBack; + + protected override IEnumerable GetEntitiesFromNotification(ContentRolledBackNotification notification) => + new List { notification.Entity }; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + // Get preview/saved version of content for a rollback + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(true, entity.Key); + return publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs new file mode 100644 index 000000000000..40630b453f39 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedBlueprintWebhookEvent.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Template [Blueprint] Saved", Constants.WebhookEvents.Types.Content)] +public class ContentSavedBlueprintWebhookEvent : WebhookEventContentBase +{ + public ContentSavedBlueprintWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSavedBlueprint; + + protected override IEnumerable + GetEntitiesFromNotification(ContentSavedBlueprintNotification notification) + => new List { notification.SavedBlueprint }; + + protected override object ConvertEntityToRequestPayload(IContent entity) => entity; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs new file mode 100644 index 000000000000..fac16de822e5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSavedWebhookEvent.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Saved", Constants.WebhookEvents.Types.Content)] +public class ContentSavedWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSaved; + + protected override IEnumerable GetEntitiesFromNotification(ContentSavedNotification notification) => + notification.SavedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + // Get preview/saved version of content + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(true, entity.Key); + return publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs new file mode 100644 index 000000000000..d2a95028e2fd --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentSortedWebhookEvent.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Content; + +[WebhookEvent("Content Sorted", Constants.WebhookEvents.Types.Content)] +public class ContentSortedWebhookEvent : WebhookEventBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public ContentSortedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base( + webhookFiringService, + webhookService, + webhookSettings, + serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => Constants.WebhookEvents.Aliases.ContentSorted; + + public override object? ConvertNotificationToRequestPayload(ContentSortedNotification notification) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + var sortedEntities = new List(); + foreach (var entity in notification.SortedEntities) + { + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(entity.Key); + object? payload = publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + sortedEntities.Add(payload); + } + return sortedEntities; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs similarity index 76% rename from src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs index 76499eb277d5..81245f9b75b2 100644 --- a/src/Umbraco.Core/Webhooks/Events/ContentUnpublishWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Content/ContentUnpublishedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Content; -[WebhookEvent("Content was unpublished", Constants.WebhookEvents.Types.Content)] -public class ContentUnpublishWebhookEvent : WebhookEventContentBase +[WebhookEvent("Content Unpublished", Constants.WebhookEvents.Types.Content)] +public class ContentUnpublishedWebhookEvent : WebhookEventContentBase { - public ContentUnpublishWebhookEvent( + public ContentUnpublishedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs new file mode 100644 index 000000000000..b121c6f394dd --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Deleted")] +public class DataTypeDeletedWebhookEvent : WebhookEventBase +{ + public DataTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(DataTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs new file mode 100644 index 000000000000..03c0954305be --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Moved")] +public class DataTypeMovedWebhookEvent : WebhookEventBase +{ + public DataTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(DataTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs new file mode 100644 index 000000000000..a6e5afe189af --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/DataType/DataTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.DataType; + +[WebhookEvent("Data Type Saved")] +public class DataTypeSavedWebhookEvent : WebhookEventBase +{ + public DataTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dataTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(DataTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs new file mode 100644 index 000000000000..fa3c95cea55b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; + +[WebhookEvent("Dictionary Item Deleted")] +public class DictionaryItemDeletedWebhookEvent : WebhookEventBase +{ + public DictionaryItemDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dictionaryItemDeleted"; + + public override object? ConvertNotificationToRequestPayload(DictionaryItemDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs new file mode 100644 index 000000000000..7edd4e784926 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Dictionary/DictionaryItemSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Dictionary; + +[WebhookEvent("Dictionary Item Saved")] +public class DictionaryItemSavedWebhookEvent : WebhookEventBase +{ + public DictionaryItemSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "dictionaryItemSaved"; + + public override object? ConvertNotificationToRequestPayload(DictionaryItemSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs new file mode 100644 index 000000000000..30605a28de3e --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Domain; + +[WebhookEvent("Domain Deleted")] +public class DomainDeletedWebhookEvent : WebhookEventBase +{ + public DomainDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "domainDeleted"; + + public override object? ConvertNotificationToRequestPayload(DomainDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs new file mode 100644 index 000000000000..5cd80f743b1b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Domain/DomainSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Domain; + +[WebhookEvent("Domain Saved")] +public class DomainSavedWebhookEvent : WebhookEventBase +{ + public DomainSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "domainSaved"; + + public override object? ConvertNotificationToRequestPayload(DomainSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs new file mode 100644 index 000000000000..98ebd4d5c843 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Language/LanguageDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Language; + +[WebhookEvent("Language Deleted")] +public class LanguageDeletedWebhookEvent : WebhookEventBase +{ + public LanguageDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "languageDeleted"; + + public override object? ConvertNotificationToRequestPayload(LanguageDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs new file mode 100644 index 000000000000..ccc52f4245c5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Language/LanguageSavedWebhookEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Language; + +[WebhookEvent("Language Saved")] +public class LanguageSavedWebhookEvent : WebhookEventBase +{ + public LanguageSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "languageSaved"; + + public override object? ConvertNotificationToRequestPayload(LanguageSavedNotification notification) + => notification.SavedEntities; +} + diff --git a/src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs similarity index 78% rename from src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs index ba9fb2333a32..659917652dda 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaDeleteWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaDeletedWebhookEvent.cs @@ -5,12 +5,12 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Media; -[WebhookEvent("Media was deleted", Constants.WebhookEvents.Types.Media)] -public class MediaDeleteWebhookEvent : WebhookEventContentBase +[WebhookEvent("Media Deleted", Constants.WebhookEvents.Types.Media)] +public class MediaDeletedWebhookEvent : WebhookEventContentBase { - public MediaDeleteWebhookEvent( + public MediaDeletedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs new file mode 100644 index 000000000000..29d0e44e4623 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaEmptiedRecycleBinWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Recycle Bin Emptied", Constants.WebhookEvents.Types.Media)] +public class MediaEmptiedRecycleBinWebhookEvent : WebhookEventContentBase +{ + public MediaEmptiedRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaEmptiedRecycleBin"; + + protected override IEnumerable GetEntitiesFromNotification(MediaEmptiedRecycleBinNotification notification) => notification.DeletedEntities; + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs new file mode 100644 index 000000000000..1bd05c6d7b03 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedToRecycleBinWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Moved to Recycle Bin", Constants.WebhookEvents.Types.Media)] +public class MediaMovedToRecycleBinWebhookEvent : WebhookEventContentBase +{ + public MediaMovedToRecycleBinWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaMovedToRecycleBin"; + + protected override IEnumerable GetEntitiesFromNotification(MediaMovedToRecycleBinNotification notification) => notification.MoveInfoCollection.Select(x => x.Entity); + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs new file mode 100644 index 000000000000..60b235f4cc63 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaMovedWebhookEvent.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Media; + +[WebhookEvent("Media Moved", Constants.WebhookEvents.Types.Media)] +public class MediaMovedWebhookEvent : WebhookEventContentBase +{ + public MediaMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base( + webhookFiringService, + webHookService, + webhookSettings, + serverRoleAccessor) + { + } + + public override string Alias => "mediaMoved"; + + protected override IEnumerable GetEntitiesFromNotification(MediaMovedNotification notification) => notification.MoveInfoCollection.Select(x => x.Entity); + + protected override object ConvertEntityToRequestPayload(IMedia entity) => new DefaultPayloadModel { Id = entity.Key }; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs similarity index 68% rename from src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs rename to src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs index cea7181d5137..6c68e8edb146 100644 --- a/src/Umbraco.Core/Webhooks/Events/MediaSaveWebhookEvent.cs +++ b/src/Umbraco.Core/Webhooks/Events/Media/MediaSavedWebhookEvent.cs @@ -8,15 +8,15 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Webhooks.Events; +namespace Umbraco.Cms.Core.Webhooks.Events.Media; -[WebhookEvent("Media was saved", Constants.WebhookEvents.Types.Media)] -public class MediaSaveWebhookEvent : WebhookEventContentBase +[WebhookEvent("Media Saved", Constants.WebhookEvents.Types.Media)] +public class MediaSavedWebhookEvent : WebhookEventContentBase { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IApiMediaBuilder _apiMediaBuilder; - public MediaSaveWebhookEvent( + public MediaSavedWebhookEvent( IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, @@ -29,8 +29,8 @@ public MediaSaveWebhookEvent( webhookSettings, serverRoleAccessor) { - _publishedSnapshotAccessor = publishedSnapshotAccessor; - _apiMediaBuilder = apiMediaBuilder; + this._publishedSnapshotAccessor = publishedSnapshotAccessor; + this._apiMediaBuilder = apiMediaBuilder; } public override string Alias => Constants.WebhookEvents.Aliases.MediaSave; @@ -39,12 +39,12 @@ public MediaSaveWebhookEvent( protected override object? ConvertEntityToRequestPayload(IMedia entity) { - if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + if (this._publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) { return null; } IPublishedContent? publishedContent = publishedSnapshot.Media?.GetById(entity.Key); - return publishedContent is null ? null : _apiMediaBuilder.Build(publishedContent); + return publishedContent is null ? null : this._apiMediaBuilder.Build(publishedContent); } } diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs new file mode 100644 index 000000000000..23decd003229 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeChangedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Changed")] +public class MediaTypeChangedWebhookEvent : WebhookEventBase +{ + public MediaTypeChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeChanged"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeChangedNotification notification) + => notification.Changes; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs new file mode 100644 index 000000000000..7b46967d06bd --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Deleted")] +public class MediaTypeDeletedWebhookEvent : WebhookEventBase +{ + public MediaTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs new file mode 100644 index 000000000000..ae2a35995ea4 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Moved")] +public class MediaTypeMovedWebhookEvent : WebhookEventBase +{ + public MediaTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs new file mode 100644 index 000000000000..818b38f71b17 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MediaType/MediaTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MediaType; + +[WebhookEvent("Media Type Saved")] +public class MediaTypeSavedWebhookEvent : WebhookEventBase +{ + public MediaTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "mediaTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(MediaTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs new file mode 100644 index 000000000000..4812bdcc7368 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/AssignedMemberRolesWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Roles Assigned")] +public class AssignedMemberRolesWebhookEvent : WebhookEventBase +{ + public AssignedMemberRolesWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "assignedMemberRoles"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs new file mode 100644 index 000000000000..3f72587c8fea --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/ExportedMemberWebhookEvent.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Exported")] +public class ExportedMemberWebhookEvent : WebhookEventBase +{ + public ExportedMemberWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "exportedMember"; + + public override object? ConvertNotificationToRequestPayload(ExportedMemberNotification notification) + { + // No need to return the original member in the notification as well + return new + { + exportedMember = notification.Exported + }; + + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs new file mode 100644 index 000000000000..c29e22003f45 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberDeletedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Deleted")] +public class MemberDeletedWebhookEvent : WebhookEventBase +{ + public MemberDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberDeletedNotification notification) + { + // TODO: Map more stuff here + var result = notification.DeletedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.ContentTypeAlias, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs new file mode 100644 index 000000000000..ab41dfed07f5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Group Deleted")] +public class MemberGroupDeletedWebhookEvent : WebhookEventBase +{ + public MemberGroupDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberGroupDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberGroupDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs new file mode 100644 index 000000000000..19d3e7d4b293 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberGroupSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Group Saved")] +public class MemberGroupSavedWebhookEvent : WebhookEventBase +{ + public MemberGroupSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberGroupSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberGroupSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs new file mode 100644 index 000000000000..edb82cb51843 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/MemberSavedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Saved")] +public class MemberSavedWebhookEvent : WebhookEventBase +{ + public MemberSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberSavedNotification notification) + { + // TODO: Map more stuff here + var result = notification.SavedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.ContentTypeAlias, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs new file mode 100644 index 000000000000..8925b240597d --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Member/RemovedMemberRolesWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Member; + +[WebhookEvent("Member Roles Removed")] +public class RemovedMemberRolesWebhookEvent : WebhookEventBase +{ + public RemovedMemberRolesWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "removedMemberRoles"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs new file mode 100644 index 000000000000..68c4424e484b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeChangedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Changed")] +public class MemberTypeChangedWebhookEvent : WebhookEventBase +{ + public MemberTypeChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeChanged"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeChangedNotification notification) + => notification.Changes; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs new file mode 100644 index 000000000000..005143826cae --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Deleted")] +public class MemberTypeDeletedWebhookEvent : WebhookEventBase +{ + public MemberTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs new file mode 100644 index 000000000000..ddef29a0c2f5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeMovedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Moved")] +public class MemberTypeMovedWebhookEvent : WebhookEventBase +{ + public MemberTypeMovedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeMoved"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeMovedNotification notification) + => notification.MoveInfoCollection; +} diff --git a/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs new file mode 100644 index 000000000000..db4bcf8ad4a2 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/MemberType/MemberTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.MemberType; + +[WebhookEvent("Member Type Saved")] +public class MemberTypeSavedWebhookEvent : WebhookEventBase +{ + public MemberTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "memberTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(MemberTypeSavedNotification notification) => + notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs new file mode 100644 index 000000000000..8495dde4c2d9 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Package/ImportedPackageWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Package; + +[WebhookEvent("Package Imported")] +public class ImportedPackageWebhookEvent : WebhookEventBase +{ + public ImportedPackageWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "packageImported"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs new file mode 100644 index 000000000000..00f5a2ca86f7 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntryDeletedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; + +[WebhookEvent("Public Access Entry Deleted")] +public class PublicAccessEntryDeletedWebhookEvent : WebhookEventBase +{ + public PublicAccessEntryDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "publicAccessEntryDeleted"; + + public override object? ConvertNotificationToRequestPayload(PublicAccessEntryDeletedNotification notification) => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs new file mode 100644 index 000000000000..3977db2832b5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/PublicAccess/PublicAccessEntrySavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.PublicAccess; + +[WebhookEvent("Public Access Entry Saved")] +public class PublicAccessEntrySavedWebhookEvent : WebhookEventBase +{ + public PublicAccessEntrySavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "publicAccessEntrySaved"; + + public override object? ConvertNotificationToRequestPayload(PublicAccessEntrySavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs new file mode 100644 index 000000000000..2ade4bd08e7b --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Relation; + +[WebhookEvent("Relation Deleted")] +public class RelationDeletedWebhookEvent : WebhookEventBase +{ + public RelationDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationDeleted"; + + public override object? ConvertNotificationToRequestPayload(RelationDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs new file mode 100644 index 000000000000..4cc775aa219d --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Relation/RelationSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Relation; + +[WebhookEvent("Relation Saved")] +public class RelationSavedWebhookEvent : WebhookEventBase +{ + public RelationSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationSaved"; + + public override object? ConvertNotificationToRequestPayload(RelationSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs new file mode 100644 index 000000000000..41f9619a4165 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeDeletedWebhookEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; + +[WebhookEvent("Relation Type Deleted")] +public class RelationTypeDeletedWebhookEvent : WebhookEventBase +{ + public RelationTypeDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + + public override string Alias => "relationTypeDeleted"; + + public override object? ConvertNotificationToRequestPayload(RelationTypeDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs new file mode 100644 index 000000000000..915e95c6cbb6 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/RelationType/RelationTypeSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.RelationType; + +[WebhookEvent("Relation Type Saved")] +public class RelationTypeSavedWebhookEvent : WebhookEventBase +{ + public RelationTypeSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "relationTypeSaved"; + + public override object? ConvertNotificationToRequestPayload(RelationTypeSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs new file mode 100644 index 000000000000..1dad592cb1ec --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Script/ScriptDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Script; + +[WebhookEvent("Script Deleted")] +public class ScriptDeletedWebhookEvent : WebhookEventBase +{ + public ScriptDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "scriptDeleted"; + + public override object? ConvertNotificationToRequestPayload(ScriptDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs new file mode 100644 index 000000000000..4d026b9f6c3c --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Script/ScriptSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Script; + +[WebhookEvent("Script Saved")] +public class ScriptSavedWebhookEvent : WebhookEventBase +{ + public ScriptSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "scriptSaved"; + + public override object? ConvertNotificationToRequestPayload(ScriptDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs new file mode 100644 index 000000000000..5214dc39cb69 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; + +[WebhookEvent("Stylesheet Deleted")] +public class StylesheetDeletedWebhookEvent : WebhookEventBase +{ + public StylesheetDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "stylesheetDeleted"; + + public override object? ConvertNotificationToRequestPayload(StylesheetDeletedNotification notification) => + notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs new file mode 100644 index 000000000000..a135f701319a --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Stylesheet/StylesheetSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Stylesheet; + +[WebhookEvent("Stylesheet Saved")] +public class StylesheetSavedWebhookEvent : WebhookEventBase +{ + public StylesheetSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "stylesheetSaved"; + + public override object? ConvertNotificationToRequestPayload(StylesheetSavedNotification notification) + => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs new file mode 100644 index 000000000000..1f3fc756fbbf --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Partial View Deleted")] +public class PartialViewDeletedWebhookEvent : WebhookEventBase +{ + public PartialViewDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "partialViewDeleted"; + + public override object? ConvertNotificationToRequestPayload(PartialViewDeletedNotification notification) + => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs new file mode 100644 index 000000000000..224d71c837af --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/PartialViewSavedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Partial View Saved")] +public class PartialViewSavedWebhookEvent : WebhookEventBase +{ + public PartialViewSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "partialViewSaved"; + + public override object? ConvertNotificationToRequestPayload(PartialViewSavedNotification notification) => + notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs new file mode 100644 index 000000000000..2e18dd99323f --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/TemplateDeletedWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Template Deleted")] +public class TemplateDeletedWebhookEvent : WebhookEventBase +{ + public TemplateDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "templateDeleted"; + + public override object? ConvertNotificationToRequestPayload(TemplateDeletedNotification notification) => + notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs new file mode 100644 index 000000000000..3950e7e370e5 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/Template/TemplateSavedWebhookEvent.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.Template; + +[WebhookEvent("Template Saved")] +public class TemplateSavedWebhookEvent : WebhookEventBase +{ + public TemplateSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "templateSaved"; + + public override object? ConvertNotificationToRequestPayload(TemplateSavedNotification notification) + { + // Create a new anonymous object with the properties we want + return new + { + notification.CreateTemplateForContentType, + notification.ContentTypeAlias, + notification.SavedEntities + }; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs new file mode 100644 index 000000000000..1b8b1002bcac --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/AssignedUserGroupPermissionsWebhookEvent.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Permissions Assigned")] +public class AssignedUserGroupPermissionsWebhookEvent : WebhookEventBase +{ + public AssignedUserGroupPermissionsWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "assignedUserGroupPermissions"; + + public override object? ConvertNotificationToRequestPayload(AssignedUserGroupPermissionsNotification notification) + => notification.EntityPermissions; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs new file mode 100644 index 000000000000..3d7a6b2afa02 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserDeletedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Deleted")] +public class UserDeletedWebhookEvent : WebhookEventBase +{ + public UserDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userDeleted"; + + public override object? ConvertNotificationToRequestPayload(UserDeletedNotification notification) + { + // TODO: Map more stuff here + var result = notification.DeletedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.Language, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs new file mode 100644 index 000000000000..0426d7a1e673 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserForgotPasswordRequestedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Forgot Password Requested")] +public class UserForgotPasswordRequestedWebhookEvent : WebhookEventBase +{ + public UserForgotPasswordRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userForgotPasswordRequested"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs new file mode 100644 index 000000000000..7bd15f5a5189 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserForgottenPasswordRequestedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Forgotten Password Requested")] +public class UserForgottenPasswordRequestedWebhookEvent : WebhookEventBase +{ + public UserForgottenPasswordRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userForgottenPasswordRequested"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs new file mode 100644 index 000000000000..c05bacb3779e --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupDeletedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Deleted")] +public class UserGroupDeletedWebhookEvent : WebhookEventBase +{ + public UserGroupDeletedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userGroupDeleted"; + + public override object? ConvertNotificationToRequestPayload(UserGroupDeletedNotification notification) => notification.DeletedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs new file mode 100644 index 000000000000..0bfe438a7b75 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserGroupSavedWebhookEvent.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Group Saved")] +public class UserGroupSavedWebhookEvent : WebhookEventBase +{ + public UserGroupSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userGroupSaved"; + + public override object? ConvertNotificationToRequestPayload(UserGroupSavedNotification notification) => notification.SavedEntities; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs new file mode 100644 index 000000000000..eb3106687f40 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLockedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Locked")] +public class UserLockedWebhookEvent : WebhookEventBase +{ + public UserLockedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLocked"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs new file mode 100644 index 000000000000..e6957d39dcd7 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginFailedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Failed")] +public class UserLoginFailedWebhookEvent : WebhookEventBase +{ + public UserLoginFailedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginFailed"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs new file mode 100644 index 000000000000..a64282715024 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginRequiresVerificationWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Requires Verification")] +public class UserLoginRequiresVerificationWebhookEvent : WebhookEventBase +{ + public UserLoginRequiresVerificationWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginRequiresVerification"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs new file mode 100644 index 000000000000..58b98d8e0775 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLoginSuccessWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Login Success")] +public class UserLoginSuccessWebhookEvent : WebhookEventBase +{ + public UserLoginSuccessWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLoginSuccess"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs new file mode 100644 index 000000000000..dc0f8d7bb232 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserLogoutSuccessWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Logout Success")] +public class UserLogoutSuccessWebhookEvent : WebhookEventBase +{ + public UserLogoutSuccessWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userLogoutSuccess"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs new file mode 100644 index 000000000000..99c654aba383 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordChangedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Password Changed")] +public class UserPasswordChangedWebhookEvent : WebhookEventBase +{ + public UserPasswordChangedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userPasswordChanged"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs new file mode 100644 index 000000000000..368116df0bcd --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserPasswordResetWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Password Reset")] +public class UserPasswordResetWebhookEvent : WebhookEventBase +{ + public UserPasswordResetWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userPasswordReset"; +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs new file mode 100644 index 000000000000..ee65e9b2eb06 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserSavedWebhookEvent.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Saved")] +public class UserSavedWebhookEvent : WebhookEventBase +{ + public UserSavedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userSaved"; + + public override object? ConvertNotificationToRequestPayload(UserSavedNotification notification) + { + // TODO: Map more stuff here + var result = notification.SavedEntities.Select(entity => new + { + entity.Id, + entity.Key, + entity.Name, + entity.Language, + entity.Email, + entity.Username, + entity.FailedPasswordAttempts + }); + + return result; + } +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs new file mode 100644 index 000000000000..afcac294ca82 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserTwoFactorRequestedWebhookEvent.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Two Factor Requested")] +public class UserTwoFactorRequestedWebhookEvent : WebhookEventBase +{ + public UserTwoFactorRequestedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userTwoFactorRequested"; + +} diff --git a/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs new file mode 100644 index 000000000000..a6aea6219695 --- /dev/null +++ b/src/Umbraco.Core/Webhooks/Events/User/UserUnlockedWebhookEvent.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Webhooks.Events.User; + +[WebhookEvent("User Unlocked")] +public class UserUnlockedWebhookEvent : WebhookEventBase +{ + public UserUnlockedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webHookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webHookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "userUnlocked"; +} diff --git a/src/Umbraco.Core/Webhooks/WebhookEventBase.cs b/src/Umbraco.Core/Webhooks/WebhookEventBase.cs index 5d4ee69d3b8d..138ebc56fdc3 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventBase.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventBase.cs @@ -62,7 +62,7 @@ public virtual async Task ProcessWebhooks(TNotification notification, IEnumerabl continue; } - await WebhookFiringService.FireAsync(webhook, Alias, notification, cancellationToken); + await WebhookFiringService.FireAsync(webhook, Alias, ConvertNotificationToRequestPayload(notification), cancellationToken); } } @@ -95,4 +95,14 @@ public async Task HandleAsync(TNotification notification, CancellationToken canc await ProcessWebhooks(notification, webhooks, cancellationToken); } + + /// + /// Use this method if you wish to change the shape of the object to be serialised + /// for the JSON webhook payload. + /// For example excluding sensitive data + /// + /// + /// + public virtual object? ConvertNotificationToRequestPayload(TNotification notification) + => notification; } diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs index e0eeb186e856..9ebc7ee13fb1 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilder.cs @@ -2,35 +2,43 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Webhooks.Events; +using Umbraco.Cms.Core.Webhooks.Events.Content; +using Umbraco.Cms.Core.Webhooks.Events.DataType; +using Umbraco.Cms.Core.Webhooks.Events.Dictionary; +using Umbraco.Cms.Core.Webhooks.Events.Domain; +using Umbraco.Cms.Core.Webhooks.Events.Language; +using Umbraco.Cms.Core.Webhooks.Events.Media; +using Umbraco.Cms.Core.Webhooks.Events.MediaType; +using Umbraco.Cms.Core.Webhooks.Events.Member; +using Umbraco.Cms.Core.Webhooks.Events.MemberType; +using Umbraco.Cms.Core.Webhooks.Events.Package; +using Umbraco.Cms.Core.Webhooks.Events.PublicAccess; +using Umbraco.Cms.Core.Webhooks.Events.Relation; +using Umbraco.Cms.Core.Webhooks.Events.RelationType; +using Umbraco.Cms.Core.Webhooks.Events.Script; +using Umbraco.Cms.Core.Webhooks.Events.Stylesheet; +using Umbraco.Cms.Core.Webhooks.Events.Template; +using Umbraco.Cms.Core.Webhooks.Events.User; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Webhooks; -public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase +public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase { protected override WebhookEventCollectionBuilder This => this; public override void RegisterWith(IServiceCollection services) { // register the collection - services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, ServiceLifetime.Singleton)); + services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, + ServiceLifetime.Singleton)); // register the types RegisterTypes(services); base.RegisterWith(services); } - public WebhookEventCollectionBuilder AddCoreWebhooks() - { - Append(); - Append(); - Append(); - Append(); - Append(); - return this; - } - private void RegisterTypes(IServiceCollection services) { Type[] types = GetRegisteringTypes(GetTypes()).ToArray(); @@ -68,7 +76,8 @@ private void RegisterTypes(IServiceCollection services) { Type[] genericArguments = handlerType.BaseType!.GetGenericArguments(); - Type? notificationType = genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg)); + Type? notificationType = + genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg)); if (notificationType is not null) { @@ -78,4 +87,187 @@ private void RegisterTypes(IServiceCollection services) return null; } + + public WebhookEventCollectionBuilder AddAllAvailableWebhooks() => + this.AddContentWebhooks() + .AddDataTypeWebhooks() + .AddDictionaryWebhooks() + .AddDomainWebhooks() + .AddLanguageWebhooks() + .AddMediaWebhooks() + .AddMemberWebhooks() + .AddMemberTypeWebhooks() + .AddPackageWebhooks() + .AddPublicAccessWebhooks() + .AddRelationWebhooks() + .AddScriptWebhooks() + .AddStylesheetWebhooks() + .AddTemplateWebhooks() + .AddUserWebhooks(); + + public WebhookEventCollectionBuilder AddContentWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddCoreWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDataTypeWebhooks() + { + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDictionaryWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddDomainWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddLanguageWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMediaWebhooks() + { + // Even though these two are in the AddCoreWebhooks() + // The job of the CollectionBuilder should be removing duplicates + // Would allow someone to use .AddCoreWebhooks().AddMediaWebhooks() + // Or if they explicitly they could skip over CoreWebHooks and just add this perhaps + Append(); + Append(); + + Append(); + Append(); + Append(); + + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMemberWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddMemberTypeWebhooks() + { + Append(); + Append(); + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddPackageWebhooks() + { + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddPublicAccessWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddRelationWebhooks() + { + Append(); + Append(); + + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddScriptWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddStylesheetWebhooks() + { + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddTemplateWebhooks() + { + Append(); + Append(); + + Append(); + Append(); + return this; + } + + public WebhookEventCollectionBuilder AddUserWebhooks() + { + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + Append(); + return this; + } } diff --git a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs index c70c9d4da021..a3508a3fedda 100644 --- a/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs +++ b/src/Umbraco.Examine.Lucene/DeliveryApiContentIndex.cs @@ -1,8 +1,11 @@ using Examine; using Examine.Lucene; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -11,16 +14,30 @@ namespace Umbraco.Cms.Infrastructure.Examine; public class DeliveryApiContentIndex : UmbracoExamineIndex { + private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler; private readonly ILogger _logger; + [Obsolete("Use the constructor that takes an IDeliveryApiCompositeIdHandler instead, scheduled for removal in v15")] public DeliveryApiContentIndex( ILoggerFactory loggerFactory, string name, IOptionsMonitor indexOptions, IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState) + : this(loggerFactory, name, indexOptions, hostingEnvironment, runtimeState, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public DeliveryApiContentIndex( + ILoggerFactory loggerFactory, + string name, + IOptionsMonitor indexOptions, + IHostingEnvironment hostingEnvironment, + IRuntimeState runtimeState, + IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler) : base(loggerFactory, name, indexOptions, hostingEnvironment, runtimeState) { + _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler; PublishedValuesOnly = false; EnableDefaultEventHandler = false; @@ -108,18 +125,8 @@ protected override void PerformDeleteFromIndex(IEnumerable itemIds, Acti private (string? ContentId, string? Culture) ParseItemId(string id) { - if (int.TryParse(id, out _)) - { - return (id, null); - } - - var parts = id.Split(Constants.CharArrays.VerticalTab); - if (parts.Length == 2 && int.TryParse(parts[0], out _)) - { - return (parts[0], parts[1]); - } - - return (null, null); + DeliveryApiIndexCompositeIdModel compositeIdModel = _deliveryApiCompositeIdHandler.Decompose(id); + return (compositeIdModel.Id.ToString() ?? id, compositeIdModel.Culture); } protected override void OnTransformingIndexValues(IndexingItemEventArgs e) diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs index ba78af33b4ca..a3dc6ec779de 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJob.cs @@ -1,14 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; @@ -38,9 +42,30 @@ public event EventHandler PeriodChanged private readonly ILogger _logger; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IProfilingLogger _profilingLogger; + private readonly IEventAggregator _eventAggregator; private readonly ICoreScopeProvider _scopeProvider; private HealthChecksSettings _healthChecksSettings; + [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")] + public HealthCheckNotifierJob( + IOptionsMonitor healthChecksSettings, + HealthCheckCollection healthChecks, + HealthCheckNotificationMethodCollection notifications, + ICoreScopeProvider scopeProvider, + ILogger logger, + IProfilingLogger profilingLogger, + ICronTabParser cronTabParser) + : this( + healthChecksSettings, + healthChecks, + notifications, + scopeProvider, + logger, + profilingLogger, + cronTabParser, + StaticServiceProvider.Instance.GetRequiredService()) + { } + /// /// Initializes a new instance of the class. /// @@ -58,7 +83,8 @@ public HealthCheckNotifierJob( ICoreScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger, - ICronTabParser cronTabParser) + ICronTabParser cronTabParser, + IEventAggregator eventAggregator) { _healthChecksSettings = healthChecksSettings.CurrentValue; _healthChecks = healthChecks; @@ -66,6 +92,7 @@ public HealthCheckNotifierJob( _scopeProvider = scopeProvider; _logger = logger; _profilingLogger = profilingLogger; + _eventAggregator = eventAggregator; Period = healthChecksSettings.CurrentValue.Notification.Period; Delay = DelayCalculator.GetDelay(healthChecksSettings.CurrentValue.Notification.FirstRunTime, cronTabParser, logger, TimeSpan.FromMinutes(3)); @@ -106,6 +133,8 @@ public async Task RunJobAsync() HealthCheckResults results = await HealthCheckResults.Create(checks); results.LogResults(); + _eventAggregator.Publish(new HealthCheckCompletedNotification(results)); + // Send using registered notification methods that are enabled. foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) { diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs index 0e56bfa2b198..e86b8c64bdc8 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/RecurringBackgroundJobHostedServiceRunner.cs @@ -1,12 +1,5 @@ -using System.Linq; -using System.Threading; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Runtime; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Sync; -using Umbraco.Cms.Infrastructure.ModelsBuilder; namespace Umbraco.Cms.Infrastructure.BackgroundJobs; @@ -18,7 +11,7 @@ public class RecurringBackgroundJobHostedServiceRunner : IHostedService private readonly ILogger _logger; private readonly List _jobs; private readonly Func _jobFactory; - private IList _hostedServices = new List(); + private readonly List _hostedServices = new(); public RecurringBackgroundJobHostedServiceRunner( @@ -33,49 +26,61 @@ public RecurringBackgroundJobHostedServiceRunner( public async Task StartAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Creating recurring background jobs hosted services"); - - // create hosted services for each background job - _hostedServices = _jobs.Select(_jobFactory).ToList(); - _logger.LogInformation("Starting recurring background jobs hosted services"); - foreach (IHostedService hostedService in _hostedServices) + foreach (IRecurringBackgroundJob job in _jobs) { + var jobName = job.GetType().Name; try { - _logger.LogInformation($"Starting background hosted service for {hostedService.GetType().Name}"); + + _logger.LogDebug("Creating background hosted service for {job}", jobName); + IHostedService hostedService = _jobFactory(job); + + _logger.LogInformation("Starting background hosted service for {job}", jobName); await hostedService.StartAsync(cancellationToken).ConfigureAwait(false); + + _hostedServices.Add(new NamedServiceJob(jobName, hostedService)); } catch (Exception exception) { - _logger.LogError(exception, $"Failed to start background hosted service for {hostedService.GetType().Name}"); + _logger.LogError(exception, "Failed to start background hosted service for {job}", jobName); } } _logger.LogInformation("Completed starting recurring background jobs hosted services"); - - } public async Task StopAsync(CancellationToken stoppingToken) { _logger.LogInformation("Stopping recurring background jobs hosted services"); - foreach (IHostedService hostedService in _hostedServices) + foreach (NamedServiceJob namedServiceJob in _hostedServices) { try { - _logger.LogInformation($"Stopping background hosted service for {hostedService.GetType().Name}"); - await hostedService.StopAsync(stoppingToken).ConfigureAwait(false); + _logger.LogInformation("Stopping background hosted service for {job}", namedServiceJob.Name); + await namedServiceJob.HostedService.StopAsync(stoppingToken).ConfigureAwait(false); } catch (Exception exception) { - _logger.LogError(exception, $"Failed to stop background hosted service for {hostedService.GetType().Name}"); + _logger.LogError(exception, "Failed to stop background hosted service for {job}", namedServiceJob.Name); } } _logger.LogInformation("Completed stopping recurring background jobs hosted services"); + } + + private class NamedServiceJob + { + public NamedServiceJob(string name, IHostedService hostedService) + { + Name = name; + HostedService = hostedService; + } + + public string Name { get; } + public IHostedService HostedService { get; } } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 1dff6e7eebc6..754c0e349eea 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -162,6 +162,9 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be // discovered when CoreBootManager configures the converters. We will remove the basic one defined @@ -367,7 +370,9 @@ public static IUmbracoBuilder AddCoreNotifications(this IUmbracoBuilder builder) .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler(); + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); // add notification handlers for redirect tracking builder diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index 5139cba48fec..19aa10312393 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -57,6 +58,7 @@ public static IUmbracoBuilder AddExamine(this IUmbracoBuilder builder) builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddUnique(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs index 32dc801dd3d0..0cdfc10db6bc 100644 --- a/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs +++ b/src/Umbraco.Infrastructure/Examine/Deferred/DeliveryApiContentIndexHandleContentTypeChanges.cs @@ -1,6 +1,9 @@ using Examine; using Examine.Search; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; @@ -18,19 +21,33 @@ internal sealed class DeliveryApiContentIndexHandleContentTypeChanges : Delivery private readonly IDeliveryApiContentIndexValueSetBuilder _deliveryApiContentIndexValueSetBuilder; private readonly IContentService _contentService; private readonly IBackgroundTaskQueue _backgroundTaskQueue; + private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler; + [Obsolete("Use the constructor that takes an IDeliveryApiCompositeIdHandler instead, scheduled for removal in v15")] public DeliveryApiContentIndexHandleContentTypeChanges( IList> changes, DeliveryApiIndexingHandler deliveryApiIndexingHandler, IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder, IContentService contentService, IBackgroundTaskQueue backgroundTaskQueue) + : this(changes, deliveryApiIndexingHandler, deliveryApiContentIndexValueSetBuilder, contentService, backgroundTaskQueue, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public DeliveryApiContentIndexHandleContentTypeChanges( + IList> changes, + DeliveryApiIndexingHandler deliveryApiIndexingHandler, + IDeliveryApiContentIndexValueSetBuilder deliveryApiContentIndexValueSetBuilder, + IContentService contentService, + IBackgroundTaskQueue backgroundTaskQueue, + IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler) { _changes = changes; _deliveryApiIndexingHandler = deliveryApiIndexingHandler; _deliveryApiContentIndexValueSetBuilder = deliveryApiContentIndexValueSetBuilder; _contentService = contentService; _backgroundTaskQueue = backgroundTaskQueue; + _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler; } public void Execute() => _backgroundTaskQueue.QueueBackgroundWorkItem(_ => @@ -79,10 +96,13 @@ private void HandleUpdatedContentTypes(IEnumerable updatedContentTypesIds, var indexIdsByContentIds = indexIds .Select(id => { - var parts = id.Split(Constants.CharArrays.VerticalTab); - return parts.Length == 2 && int.TryParse(parts[0], out var contentId) - ? (ContentId: contentId, IndexId: id) - : throw new InvalidOperationException($"Delivery API identifier should be composite of ID and culture, got: {id}"); + DeliveryApiIndexCompositeIdModel compositeIdModel = _deliveryApiCompositeIdHandler.Decompose(id); + if (compositeIdModel.Id is null) + { + throw new InvalidOperationException($"Delivery API identifier should be composite of ID and culture, got: {id}"); + } + + return (ContentId: compositeIdModel.Id.Value, IndexId: compositeIdModel.Culture!); }) .GroupBy(tuple => tuple.ContentId) .ToDictionary( diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs deleted file mode 100644 index 2bfd3d6f80ba..000000000000 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexUtilites.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Infrastructure.Examine; - -internal static class DeliveryApiContentIndexUtilites -{ - public static string IndexId(IContent content, string culture) => $"{content.Id}|{culture}"; -} diff --git a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs index abf19b6bfef0..e8226d994c22 100644 --- a/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs +++ b/src/Umbraco.Infrastructure/Examine/DeliveryApiContentIndexValueSetBuilder.cs @@ -1,9 +1,11 @@ using Examine; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -18,8 +20,10 @@ internal sealed class DeliveryApiContentIndexValueSetBuilder : IDeliveryApiConte private readonly ILogger _logger; private readonly IDeliveryApiContentIndexFieldDefinitionBuilder _deliveryApiContentIndexFieldDefinitionBuilder; private readonly IMemberService _memberService; + private readonly IDeliveryApiCompositeIdHandler _deliveryApiCompositeIdHandler; private DeliveryApiSettings _deliveryApiSettings; + [Obsolete("Please use ctor that takes an IDeliveryApiCompositeIdHandler. Scheduled for removal in v15")] public DeliveryApiContentIndexValueSetBuilder( ContentIndexHandlerCollection contentIndexHandlerCollection, IContentService contentService, @@ -28,12 +32,34 @@ public DeliveryApiContentIndexValueSetBuilder( IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder, IOptionsMonitor deliveryApiSettings, IMemberService memberService) + : this( + contentIndexHandlerCollection, + contentService, + publicAccessService, + logger, + deliveryApiContentIndexFieldDefinitionBuilder, + deliveryApiSettings, + memberService, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public DeliveryApiContentIndexValueSetBuilder( + ContentIndexHandlerCollection contentIndexHandlerCollection, + IContentService contentService, + IPublicAccessService publicAccessService, + ILogger logger, + IDeliveryApiContentIndexFieldDefinitionBuilder deliveryApiContentIndexFieldDefinitionBuilder, + IOptionsMonitor deliveryApiSettings, + IMemberService memberService, + IDeliveryApiCompositeIdHandler deliveryApiCompositeIdHandler) { _contentIndexHandlerCollection = contentIndexHandlerCollection; _publicAccessService = publicAccessService; _logger = logger; _deliveryApiContentIndexFieldDefinitionBuilder = deliveryApiContentIndexFieldDefinitionBuilder; _memberService = memberService; + _deliveryApiCompositeIdHandler = deliveryApiCompositeIdHandler; _contentService = contentService; _deliveryApiSettings = deliveryApiSettings.CurrentValue; deliveryApiSettings.OnChange(settings => _deliveryApiSettings = settings); @@ -73,7 +99,7 @@ public IEnumerable GetValueSets(params IContent[] contents) AddContentIndexHandlerFields(content, culture, fieldDefinitions, indexValues); - yield return new ValueSet(DeliveryApiContentIndexUtilites.IndexId(content, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues); + yield return new ValueSet(_deliveryApiCompositeIdHandler.IndexId(content.Id, indexCulture), IndexTypes.Content, content.ContentType.Alias, indexValues); } } } diff --git a/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs index 9d89b416430b..6644ee7fb52e 100644 --- a/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs +++ b/src/Umbraco.Infrastructure/Models/Blocks/BlockListEditorDataConverter.cs @@ -6,15 +6,32 @@ namespace Umbraco.Cms.Core.Models.Blocks; /// -/// Data converter for the block list property editor +/// Handles the conversion of data for the block list property editor. /// public class BlockListEditorDataConverter : BlockEditorDataConverter { + /// + /// Initializes a new instance of the class with a default alias. + /// public BlockListEditorDataConverter() : base(Constants.PropertyEditors.Aliases.BlockList) { } + /// + /// Initializes a new instance of the class with a provided alias. + /// + /// The alias of the property editor. + public BlockListEditorDataConverter(string propertyEditorAlias) + : base(propertyEditorAlias) + { + } + + /// + /// Extracts block references from the provided JSON layout. + /// + /// The JSON layout containing the block references. + /// A collection of objects extracted from the JSON layout. protected override IEnumerable? GetBlockReferences(JToken jsonLayout) { IEnumerable? blockListLayout = jsonLayout.ToObject>(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 194383560e53..a2f56165184d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -35,16 +35,22 @@ protected BlockListPropertyEditorBase(IDataValueEditorFactory dataValueEditorFac public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory; - #region Value Editor + /// + /// Instantiates a new for use with the block list editor property value editor. + /// + /// A new instance of . + protected virtual BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(); + protected override IDataValueEditor CreateValueEditor() => - DataValueEditorFactory.Create(Attribute!); + DataValueEditorFactory.Create(Attribute!, CreateBlockEditorDataConverter()); internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor { public BlockListEditorPropertyValueEditor( DataEditorAttribute attribute, + BlockEditorDataConverter blockEditorDataConverter, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, @@ -56,7 +62,7 @@ public BlockListEditorPropertyValueEditor( IPropertyValidationService propertyValidationService) : base(attribute, propertyEditors, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper) { - BlockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, logger); + BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, contentTypeService, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService)); Validators.Add(new MinMaxValidator(BlockEditorValues, textService)); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs new file mode 100644 index 000000000000..3edd1a680ee8 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockEditorPropertyValueConstructorCacheBase.cs @@ -0,0 +1,22 @@ +using System.Diagnostics.CodeAnalysis; +using Umbraco.Cms.Core.Models.Blocks; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +public abstract class BlockEditorPropertyValueConstructorCacheBase + where T : IBlockReference +{ + private readonly + Dictionary<(Guid, Guid?), Func> + _constructorCache = new(); + + public bool TryGetValue((Guid ContentTypeKey, Guid? SettingsTypeKey) key, [MaybeNullWhen(false)] out Func value) + => _constructorCache.TryGetValue(key, out value); + + public void SetValue((Guid ContentTypeKey, Guid? SettingsTypeKey) key, Func value) + => _constructorCache[key] = value; + + public void Clear() + => _constructorCache.Clear(); +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs new file mode 100644 index 000000000000..61543810131a --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConstructorCache.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Models.Blocks; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +public class BlockGridPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase +{ +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs index d0e1e2ba1976..6ca6c30b3c47 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueConverter.cs @@ -21,8 +21,9 @@ public class BlockGridPropertyValueConverter : PropertyValueConverterBase, IDeli private readonly BlockEditorConverter _blockConverter; private readonly IJsonSerializer _jsonSerializer; private readonly IApiElementBuilder _apiElementBuilder; + private readonly BlockGridPropertyValueConstructorCache _constructorCache; - [Obsolete("Please use non-obsolete cconstrutor. This will be removed in Umbraco 14.")] + [Obsolete("Please use non-obsolete construtor. This will be removed in Umbraco 14.")] public BlockGridPropertyValueConverter( IProfilingLogger proflog, BlockEditorConverter blockConverter, @@ -32,16 +33,28 @@ public BlockGridPropertyValueConverter( } + [Obsolete("Please use non-obsolete construtor. This will be removed in Umbraco 15.")] public BlockGridPropertyValueConverter( IProfilingLogger proflog, BlockEditorConverter blockConverter, IJsonSerializer jsonSerializer, IApiElementBuilder apiElementBuilder) + : this(proflog, blockConverter, jsonSerializer, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public BlockGridPropertyValueConverter( + IProfilingLogger proflog, + BlockEditorConverter blockConverter, + IJsonSerializer jsonSerializer, + IApiElementBuilder apiElementBuilder, + BlockGridPropertyValueConstructorCache constructorCache) { _proflog = proflog; _blockConverter = blockConverter; _jsonSerializer = jsonSerializer; _apiElementBuilder = apiElementBuilder; + _constructorCache = constructorCache; } /// @@ -129,7 +142,7 @@ ApiBlockGridArea CreateApiBlockGridArea(BlockGridArea area) return null; } - var creator = new BlockGridPropertyValueCreator(_blockConverter, _jsonSerializer); + var creator = new BlockGridPropertyValueCreator(_blockConverter, _jsonSerializer, _constructorCache); return creator.CreateBlockModel(referenceCacheLevel, intermediateBlockModelValue, preview, configuration.Blocks, configuration.GridColumns); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs index b50d95f5c304..9c7d7418fb03 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockGridPropertyValueCreator.cs @@ -7,10 +7,14 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; internal class BlockGridPropertyValueCreator : BlockPropertyValueCreatorBase { private readonly IJsonSerializer _jsonSerializer; + private readonly BlockGridPropertyValueConstructorCache _constructorCache; - public BlockGridPropertyValueCreator(BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer) + public BlockGridPropertyValueCreator(BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer, BlockGridPropertyValueConstructorCache constructorCache) : base(blockEditorConverter) - => _jsonSerializer = jsonSerializer; + { + _jsonSerializer = jsonSerializer; + _constructorCache = constructorCache; + } public BlockGridModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, string intermediateBlockModelValue, bool preview, BlockGridConfiguration.BlockGridBlockConfiguration[] blockConfigurations, int? gridColumns) { @@ -55,11 +59,12 @@ public BlockGridModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, s protected override BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockGridEditorDataConverter(_jsonSerializer); - protected override BlockItemActivator CreateBlockItemActivator() => new BlockGridItemActivator(BlockEditorConverter); + protected override BlockItemActivator CreateBlockItemActivator() => new BlockGridItemActivator(BlockEditorConverter, _constructorCache); private class BlockGridItemActivator : BlockItemActivator { - public BlockGridItemActivator(BlockEditorConverter blockConverter) : base(blockConverter) + public BlockGridItemActivator(BlockEditorConverter blockConverter, BlockGridPropertyValueConstructorCache constructorCache) + : base(blockConverter, constructorCache) { } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs new file mode 100644 index 000000000000..b07185a9544e --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConstructorCache.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Models.Blocks; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +public class BlockListPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase +{ +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index 03445094c14a..49562b07a00f 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -23,6 +23,7 @@ public class BlockListPropertyValueConverter : PropertyValueConverterBase, IDeli private readonly IProfilingLogger _proflog; private readonly BlockEditorConverter _blockConverter; private readonly IApiElementBuilder _apiElementBuilder; + private readonly BlockListPropertyValueConstructorCache _constructorCache; [Obsolete("Use the constructor that takes all parameters, scheduled for removal in V14")] public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) @@ -36,12 +37,19 @@ public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConv { } + [Obsolete("Use the constructor that takes all parameters, scheduled for removal in V15")] public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder) + : this(proflog, blockConverter, contentTypeService, apiElementBuilder, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter, IContentTypeService contentTypeService, IApiElementBuilder apiElementBuilder, BlockListPropertyValueConstructorCache constructorCache) { _proflog = proflog; _blockConverter = blockConverter; _contentTypeService = contentTypeService; _apiElementBuilder = apiElementBuilder; + _constructorCache = constructorCache; } /// @@ -153,7 +161,7 @@ public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) return null; } - var creator = new BlockListPropertyValueCreator(_blockConverter); + var creator = new BlockListPropertyValueCreator(_blockConverter, _constructorCache); return creator.CreateBlockModel(referenceCacheLevel, intermediateBlockModelValue, preview, configuration.Blocks); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs index 952dc43e2ffd..daa390241ca0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueCreator.cs @@ -4,10 +4,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; internal class BlockListPropertyValueCreator : BlockPropertyValueCreatorBase { - public BlockListPropertyValueCreator(BlockEditorConverter blockEditorConverter) - : base(blockEditorConverter) - { - } + private readonly BlockListPropertyValueConstructorCache _constructorCache; + + public BlockListPropertyValueCreator(BlockEditorConverter blockEditorConverter, BlockListPropertyValueConstructorCache constructorCache) + : base(blockEditorConverter) => + _constructorCache = constructorCache; public BlockListModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, string intermediateBlockModelValue, bool preview, BlockListConfiguration.BlockConfiguration[] blockConfigurations) { @@ -22,11 +23,12 @@ public BlockListModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, s protected override BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(); - protected override BlockItemActivator CreateBlockItemActivator() => new BlockListItemActivator(BlockEditorConverter); + protected override BlockItemActivator CreateBlockItemActivator() => new BlockListItemActivator(BlockEditorConverter, _constructorCache); private class BlockListItemActivator : BlockItemActivator { - public BlockListItemActivator(BlockEditorConverter blockConverter) : base(blockConverter) + public BlockListItemActivator(BlockEditorConverter blockConverter, BlockListPropertyValueConstructorCache constructorCache) + : base(blockConverter, constructorCache) { } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs index 84a82338db3e..ef5195808260 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockPropertyValueCreatorBase.cs @@ -216,17 +216,19 @@ private TBlockModel CreateBlockModel( // Cache constructors locally (it's tied to the current IPublishedSnapshot and IPublishedModelFactory) protected abstract class BlockItemActivator + where T : IBlockReference { protected abstract Type GenericItemType { get; } private readonly BlockEditorConverter _blockConverter; - private readonly - Dictionary<(Guid, Guid?), Func> - _constructorCache = new(); + private readonly BlockEditorPropertyValueConstructorCacheBase _constructorCache; - public BlockItemActivator(BlockEditorConverter blockConverter) - => _blockConverter = blockConverter; + public BlockItemActivator(BlockEditorConverter blockConverter, BlockEditorPropertyValueConstructorCacheBase constructorCache) + { + _blockConverter = blockConverter; + _constructorCache = constructorCache; + } public T CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentUdi, IPublishedElement contentData, Udi? settingsUdi, IPublishedElement? settingsData) { @@ -234,8 +236,8 @@ public T CreateInstance(Guid contentTypeKey, Guid? settingsTypeKey, Udi contentU (contentTypeKey, settingsTypeKey), out Func? constructor)) { - constructor = _constructorCache[(contentTypeKey, settingsTypeKey)] = - EmitConstructor(contentTypeKey, settingsTypeKey); + constructor = EmitConstructor(contentTypeKey, settingsTypeKey); + _constructorCache.SetValue((contentTypeKey, settingsTypeKey), constructor); } return constructor(contentUdi, contentData, settingsUdi, settingsData); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs new file mode 100644 index 000000000000..2abbfe73e0be --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ConstructorCacheClearNotificationHandler.cs @@ -0,0 +1,38 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +public class ConstructorCacheClearNotificationHandler : + INotificationHandler, + INotificationHandler +{ + private readonly BlockListPropertyValueConstructorCache _blockListPropertyValueConstructorCache; + private readonly BlockGridPropertyValueConstructorCache _blockGridPropertyValueConstructorCache; + private readonly RichTextBlockPropertyValueConstructorCache _richTextBlockPropertyValueConstructorCache; + + public ConstructorCacheClearNotificationHandler( + BlockListPropertyValueConstructorCache blockListPropertyValueConstructorCache, + BlockGridPropertyValueConstructorCache blockGridPropertyValueConstructorCache, + RichTextBlockPropertyValueConstructorCache richTextBlockPropertyValueConstructorCache) + { + _blockListPropertyValueConstructorCache = blockListPropertyValueConstructorCache; + _blockGridPropertyValueConstructorCache = blockGridPropertyValueConstructorCache; + _richTextBlockPropertyValueConstructorCache = richTextBlockPropertyValueConstructorCache; + } + + public void Handle(ContentTypeCacheRefresherNotification notification) + => ClearCaches(); + + public void Handle(DataTypeCacheRefresherNotification notification) + => ClearCaches(); + + private void ClearCaches() + { + // must clear the block item constructor caches whenever content types and data types change, + // otherwise InMemoryAuto generated models will not work. + _blockListPropertyValueConstructorCache.Clear(); + _blockGridPropertyValueConstructorCache.Clear(); + _richTextBlockPropertyValueConstructorCache.Clear(); + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs new file mode 100644 index 000000000000..f6458df23ff1 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueConstructorCache.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Models.Blocks; + +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +public class RichTextBlockPropertyValueConstructorCache : BlockEditorPropertyValueConstructorCacheBase +{ +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs index b4ff9510f1ad..4616f0342585 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RichTextBlockPropertyValueCreator.cs @@ -7,10 +7,11 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; internal class RichTextBlockPropertyValueCreator : BlockPropertyValueCreatorBase { - public RichTextBlockPropertyValueCreator(BlockEditorConverter blockEditorConverter) + private readonly RichTextBlockPropertyValueConstructorCache _constructorCache; + + public RichTextBlockPropertyValueCreator(BlockEditorConverter blockEditorConverter, RichTextBlockPropertyValueConstructorCache constructorCache) : base(blockEditorConverter) - { - } + => _constructorCache = constructorCache; public RichTextBlockModel CreateBlockModel(PropertyCacheLevel referenceCacheLevel, BlockValue blockValue, bool preview, RichTextConfiguration.RichTextBlockConfiguration[] blockConfigurations) { @@ -25,11 +26,12 @@ public RichTextBlockModel CreateBlockModel(PropertyCacheLevel referenceCacheLeve protected override BlockEditorDataConverter CreateBlockEditorDataConverter() => new RichTextEditorBlockDataConverter(); - protected override BlockItemActivator CreateBlockItemActivator() => new RichTextBlockItemActivator(BlockEditorConverter); + protected override BlockItemActivator CreateBlockItemActivator() => new RichTextBlockItemActivator(BlockEditorConverter, _constructorCache); private class RichTextBlockItemActivator : BlockItemActivator { - public RichTextBlockItemActivator(BlockEditorConverter blockConverter) : base(blockConverter) + public RichTextBlockItemActivator(BlockEditorConverter blockConverter, RichTextBlockPropertyValueConstructorCache constructorCache) + : base(blockConverter, constructorCache) { } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 871595b3bc2b..06ec417c4cd9 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -46,6 +46,7 @@ public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter, IDel private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private readonly IApiElementBuilder _apiElementBuilder; + private readonly RichTextBlockPropertyValueConstructorCache _constructorCache; private DeliveryApiSettings _deliveryApiSettings; [Obsolete("Please use the constructor that takes all arguments. Will be removed in V14.")] @@ -79,6 +80,7 @@ public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAcc StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), StaticServiceProvider.Instance.GetRequiredService>(), deliveryApiSettingsMonitor ) @@ -89,7 +91,7 @@ public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAcc HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser, HtmlImageSourceParser imageSourceParser, IApiRichTextElementParser apiRichTextElementParser, IApiRichTextMarkupParser apiRichTextMarkupParser, IPartialViewBlockEngine partialViewBlockEngine, BlockEditorConverter blockEditorConverter, IJsonSerializer jsonSerializer, - IApiElementBuilder apiElementBuilder, ILogger logger, + IApiElementBuilder apiElementBuilder, RichTextBlockPropertyValueConstructorCache constructorCache, ILogger logger, IOptionsMonitor deliveryApiSettingsMonitor) { _umbracoContextAccessor = umbracoContextAccessor; @@ -103,6 +105,7 @@ public RteMacroRenderingValueConverter(IUmbracoContextAccessor umbracoContextAcc _blockEditorConverter = blockEditorConverter; _jsonSerializer = jsonSerializer; _apiElementBuilder = apiElementBuilder; + _constructorCache = constructorCache; _logger = logger; _deliveryApiSettings = deliveryApiSettingsMonitor.CurrentValue; deliveryApiSettingsMonitor.OnChange(settings => _deliveryApiSettings = settings); @@ -267,7 +270,7 @@ private string RenderRteMacros(string source, bool preview) return null; } - var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter); + var creator = new RichTextBlockPropertyValueCreator(_blockEditorConverter, _constructorCache); return creator.CreateBlockModel(referenceCacheLevel, blocks, preview, configuration.Blocks); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 134a89372fd9..65d7c442f2c7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -11,13 +11,13 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Grid; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -145,7 +145,6 @@ public async Task Default() return await RenderDefaultOrProcessExternalLoginAsync( result, - () => defaultView, () => defaultView); } @@ -172,7 +171,6 @@ public async Task Login() return await RenderDefaultOrProcessExternalLoginAsync( result, - () => View(viewPath), () => View(viewPath)); } @@ -460,11 +458,9 @@ public async Task ExternalLinkLoginCallback() /// private async Task RenderDefaultOrProcessExternalLoginAsync( AuthenticateResult authenticateResult, - Func defaultResponse, - Func externalSignInResponse) + Func defaultResponse) { ArgumentNullException.ThrowIfNull(defaultResponse); - ArgumentNullException.ThrowIfNull(externalSignInResponse); ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment)); @@ -481,23 +477,35 @@ private async Task RenderDefaultOrProcessExternalLoginAsync( // First check if there's external login info, if there's not proceed as normal ExternalLoginInfo? loginInfo = await _signInManager.GetExternalLoginInfoAsync(); - if (loginInfo == null || loginInfo.Principal == null) + if (loginInfo != null) { - // if the user is not logged in, check if there's any auto login redirects specified - if (!authenticateResult.Succeeded) - { - var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); - if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace()) - { - return ExternalLogin(oauthRedirectAuthProvider!); - } - } + // we're just logging in with an external source, not linking accounts + return await ExternalSignInAsync(loginInfo, defaultResponse); + } + // If we are authenticated then we can just render the default view + if (authenticateResult.Succeeded) + { return defaultResponse(); } - // we're just logging in with an external source, not linking accounts - return await ExternalSignInAsync(loginInfo, externalSignInResponse); + // If the user is not logged in, check if there's any auto login redirects specified + var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider(); + + // If there's no auto login provider specified, then we'll render the default view + if (oauthRedirectAuthProvider.IsNullOrWhiteSpace()) + { + return defaultResponse(); + } + + // If the ?logout=true query string is not specified, then we'll redirect to the external login provider + // which will then redirect back to the ExternalLoginCallback action + if (Request.Query.TryGetValue("logout", out StringValues logout) == false || logout != "true") + { + return ExternalLogin(oauthRedirectAuthProvider); + } + + return defaultResponse(); } private async Task ExternalSignInAsync(ExternalLoginInfo loginInfo, Func response) diff --git a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs index 9744ed8c5f82..1880b26cedf0 100644 --- a/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs +++ b/src/Umbraco.Web.BackOffice/HealthChecks/HealthCheckController.cs @@ -3,11 +3,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Authorization; @@ -24,14 +28,27 @@ public class HealthCheckController : UmbracoAuthorizedJsonController private readonly HealthCheckCollection _checks; private readonly IList _disabledCheckIds; private readonly ILogger _logger; + private readonly IEventAggregator _eventAggregator; + private readonly HealthChecksSettings _healthChecksSettings; /// /// Initializes a new instance of the class. /// + [Obsolete("Use constructor that accepts IEventAggregator as a parameter, scheduled for removal in V14")] public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings) + : this(checks, logger, healthChecksSettings, StaticServiceProvider.Instance.GetRequiredService()) + { } + + /// + /// Initializes a new instance of the class. + /// + [ActivatorUtilitiesConstructor] + public HealthCheckController(HealthCheckCollection checks, ILogger logger, IOptions healthChecksSettings, IEventAggregator eventAggregator) { _checks = checks ?? throw new ArgumentNullException(nameof(checks)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _eventAggregator = eventAggregator ?? throw new ArgumentException(nameof(eventAggregator)); + _healthChecksSettings = healthChecksSettings?.Value ?? throw new ArgumentException(nameof(healthChecksSettings)); HealthChecksSettings healthCheckConfig = healthChecksSettings.Value ?? throw new ArgumentNullException(nameof(healthChecksSettings)); @@ -80,6 +97,16 @@ public async Task GetStatus(Guid id) { _logger.LogDebug("Running health check: " + check.Name); } + + if (!_healthChecksSettings.Notification.Enabled) + { + return await check.GetStatus(); + } + + HealthCheckResults results = await HealthCheckResults.Create(check); + _eventAggregator.Publish(new HealthCheckCompletedNotification(results)); + + return await check.GetStatus(); } catch (Exception ex) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs index a6e4e96d8b2f..cc1fc0c4bc33 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgery.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; @@ -20,25 +22,35 @@ namespace Umbraco.Cms.Web.BackOffice.Security; public class BackOfficeAntiforgery : IBackOfficeAntiforgery { private readonly IAntiforgery _internalAntiForgery; - private GlobalSettings _globalSettings; + private readonly CookieBuilder _angularCookieBuilder; + [Obsolete($"Please use the constructor that accepts {nameof(ILoggerFactory)}. Will be removed in V14.")] public BackOfficeAntiforgery(IOptionsMonitor globalSettings) + : this(globalSettings, NullLoggerFactory.Instance) + { } + + public BackOfficeAntiforgery(IOptionsMonitor globalSettings, ILoggerFactory loggerFactory) { + CookieSecurePolicy cookieSecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; + // NOTE: This is the only way to create a separate IAntiForgery service :( // Everything in netcore is internal. I have logged an issue here https://github.com/dotnet/aspnetcore/issues/22217 // but it will not be handled so we have to revert to this. - var services = new ServiceCollection(); - services.AddLogging(); - services.AddAntiforgery(x => - { - x.HeaderName = Constants.Web.AngularHeadername; - x.Cookie.Name = Constants.Web.CsrfValidationCookieName; - x.Cookie.SecurePolicy = globalSettings.CurrentValue.UseHttps ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; - }); - ServiceProvider container = services.BuildServiceProvider(); - _internalAntiForgery = container.GetRequiredService(); - _globalSettings = globalSettings.CurrentValue; - globalSettings.OnChange(x => _globalSettings = x); + _internalAntiForgery = new ServiceCollection() + .AddSingleton(loggerFactory) + .AddAntiforgery(x => + { + x.HeaderName = Constants.Web.AngularHeadername; + x.Cookie.Name = Constants.Web.CsrfValidationCookieName; + x.Cookie.SecurePolicy = cookieSecurePolicy; + }) + .BuildServiceProvider() + .GetRequiredService(); + + // Configure cookie builder using defaults from antiforgery options + _angularCookieBuilder = new AntiforgeryOptions().Cookie; + _angularCookieBuilder.HttpOnly = false; // Needs to be accessed from JavaScript + _angularCookieBuilder.SecurePolicy = cookieSecurePolicy; } /// @@ -68,15 +80,6 @@ public void GetAndStoreTokens(HttpContext httpContext) // We need to set 2 cookies: // The cookie value that angular will use to set a header value on each request - we need to manually set this here // The validation cookie value generated by the anti-forgery helper that we validate the header token against - set above in GetAndStoreTokens - httpContext.Response.Cookies.Append( - Constants.Web.AngularCookieName, - set.RequestToken, - new CookieOptions - { - Path = "/", - //must be js readable - HttpOnly = false, - Secure = _globalSettings.UseHttps - }); + httpContext.Response.Cookies.Append(Constants.Web.AngularCookieName, set.RequestToken, _angularCookieBuilder.Build(httpContext)); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html index e65a5f767f41..e87674791c36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/eventpicker/eventpicker.html @@ -2,29 +2,55 @@ - + + + - - +
+ + +
- - -
    -
  • -
    - -
    -
  • -
+ + +
+
    +
  • +
    + +
    +
  • +
+
+ + + No events were found. +
diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html index 9b1a59a0db49..3a92c04a56b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html @@ -3,11 +3,11 @@ - Webhook key - Date - Url - Event - Retry count + Webhook key + Date + Url + Event + Retry count diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html index 70d2a0be0584..44e69cd49985 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/webhooks.html @@ -17,10 +17,10 @@ - - - - + + + + diff --git a/templates/.editorconfig b/templates/.editorconfig new file mode 100644 index 000000000000..8e7e638d6883 --- /dev/null +++ b/templates/.editorconfig @@ -0,0 +1,2 @@ +# Do not inherit customized EditorConfig for template content +root = true diff --git a/templates/Directory.Build.props b/templates/Directory.Build.props index c4e40e3e066a..5e5e16699f0a 100644 --- a/templates/Directory.Build.props +++ b/templates/Directory.Build.props @@ -1,4 +1,4 @@ - - + + diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 6a4d0460d113..ae01c56af98f 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -12,56 +12,55 @@ false - - - - - - UmbracoProject\Program.cs - UmbracoProject - - - UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension) - UmbracoProject\Views\Partials\blocklist - - - UmbracoProject\Views\Partials\grid\%(RecursiveDir)%(Filename)%(Extension) - UmbracoProject\Views\Partials\grid - - - UmbracoProject\Views\Partials\blockgrid\%(RecursiveDir)%(Filename)%(Extension) - UmbracoProject\Views\Partials\blockgrid - - - UmbracoProject\Views\_ViewImports.cshtml - UmbracoProject\Views - - - UmbracoProject\wwwroot\favicon.ico - UmbracoProject\wwwroot - - + + + + + UmbracoProject\Program.cs + UmbracoProject + + + UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension) + UmbracoProject\Views\Partials\blocklist + + + UmbracoProject\Views\Partials\grid\%(RecursiveDir)%(Filename)%(Extension) + UmbracoProject\Views\Partials\grid + + + UmbracoProject\Views\Partials\blockgrid\%(RecursiveDir)%(Filename)%(Extension) + UmbracoProject\Views\Partials\blockgrid + + + UmbracoProject\Views\_ViewImports.cshtml + UmbracoProject\Views + + + UmbracoProject\wwwroot\favicon.ico + UmbracoProject\wwwroot + + - - - - - - - - - - <_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" /> - <_TemplateJsonFiles> - $(IntermediateOutputPath)%(RelativeDir)%(Filename)%(Extension) - - - - - - <_PackageFiles Include="%(_TemplateJsonFiles.DestinationFile)"> - %(_TemplateJsonFiles.RelativeDir) - - - + + + + + + + + + + <_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" /> + <_TemplateJsonFiles> + $(IntermediateOutputPath)%(RelativeDir)%(Filename)%(Extension) + + + + + + <_PackageFiles Include="%(_TemplateJsonFiles.DestinationFile)"> + %(_TemplateJsonFiles.RelativeDir) + + + diff --git a/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json index 6473c5c643b5..6b943b03c583 100644 --- a/templates/UmbracoPackage/.template.config/dotnetcli.host.json +++ b/templates/UmbracoPackage/.template.config/dotnetcli.host.json @@ -2,17 +2,21 @@ "$schema": "https://json.schemastore.org/dotnetcli.host.json", "symbolInfo": { "Framework": { - "longName": "Framework", - "shortName": "F", + "longName": "framework", + "shortName": "f", "isHidden": true }, "UmbracoVersion": { "longName": "version", - "shortName": "v" + "shortName": "" }, "SkipRestore": { "longName": "no-restore", "shortName": "" + }, + "SupportPagesAndViews": { + "longName": "support-pages-and-views", + "shortName": "s" } } } diff --git a/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json index 0464cfeb1f10..6d9872c7447e 100644 --- a/templates/UmbracoPackage/.template.config/ide.host.json +++ b/templates/UmbracoPackage/.template.config/ide.host.json @@ -4,12 +4,17 @@ "icon": "../../icon.png", "description": { "id": "UmbracoPackage", - "text": "Umbraco Package - An empty Umbraco CMS package/plugin." + "text": "Umbraco Package - An empty Umbraco package/plugin." }, "symbolInfo": [ { "id": "UmbracoVersion", "isVisible": true + }, + { + "id": "SupportPagesAndViews", + "isVisible": true, + "persistenceScope": "templateGroup" } ] } diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json index 33ec0699b1c9..6124457d399a 100644 --- a/templates/UmbracoPackage/.template.config/template.json +++ b/templates/UmbracoPackage/.template.config/template.json @@ -9,7 +9,7 @@ "Plugin" ], "name": "Umbraco Package", - "description": "An empty Umbraco package/plugin project ready to get started.", + "description": "An empty Umbraco package/plugin.", "groupIdentity": "Umbraco.Templates.UmbracoPackage", "identity": "Umbraco.Templates.UmbracoPackage.CSharp", "shortName": "umbracopackage", @@ -51,39 +51,12 @@ "datatype": "bool", "defaultValue": "false" }, - "Namespace": { - "type": "derived", - "valueSource": "name", - "valueTransform": "safe_namespace", - "fileRename": "UmbracoPackage", - "replaces": "UmbracoPackage" - }, - "MsBuildName": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "" - }, - { - "regex": "\\.", - "replacement": "" - }, - { - "regex": "-", - "replacement": "" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "" - } - ] - }, - "replaces": "UmbracoPackageMsBuild" + "SupportPagesAndViews": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Support pages and views", + "description": "Whether to support adding traditional Razor pages and Views to this library." } }, "primaryOutputs": [ diff --git a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest deleted file mode 100644 index db8d6385fc29..000000000000 --- a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/package.manifest +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true -} diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj index 98f5bac3ad7d..0bec6e45516b 100644 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ b/templates/UmbracoPackage/UmbracoPackage.csproj @@ -1,23 +1,27 @@ - - - net8.0 - . - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package - UmbracoPackage - false - + + + net8.0 + enable + enable + true + UmbracoPackage + App_Plugins/UmbracoPackage + - - - - + + UmbracoPackage + UmbracoPackage + UmbracoPackage + ... + umbraco plugin package + - - - - + + + + + + + + diff --git a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets b/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets deleted file mode 100644 index 4c376ac97b36..000000000000 --- a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets +++ /dev/null @@ -1,21 +0,0 @@ - - - $(MSBuildThisFileDirectory)..\App_Plugins\UmbracoPackage\**\*.* - - - - - - - - - - - - - - - - - - diff --git a/templates/UmbracoPackage/wwwroot/package.manifest b/templates/UmbracoPackage/wwwroot/package.manifest new file mode 100644 index 000000000000..519d15d145bb --- /dev/null +++ b/templates/UmbracoPackage/wwwroot/package.manifest @@ -0,0 +1,5 @@ +{ + "id": "UmbracoPackage", + "name": "UmbracoPackage", + "allowPackageTelemetry": true +} diff --git a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json b/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json deleted file mode 100644 index 9a960b348e93..000000000000 --- a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/dotnetcli.host.json", - "symbolInfo": { - "Framework": { - "longName": "Framework", - "shortName": "F", - "isHidden": true - }, - "UmbracoVersion": { - "longName": "version", - "shortName": "" - }, - "SkipRestore": { - "longName": "no-restore", - "shortName": "" - }, - "SupportPagesAndViews": { - "longName": "support-pages-and-views", - "shortName": "s" - } - } -} diff --git a/templates/UmbracoPackageRcl/.template.config/ide.host.json b/templates/UmbracoPackageRcl/.template.config/ide.host.json deleted file mode 100644 index 8e630f1e99f8..000000000000 --- a/templates/UmbracoPackageRcl/.template.config/ide.host.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/ide.host.json", - "order": 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackageRcl", - "text": "Umbraco Package RCL - An empty Umbraco package/plugin (Razor Class Library)." - }, - "symbolInfo": [ - { - "id": "UmbracoVersion", - "isVisible": true - }, - { - "id": "SupportPagesAndViews", - "isVisible": true, - "persistenceScope": "templateGroup" - } - ] -} diff --git a/templates/UmbracoPackageRcl/.template.config/template.json b/templates/UmbracoPackageRcl/.template.config/template.json deleted file mode 100644 index c887e8902cfa..000000000000 --- a/templates/UmbracoPackageRcl/.template.config/template.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/template.json", - "author": "Umbraco HQ", - "classifications": [ - "Web", - "CMS", - "Umbraco", - "Package", - "Plugin", - "Razor Class Library" - ], - "name": "Umbraco Package RCL", - "description": "An empty Umbraco package/plugin (Razor Class Library).", - "groupIdentity": "Umbraco.Templates.UmbracoPackageRcl", - "identity": "Umbraco.Templates.UmbracoPackageRcl.CSharp", - "shortName": "umbracopackage-rcl", - "tags": { - "language": "C#", - "type": "project" - }, - "sourceName": "UmbracoPackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "symbols": { - "Framework": { - "displayName": "Framework", - "description": "The target framework for the project.", - "type": "parameter", - "datatype": "choice", - "choices": [ - { - "displayName": ".NET 8.0", - "description": "Target net8.0", - "choice": "net8.0" - } - ], - "defaultValue": "net8.0", - "replaces": "net8.0" - }, - "UmbracoVersion": { - "displayName": "Umbraco version", - "description": "The version of Umbraco.Cms to add as PackageReference.", - "type": "parameter", - "datatype": "string", - "defaultValue": "*", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "SkipRestore": { - "displayName": "Skip restore", - "description": "If specified, skips the automatic restore of the project on create.", - "type": "parameter", - "datatype": "bool", - "defaultValue": "false" - }, - "SupportPagesAndViews": { - "type": "parameter", - "datatype": "bool", - "defaultValue": "false", - "displayName": "Support pages and views", - "description": "Whether to support adding traditional Razor pages and Views to this library." - } - }, - "primaryOutputs": [ - { - "path": "UmbracoPackage.csproj" - } - ], - "postActions": [ - { - "id": "restore", - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/templates/UmbracoPackageRcl/UmbracoPackage.csproj b/templates/UmbracoPackageRcl/UmbracoPackage.csproj deleted file mode 100644 index 1cbdd209e505..000000000000 --- a/templates/UmbracoPackageRcl/UmbracoPackage.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - net8.0 - enable - enable - true - UmbracoPackage - App_Plugins/UmbracoPackage - - - - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package - - - - - - - - - - - diff --git a/templates/UmbracoPackageRcl/wwwroot/package.manifest b/templates/UmbracoPackageRcl/wwwroot/package.manifest deleted file mode 100644 index db8d6385fc29..000000000000 --- a/templates/UmbracoPackageRcl/wwwroot/package.manifest +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true -} diff --git a/templates/UmbracoProject/.template.config/dotnetcli.host.json b/templates/UmbracoProject/.template.config/dotnetcli.host.json index dfd6f80184b7..5756990f2404 100644 --- a/templates/UmbracoProject/.template.config/dotnetcli.host.json +++ b/templates/UmbracoProject/.template.config/dotnetcli.host.json @@ -2,13 +2,13 @@ "$schema": "https://json.schemastore.org/dotnetcli.host.json", "symbolInfo": { "Framework": { - "longName": "Framework", - "shortName": "F", + "longName": "framework", + "shortName": "f", "isHidden": true }, "UmbracoVersion": { "longName": "version", - "shortName": "v" + "shortName": "" }, "UseHttpsRedirect": { "longName": "use-https-redirect", @@ -53,11 +53,6 @@ "NoNodesViewPath": { "longName": "no-nodes-view-path", "shortName": "" - }, - "PackageProjectName": { - "longName": "PackageTestSiteName", - "shortName": "p", - "isHidden": true } }, "usageExamples": [ diff --git a/templates/UmbracoProject/.template.config/template.json b/templates/UmbracoProject/.template.config/template.json index b17352476ea5..91994a7fdf0c 100644 --- a/templates/UmbracoProject/.template.config/template.json +++ b/templates/UmbracoProject/.template.config/template.json @@ -236,14 +236,6 @@ "type": "computed", "value": "(NoNodesViewPath != '')" }, - "PackageProjectName": { - "displayName": "Umbraco package project name", - "description": "The name of the package project this should be a test site for.", - "type": "parameter", - "datatype": "string", - "defaultValue": "", - "replaces": "PACKAGE_PROJECT_NAME_FROM_TEMPLATE" - }, "Namespace": { "type": "derived", "valueSource": "name", diff --git a/templates/UmbracoProject/Properties/launchSettings.json b/templates/UmbracoProject/Properties/launchSettings.json index 97fdc9f7f517..7b80bdaa38a9 100644 --- a/templates/UmbracoProject/Properties/launchSettings.json +++ b/templates/UmbracoProject/Properties/launchSettings.json @@ -23,7 +23,7 @@ "applicationUrl": "https://localhost:HTTPS_PORT_FROM_TEMPLATE;http://localhost:HTTP_PORT_FROM_TEMPLATE", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + } } } } diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index 1c530223ada2..e2a8ad73464c 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -1,35 +1,29 @@ - - net8.0 - enable - enable - Umbraco.Cms.Web.UI - false - + + net8.0 + enable + enable + Umbraco.Cms.Web.UI + - - - + + + - - - - - + + + + + - - - true - + + + true + - - - false - false - - - - - - + + + false + false + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs index 89379322ee96..2d65ff50933d 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockGridPropertyValueConverterTests.cs @@ -37,7 +37,8 @@ private BlockGridPropertyValueConverter CreateConverter() Mock.Of(), new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory), new JsonNetSerializer(), - new ApiElementBuilder(Mock.Of())); + new ApiElementBuilder(Mock.Of()), + new BlockGridPropertyValueConstructorCache()); return editor; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index f0971ffee6cd..423231eeeed8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -27,7 +27,8 @@ private BlockListPropertyValueConverter CreateConverter() Mock.Of(), new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory), Mock.Of(), - new ApiElementBuilder(Mock.Of())); + new ApiElementBuilder(Mock.Of()), + new BlockListPropertyValueConstructorCache()); return editor; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index d88a9689abf0..67dd5162bf12 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -1,8 +1,9 @@ -using System.Linq; +using System.Linq; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -34,9 +35,10 @@ public void SetUp() _dataValueEditorFactoryMock .Setup(m => - m.Create(It.IsAny())) + m.Create(It.IsAny(), It.IsAny())) .Returns(() => new BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor( new DataEditorAttribute("a", "b", "c"), + new BlockListEditorDataConverter(), _propertyEditorCollection, Mock.Of(), Mock.Of(), @@ -105,7 +107,7 @@ public void GetValueEditor_Not_Reusable_Value_Editor_Is_Not_Reused_When_Created_ Assert.NotNull(dataValueEditor2); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny()), + m => m.Create(It.IsAny(), It.IsAny()), Times.Exactly(2)); } @@ -128,7 +130,7 @@ public void GetValueEditor_Not_Reusable_Value_Editor_Is_Not_Reused_When_Created_ Assert.AreEqual("config", ((DataValueEditor)dataValueEditor2).Configuration); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny()), + m => m.Create(It.IsAny(), It.IsAny()), Times.Exactly(2)); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs index cf9883603b11..316605a04e3a 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackgroundJobs/Jobs/HealthCheckNotifierJobTests.cs @@ -10,6 +10,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Logging; @@ -105,7 +106,8 @@ private HealthCheckNotifierJob CreateHealthCheckNotifier( mockScopeProvider.Object, mockLogger.Object, mockProfilingLogger.Object, - Mock.Of()); + Mock.Of(), + Mock.Of()); } private void VerifyNotificationsNotSent() => VerifyNotificationsSentTimes(Times.Never());
EnabledEventsUrlTypesEnabledEventsUrlTypes