From de2529f44d9d03ecbe6d2cf88c969f22dd6ba370 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 4 Mar 2025 10:23:49 +0100 Subject: [PATCH 1/8] Updated copy and code sample relating to allowed at root and allowed children content type filters --- .../reference/content-type-filters.md | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/15/umbraco-cms/reference/content-type-filters.md b/15/umbraco-cms/reference/content-type-filters.md index cbfa5fc4dfb..d7645c16351 100644 --- a/15/umbraco-cms/reference/content-type-filters.md +++ b/15/umbraco-cms/reference/content-type-filters.md @@ -1,5 +1,5 @@ --- -description: Describes how to use Content Type Filters to restrict the allowed content options available to editors. +description: Describes how to use Allowed Content Type Filters to restrict the allowed content options available to editors. --- # Filtering Allowed Content Types @@ -23,27 +23,45 @@ There are two methods you can implement: * One for filtering the content types allowed at the content root * One for the content types allowed below a given parent node. -If you don't want to filter using one of the two approaches, you can return the provided collection unmodified. +If you don't want to filter using one of the two approaches, you can omit the implementation of that method. The default implementation will return the provided collection unmodified. ### Example Use Case -The following example shows a typical use case. Often websites will have a "Home Page" Document Type which is created at the root. Normally, only one of these is required. You can enforce that using the following Content Type Filter. +The following example shows an illustrative but also typical use case. Often websites will have a "Home Page" Document Type which is created at the root. Normally, only one of these is required. You can enforce that using the following Content Type Filter. -The code below is querying the existing content available at the root. Normally you can create a "Home Page" here, but if one already exists that option is removed: +The code below is querying the existing content available at the root. Normally you can create a "Home Page" here, but if one already exists that option is removed. + +It then shows how to limit the allowed children by only permitting a single "Landing Page" under the "Home Page" Document Type. ```csharp -internal class OneHomePageOnlyContentTypeFilter : IContentTypeFilter +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Filters; + +internal class MyContentTypeFilter : IContentTypeFilter { + private const string HomePageDocTypeAlias = "homePage"; + private const string LandingPageDocTypeAlias = "landingPage"; + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IIdKeyMap _idKeyMap; - public OneHomePageOnlyContentTypeFilter(IContentService contentService) => _contentService = contentService; + public MyContentTypeFilter(IContentService contentService, IContentTypeService contentTypeService, IIdKeyMap idKeyMap) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + _idKeyMap = idKeyMap; + } public Task> FilterAllowedAtRootAsync(IEnumerable contentTypes) where TItem : IContentTypeComposition { + // Only allow one home page at the root. var docTypeAliasesToExclude = new List(); - const string HomePageDocTypeAlias = "homePage"; var docTypeAliasesAtRoot = _contentService.GetRootContent() .Select(x => x.ContentType.Alias) .Distinct() @@ -57,12 +75,47 @@ internal class OneHomePageOnlyContentTypeFilter : IContentTypeFilter .Where(x => docTypeAliasesToExclude.Contains(x.Alias) is false)); } - public Task> FilterAllowedChildrenAsync(IEnumerable contentTypes, Guid parentKey) - => Task.FromResult(contentTypes); + public Task> FilterAllowedChildrenAsync( + IEnumerable contentTypes, + Guid parentContentTypeKey, + Guid? parentContentKey) + { + // Only allow one landing page when under a home page. + if (parentContentKey.HasValue is false) + { + return Task.FromResult(contentTypes); + } + + IContentType? docType = _contentTypeService.Get(parentContentTypeKey); + if (docType is null || docType.Alias != HomePageDocTypeAlias) + { + return Task.FromResult(contentTypes); + } + + var docTypeAliasesToExclude = new List(); + + Attempt parentContentIdAttempt = _idKeyMap.GetIdForKey(parentContentKey.Value, UmbracoObjectTypes.Document); + if (parentContentIdAttempt.Success is false) + { + return Task.FromResult(contentTypes); + } + + var docTypeAliasesAtFolder = _contentService.GetPagedChildren(parentContentIdAttempt.Result, 0, int.MaxValue, out _) + .Select(x => x.ContentType.Alias) + .Distinct() + .ToList(); + if (docTypeAliasesAtFolder.Contains(LandingPageDocTypeAlias)) + { + docTypeAliasesToExclude.Add(LandingPageDocTypeAlias); + } + + return Task.FromResult(contentTypes + .Where(x => docTypeAliasesToExclude.Contains(x.Alias) is false)); + } } ``` -Content Type Filters are registered as a collection, making it possible to have more than one in the solution or an installed package. +Allowed Content Type Filters are registered as a collection, making it possible to have more than one in the solution or an installed package. The filters need to be registered in a composer: @@ -72,7 +125,7 @@ public class MyComposer : IComposer public void Compose(IUmbracoBuilder builder) { builder.ContentTypeFilters() - .Append(); + .Append(); } } ``` From a00582e4d6d149a15685d7f25bdd5a5f2fa42f48 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 13 Mar 2025 13:50:43 +0100 Subject: [PATCH 2/8] Added details of multiple segments per document --- .../request-pipeline/outbound-pipeline.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md index ee6d8d8a1e9..2173dccb3ef 100644 --- a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md +++ b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -16,7 +16,7 @@ To explain things we will use the following content tree: ## 1. Create segments -When the URL is constructed, Umbraco will convert every node in the tree into a segment. Each published Content item has a corresponding URL segment. +When the URL is constructed, Umbraco will convert every document node in the tree into a segment. Each published document has a corresponding URL segment. In our example "Our Products" will become "our-products" and "Swibble" will become "swibble". @@ -24,15 +24,22 @@ The segments are created by the "Url Segment provider" ### Url Segment Provider -The DI container of an Umbraco implementation contains a collection of `UrlSegmentProviders`. This collection is populated during Umbraco boot up. Umbraco ships with a 'DefaultUrlSegmentProvider' - but custom implementations can be added to the collection. +The DI container of an Umbraco implementation contains a collection of `UrlSegmentProviders`. This collection is populated during Umbraco start up. Umbraco ships with a `DefaultUrlSegmentProvider` and custom implementations can be added to the collection. -When the `GetUrlSegment` extension method is called for a content item + culture combination, each registered `IUrlSegmentProvider` in the collection is executed in 'collection order'. This continues until a particular `UrlSegmentProvider` returns a segment value for the content, and no further `UrlSegmentProviders` in the collection will be executed. If no segment is returned by any provider in the collection a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, like when a default provider is removed from a collection without a new one being added. +When the segments are requested for a document and culture combination, each registered `IUrlSegmentProvider` in the collection is executed in 'collection order'. Each provider can provide a segment for the document and culture, or return null. + +Each URL segment provider is configured to either terminate after providing a segment or to allow other segments providers to be executed. When a terminating provider return a segment value for the document and culture, no further `UrlSegmentProviders` in the collection will be executed. + +If the provider does not terminate, other providers are able to return segments as well. In this way, multiple segments can be returned for a single document and culture combination. Along with the use of custom `IUrlProvider` and `IContentFinder` instances, considerable flexibility in the generated URLs can be achieved. + +If no segment is returned by any provider in the collection a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, even when a default provider is removed from a collection without a new one being added. To create a new Url Segment Provider, implement the following interface: ```csharp public interface IUrlSegmentProvider { + bool AllowAdditionalSegments => false; string GetUrlSegment(IContentBase content, string? culture = null); } ``` @@ -59,7 +66,7 @@ public class ProductPageUrlSegmentProvider : IUrlSegmentProvider { _provider = new DefaultUrlSegmentProvider(stringHelper); } - + public string GetUrlSegment(IContentBase content, string? culture = null) { // Only apply this rule for product pages @@ -259,7 +266,7 @@ public class ProductPageUrlProvider : NewDefaultUrlProvider { return null; } - + // Only apply this to product pages if (content.ContentType.Alias == "productPage") { @@ -270,7 +277,7 @@ public class ProductPageUrlProvider : NewDefaultUrlProvider { return null; } - + if (!defaultUrlInfo.IsUrl) { // This is a message (eg published but not visible because the parent is unpublished or similar) @@ -453,7 +460,7 @@ using Umbraco.Cms.Core.Composing; namespace RoutingDocs.SiteDomainMapping; public class AddSiteComposer : ComponentComposer -{ +{ } ``` From 378634eb25a7691191d35c91f5cb193fa8924e69 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 9 Apr 2025 12:28:01 +0200 Subject: [PATCH 3/8] Added detail of UserPasswordResettingNotification --- 15/umbraco-cms/SUMMARY.md | 1 + .../reference/notifications/README.md | 1 + .../userservice-notifications.md | 29 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 15/umbraco-cms/reference/notifications/userservice-notifications.md diff --git a/15/umbraco-cms/SUMMARY.md b/15/umbraco-cms/SUMMARY.md index af4a95a927e..9508018087c 100644 --- a/15/umbraco-cms/SUMMARY.md +++ b/15/umbraco-cms/SUMMARY.md @@ -362,6 +362,7 @@ * [Determining if an entity is new](reference/notifications/determining-new-entity.md) * [MediaService Notifications Example](reference/notifications/mediaservice-notifications.md) * [MemberService Notifications Example](reference/notifications/memberservice-notifications.md) + * [UserService Notifications Example](reference/notifications/userservice-notifications.md) * [Sending Allowed Children Notification](reference/notifications/sendingallowedchildrennotifications.md) * [Umbraco Application Lifetime Notifications](reference/notifications/umbracoapplicationlifetime-notifications.md) * [EditorModel Notifications](reference/notifications/editormodel-notifications/README.md) diff --git a/15/umbraco-cms/reference/notifications/README.md b/15/umbraco-cms/reference/notifications/README.md index b551f10a97d..79ba4613979 100644 --- a/15/umbraco-cms/reference/notifications/README.md +++ b/15/umbraco-cms/reference/notifications/README.md @@ -285,5 +285,6 @@ Below you can find some articles with some examples using Notifications. * [Hot vs. cold restarts](hot-vs-cold-restarts.md) * [MediaService Notifications](mediaservice-notifications.md) * [MemberService Notifications](memberservice-notifications.md) +* [UserService Notifications](userservice-notifications.md) * [Sending Allowed Children Notification](sendingallowedchildrennotifications.md) * [Umbraco Application Lifetime Notifications](umbracoapplicationlifetime-notifications.md) diff --git a/15/umbraco-cms/reference/notifications/userservice-notifications.md b/15/umbraco-cms/reference/notifications/userservice-notifications.md new file mode 100644 index 00000000000..b217c17bfb7 --- /dev/null +++ b/15/umbraco-cms/reference/notifications/userservice-notifications.md @@ -0,0 +1,29 @@ +--- +description: Example of how to use a UserService Notification +--- + +# UserService Notifications + +The UserService implements `IUserService` and provides access to operations involving `IUser`. + +The following example illustrates how the password reset feature can be cancelled for a given set of users. + +## Usage + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MySite; + +public class UserPasswordResettingNotificationHandler : INotificationHandler +{ + public void Handle(UserPasswordResettingNotification notification) + { + if (notification.User.Name?.Contains("Eddie") ?? false) + { + notification.CancelOperation(new EventMessage("fail", "Can't reset password for users with name containing 'Eddie'")); + } + } +} +``` From 78f8568862e00c0c99a3f8d79d6d604e4c65859e Mon Sep 17 00:00:00 2001 From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:51:06 +0200 Subject: [PATCH 4/8] Update 15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md --- .../reference/routing/request-pipeline/outbound-pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md index 2173dccb3ef..53c1c3a467b 100644 --- a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md +++ b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -26,7 +26,7 @@ The segments are created by the "Url Segment provider" The DI container of an Umbraco implementation contains a collection of `UrlSegmentProviders`. This collection is populated during Umbraco start up. Umbraco ships with a `DefaultUrlSegmentProvider` and custom implementations can be added to the collection. -When the segments are requested for a document and culture combination, each registered `IUrlSegmentProvider` in the collection is executed in 'collection order'. Each provider can provide a segment for the document and culture, or return null. +When the segments are requested for a document and culture combination, each registered `IUrlSegmentProvider` in the collection is executed in _collection order_. Each provider can provide a segment for the document and culture or return `null`. Each URL segment provider is configured to either terminate after providing a segment or to allow other segments providers to be executed. When a terminating provider return a segment value for the document and culture, no further `UrlSegmentProviders` in the collection will be executed. From 34cca1988aef37e569d328cb95743204cc287019 Mon Sep 17 00:00:00 2001 From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:51:15 +0200 Subject: [PATCH 5/8] Update 15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md --- .../reference/routing/request-pipeline/outbound-pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md index 53c1c3a467b..a4cf6fb6648 100644 --- a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md +++ b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -28,7 +28,7 @@ The DI container of an Umbraco implementation contains a collection of `UrlSegme When the segments are requested for a document and culture combination, each registered `IUrlSegmentProvider` in the collection is executed in _collection order_. Each provider can provide a segment for the document and culture or return `null`. -Each URL segment provider is configured to either terminate after providing a segment or to allow other segments providers to be executed. When a terminating provider return a segment value for the document and culture, no further `UrlSegmentProviders` in the collection will be executed. +Each URL segment provider is configured either to terminate after providing a segment or to allow other segment providers to be executed. When a terminating provider returns a segment value for the document and culture, no further `UrlSegmentProviders` in the collection will be executed. If the provider does not terminate, other providers are able to return segments as well. In this way, multiple segments can be returned for a single document and culture combination. Along with the use of custom `IUrlProvider` and `IContentFinder` instances, considerable flexibility in the generated URLs can be achieved. From f9070f1a6d09e09d9860879b9d7684a834b21716 Mon Sep 17 00:00:00 2001 From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:51:23 +0200 Subject: [PATCH 6/8] Update 15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md --- .../reference/routing/request-pipeline/outbound-pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md index a4cf6fb6648..e90cedb3873 100644 --- a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md +++ b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -30,7 +30,7 @@ When the segments are requested for a document and culture combination, each reg Each URL segment provider is configured either to terminate after providing a segment or to allow other segment providers to be executed. When a terminating provider returns a segment value for the document and culture, no further `UrlSegmentProviders` in the collection will be executed. -If the provider does not terminate, other providers are able to return segments as well. In this way, multiple segments can be returned for a single document and culture combination. Along with the use of custom `IUrlProvider` and `IContentFinder` instances, considerable flexibility in the generated URLs can be achieved. +If the provider does not terminate, other providers can also return segments. In this way, multiple segments can be returned for a single document and culture combination. Along with the use of custom `IUrlProvider` and `IContentFinder` instances, considerable flexibility in the generated URLs can be achieved. If no segment is returned by any provider in the collection a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, even when a default provider is removed from a collection without a new one being added. From 063d5f91ca4fef291b6a623a401b9df89c3f2204 Mon Sep 17 00:00:00 2001 From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:51:32 +0200 Subject: [PATCH 7/8] Update 15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md --- .../reference/routing/request-pipeline/outbound-pipeline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md index e90cedb3873..563a89208db 100644 --- a/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md +++ b/15/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -32,7 +32,7 @@ Each URL segment provider is configured either to terminate after providing a se If the provider does not terminate, other providers can also return segments. In this way, multiple segments can be returned for a single document and culture combination. Along with the use of custom `IUrlProvider` and `IContentFinder` instances, considerable flexibility in the generated URLs can be achieved. -If no segment is returned by any provider in the collection a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, even when a default provider is removed from a collection without a new one being added. +If no segment is returned by any provider in the collection, a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, even when a default provider has been removed from the collection without a new one being added. To create a new Url Segment Provider, implement the following interface: From f9a42bf113b6a27780021572a48bb151df42c91c Mon Sep 17 00:00:00 2001 From: Esha Noronha <82437098+eshanrnh@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:51:41 +0200 Subject: [PATCH 8/8] Update 15/umbraco-cms/reference/notifications/userservice-notifications.md --- .../reference/notifications/userservice-notifications.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/15/umbraco-cms/reference/notifications/userservice-notifications.md b/15/umbraco-cms/reference/notifications/userservice-notifications.md index b217c17bfb7..75be8941d5b 100644 --- a/15/umbraco-cms/reference/notifications/userservice-notifications.md +++ b/15/umbraco-cms/reference/notifications/userservice-notifications.md @@ -6,7 +6,7 @@ description: Example of how to use a UserService Notification The UserService implements `IUserService` and provides access to operations involving `IUser`. -The following example illustrates how the password reset feature can be cancelled for a given set of users. +The following example illustrates how to cancel the password reset feature for a given set of users. ## Usage