Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
Services.AddUnique<IDictionaryItemService, DictionaryItemService>();
Services.AddUnique<IDataTypeContainerService, DataTypeContainerService>();
Services.AddUnique<IContentTypeContainerService, ContentTypeContainerService>();
Services.AddUnique<IContentTypeSchemaService, ContentTypeSchemaService>();

Check warning on line 287 in src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Large Method

AddCoreServices increases from 212 to 213 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
Services.AddUnique<IMediaTypeContainerService, MediaTypeContainerService>();
Services.AddUnique<IContentBlueprintContainerService, ContentBlueprintContainerService>();
Services.AddUnique<IIsoCodeValidator, IsoCodeValidator>();
Expand Down
27 changes: 27 additions & 0 deletions src/Umbraco.Core/Models/ContentTypePropertySchemaInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents the subset of content type property information that is needed for schema generation.
/// </summary>
public class ContentTypePropertySchemaInfo
{
/// <summary>
/// Gets the property alias.
/// </summary>
public required string Alias { get; init; }

/// <summary>
/// Gets the property editor alias.
/// </summary>
public required string EditorAlias { get; init; }

/// <summary>
/// Gets the CLR type used to represent this property in the Delivery API.
/// </summary>
public required Type DeliveryApiClrType { get; init; }

/// <summary>
/// Gets a value indicating whether this property is inherited from a composition.
/// </summary>
public bool Inherited { get; init; }
}
32 changes: 32 additions & 0 deletions src/Umbraco.Core/Models/ContentTypeSchemaInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Umbraco.Cms.Core.Models;

/// <summary>
/// Represents the subset of content type information that is needed for schema generation.
/// </summary>
public class ContentTypeSchemaInfo
{
/// <summary>
/// Gets the content type alias.
/// </summary>
public required string Alias { get; init; }

/// <summary>
/// Gets the content type schema ID.
/// </summary>
public required string SchemaId { get; init; }

/// <summary>
/// Gets the schema IDs of the content type's compositions.
/// </summary>
public required IReadOnlyList<string> CompositionSchemaIds { get; init; }

/// <summary>
/// Gets the properties for this content type.
/// </summary>
public required IReadOnlyList<ContentTypePropertySchemaInfo> Properties { get; init; }

/// <summary>
/// Gets a value indicating whether the content type is an element type.
/// </summary>
public bool IsElement { get; init; }
}
87 changes: 87 additions & 0 deletions src/Umbraco.Core/Services/ContentTypeSchemaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;

namespace Umbraco.Cms.Core.Services;

/// <inheritdoc />
internal sealed class ContentTypeSchemaService : IContentTypeSchemaService
{
private readonly IContentTypeService _contentTypeService;
private readonly IMediaTypeService _mediaTypeService;
private readonly IPublishedContentTypeCache _publishedContentTypeCache;
private readonly IShortStringHelper _shortStringHelper;

/// <summary>
/// Initializes a new instance of the <see cref="ContentTypeSchemaService"/> class.
/// </summary>
public ContentTypeSchemaService(
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IPublishedContentTypeCache publishedContentTypeCache,
IShortStringHelper shortStringHelper)
{
_contentTypeService = contentTypeService;
_mediaTypeService = mediaTypeService;
_publishedContentTypeCache = publishedContentTypeCache;
_shortStringHelper = shortStringHelper;
}

/// <inheritdoc/>
public IReadOnlyCollection<ContentTypeSchemaInfo> GetDocumentTypes()
=> GetContentTypeSchemaInfos(PublishedItemType.Content, _contentTypeService.GetAll());

/// <inheritdoc/>
public IReadOnlyCollection<ContentTypeSchemaInfo> GetMediaTypes()
=> GetContentTypeSchemaInfos(PublishedItemType.Media, _mediaTypeService.GetAll());
Comment thread
lauraneto marked this conversation as resolved.
Comment thread
lauraneto marked this conversation as resolved.

private List<ContentTypeSchemaInfo> GetContentTypeSchemaInfos(
PublishedItemType itemType,
IEnumerable<IContentTypeComposition> contentTypes)
{
List<ContentTypeSchemaInfo> result = [];

foreach (IContentTypeComposition contentType in contentTypes)
{
IPublishedContentType publishedContentType;
try
{
publishedContentType = _publishedContentTypeCache.Get(itemType, contentType.Alias);
}
catch
{
// Skip content types that fail to load from cache
Comment thread
lauraneto marked this conversation as resolved.
continue;
}

HashSet<string> ownPropertyAliases = [.. contentType.PropertyTypes.Select(p => p.Alias)];

result.Add(
new ContentTypeSchemaInfo
{
Alias = contentType.Alias,
SchemaId = GetContentTypeSchemaId(contentType.Alias),
CompositionSchemaIds = [.. publishedContentType.CompositionAliases.Select(GetContentTypeSchemaId)],
Properties =
[
.. publishedContentType.PropertyTypes.Select(p => new ContentTypePropertySchemaInfo
{
Alias = p.Alias,
EditorAlias = p.EditorAlias,
DeliveryApiClrType = p.DeliveryApiModelClrType,
Inherited = !ownPropertyAliases.Contains(p.Alias),
Comment thread
lauraneto marked this conversation as resolved.
Outdated
})
],
IsElement = publishedContentType.IsElement,
});
}

return result;
}

// Currently uses the same transformation as ModelsBuilder (UmbracoServices.GetClrName)
private string GetContentTypeSchemaId(string contentTypeAlias) =>
contentTypeAlias.ToCleanString(_shortStringHelper, CleanStringType.ConvertCase | CleanStringType.PascalCase);
}
Comment thread
lauraneto marked this conversation as resolved.
21 changes: 21 additions & 0 deletions src/Umbraco.Core/Services/IContentTypeSchemaService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Umbraco.Cms.Core.Models;

