Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 15/umbraco-cms/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
75 changes: 64 additions & 11 deletions 15/umbraco-cms/reference/content-type-filters.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<IEnumerable<TItem>> FilterAllowedAtRootAsync<TItem>(IEnumerable<TItem> contentTypes)
where TItem : IContentTypeComposition
{
// Only allow one home page at the root.
var docTypeAliasesToExclude = new List<string>();

const string HomePageDocTypeAlias = "homePage";
var docTypeAliasesAtRoot = _contentService.GetRootContent()
.Select(x => x.ContentType.Alias)
.Distinct()
Expand All @@ -57,12 +75,47 @@ internal class OneHomePageOnlyContentTypeFilter : IContentTypeFilter
.Where(x => docTypeAliasesToExclude.Contains(x.Alias) is false));
}

public Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(IEnumerable<ContentTypeSort> contentTypes, Guid parentKey)
=> Task.FromResult(contentTypes);
public Task<IEnumerable<ContentTypeSort>> FilterAllowedChildrenAsync(
IEnumerable<ContentTypeSort> 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<string>();

Attempt<int> 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:

Expand All @@ -72,7 +125,7 @@ public class MyComposer : IComposer
public void Compose(IUmbracoBuilder builder)
{
builder.ContentTypeFilters()
.Append<OneHomePageOnlyContentTypeFilter>();
.Append<MyContentTypeFilter>();
}
}
```
1 change: 1 addition & 0 deletions 15/umbraco-cms/reference/notifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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 to cancel the password reset feature for a given set of users.

## Usage

```csharp
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;

namespace MySite;

public class UserPasswordResettingNotificationHandler : INotificationHandler<UserPasswordResettingNotification>
{
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'"));
}
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,30 @@ 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".

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 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 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 has been removed from the 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);
}
```
Expand All @@ -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
Expand Down Expand Up @@ -259,7 +266,7 @@ public class ProductPageUrlProvider : NewDefaultUrlProvider
{
return null;
}

// Only apply this to product pages
if (content.ContentType.Alias == "productPage")
{
Expand All @@ -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)
Expand Down Expand Up @@ -453,7 +460,7 @@ using Umbraco.Cms.Core.Composing;
namespace RoutingDocs.SiteDomainMapping;

public class AddSiteComposer : ComponentComposer<SiteDomainMapperComponent>
{
{
}
```

Expand Down