namespace Umbraco.Cms.Core.Services;

/// <summary>
/// Service to get content type schema information for schema generation.
/// </summary>
public interface IContentTypeSchemaService
{
/// <summary>
/// Gets all available document types.
/// </summary>
/// <returns>A collection of document type schema information.</returns>
public IReadOnlyCollection<ContentTypeSchemaInfo> GetDocumentTypes();

/// <summary>
/// Gets all available media types.
/// </summary>
/// <returns>A collection of media type schema information.</returns>
public IReadOnlyCollection<ContentTypeSchemaInfo> GetMediaTypes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using NUnit.Framework;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;

namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;

[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
internal sealed class ContentTypeSchemaServiceTests : UmbracoIntegrationTestWithContentEditing
{
// Required to register IPublishedContentTypeCache (even though it uses in-memory cache, not HybridCache)
protected override void CustomTestSetup(IUmbracoBuilder builder)
=> builder.AddUmbracoHybridCache();

private IContentTypeSchemaService ContentTypeSchemaService =>
GetRequiredService<IContentTypeSchemaService>();

private IContentTypeService ContentTypeService =>
GetRequiredService<IContentTypeService>();

private IMediaTypeService MediaTypeService =>
GetRequiredService<IMediaTypeService>();

[Test]
public void Can_Get_DocumentTypes()
{
// Act
var result = ContentTypeSchemaService.GetDocumentTypes();

// Assert - count should match the content type service
Assert.That(result, Has.Count.EqualTo(ContentTypeService.Count()));

var contentTypeSchema = result.FirstOrDefault(ct => ct.Alias == ContentType.Alias);
Assert.That(contentTypeSchema, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(contentTypeSchema!.Alias, Is.EqualTo("umbTextpage"));
Assert.That(contentTypeSchema.SchemaId, Is.EqualTo("UmbTextpage"));
Assert.That(contentTypeSchema.Properties, Has.Count.EqualTo(1));
});

// Verify property details - the base class creates a "title" property
var titleProperty = contentTypeSchema!.Properties.First();
Assert.Multiple(() =>
{
Assert.That(titleProperty.Alias, Is.EqualTo("title"));
Assert.That(titleProperty.EditorAlias, Is.EqualTo("Umbraco.TextArea"));
Assert.That(titleProperty.Inherited, Is.False);
Comment thread
lauraneto marked this conversation as resolved.
});
}

[Test]
public void Can_Get_MediaTypes()
{
// Act
var result = ContentTypeSchemaService.GetMediaTypes();

// Assert - count should match the media type service
Assert.That(result, Has.Count.EqualTo(MediaTypeService.Count()));

// Built-in Image media type should be present with its properties
var imageSchema = result.FirstOrDefault(mt => mt.Alias == "Image");
Assert.That(imageSchema, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(imageSchema!.SchemaId, Is.EqualTo("Image"));
Assert.That(imageSchema.Properties, Is.Not.Empty);
});

// Verify the umbracoFile property exists on Image
var umbracoFileProperty = imageSchema!.Properties.FirstOrDefault(p => p.Alias == "umbracoFile");
Assert.That(umbracoFileProperty, Is.Not.Null);
Assert.That(umbracoFileProperty!.EditorAlias, Is.EqualTo("Umbraco.ImageCropper"));
Comment thread
lauraneto marked this conversation as resolved.
}
}
Loading
Loading