Files
Umbraco-CMS/tests/Umbraco.Tests.Common/Factories/ContentEditingModelFactory.cs
Sven Geusens dd01a56d2a Feature: single block property editor (#20098)
* First Go at the single block property editor based on blocklistpropertyeditor

* Add simalar tests to the blocklist editor

Also check whether either block of configured blocks can be picked and used from a data perspective

* WIP singleblock Valiation tests

* Finished first full pass off SingleBlock validation testing

* Typos, Future test function

* Restore accidently removed file

* Introduce propertyValueConverter

* Comment updates

* Add singleBlock renderer

* Textual improvements

Comment improvements, remove licensing in file

* Update DataEditorCount by 1 as we introduced a new one

* Align test naming

* Add ignored singleblock default renderer

* Enable SingleBlock Property Indexing

* Enable Partial value merging

* Fix indentation

---------

Co-authored-by: kjac <kja@umbraco.dk>
2025-09-17 07:20:09 +02:00

188 lines
7.2 KiB
C#

using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services;
// Disclaimer: Based on code generated by Claude code.
// Most likely not complete, but the logic looks sound.
public interface IContentEditingModelFactory
{
Task<ContentUpdateModel> CreateFromAsync(IContent content);
}
public class ContentEditingModelFactory : IContentEditingModelFactory
{
private readonly ITemplateService _templateService;
public ContentEditingModelFactory(ITemplateService templateService)
{
_templateService = templateService;
}
public async Task<ContentUpdateModel> CreateFromAsync(IContent content)
{
{
var templateKey = content.TemplateId.HasValue
? (await _templateService.GetAsync(content.TemplateId.Value))?.Key
: null;
var model = new ContentUpdateModel { TemplateKey = templateKey };
var properties = new List<PropertyValueModel>();
var variants = new List<VariantModel>();
MapProperties(content, properties);
MapNames(content, properties, variants);
model.Properties = properties;
model.Variants = variants;
return model;
}
}
private static void MapNames(IContent content, List<PropertyValueModel> properties, List<VariantModel> variants)
{
// Handle variants (content names per culture/segment)
var contentVariesByCulture = content.ContentType.VariesByCulture();
var contentVariesBySegment = content.ContentType.VariesBySegment();
if (contentVariesByCulture || contentVariesBySegment)
{
// Get all unique culture/segment combinations from CultureInfos
var cultureSegmentCombinations = new HashSet<(string? culture, string? segment)>();
// Add invariant combination
cultureSegmentCombinations.Add((null, null));
if (contentVariesByCulture)
{
// Add cultures
foreach (var culture in content.AvailableCultures)
{
cultureSegmentCombinations.Add((culture, null));
}
}
// For segment support, we need to extract segments from property values
// since content doesn't have "AvailableSegments" like cultures
if (contentVariesBySegment)
{
var segmentsFromProperties = properties
.Where(p => !string.IsNullOrEmpty(p.Segment))
.Select(p => p.Segment)
.Distinct()
.ToList();
foreach (var segment in segmentsFromProperties)
{
cultureSegmentCombinations.Add((null, segment));
// If content also varies by culture, add culture+segment combinations
if (contentVariesByCulture)
{
foreach (var culture in content.AvailableCultures)
{
cultureSegmentCombinations.Add((culture, segment));
}
}
}
}
// Create variants for each combination
foreach (var (culture, segment) in cultureSegmentCombinations)
{
string? variantName;
if (culture == null && segment == null)
{
// Invariant
variantName = content.Name;
}
else if (culture != null && segment == null)
{
// Culture-specific
variantName = content.GetCultureName(culture);
}
else
{
// For segment-specific or culture+segment combinations,
// we'll use the invariant or culture name as segments don't have separate names
variantName = culture != null ? content.GetCultureName(culture) : content.Name;
}
if (!string.IsNullOrEmpty(variantName))
{
variants.Add(new VariantModel { Culture = culture, Segment = segment, Name = variantName });
}
}
}
else
{
// For invariant content, add single variant
variants.Add(new VariantModel { Culture = null, Segment = null, Name = content.Name ?? string.Empty });
}
}
private static void MapProperties(IContent content, List<PropertyValueModel> properties)
{
// Handle properties
foreach (var property in content.Properties)
{
var propertyVariesByCulture = property.PropertyType.VariesByCulture();
var propertyVariesBySegment = property.PropertyType.VariesBySegment();
// Get all property values from the property's Values collection
foreach (var propertyValue in property.Values)
{
if (propertyValue.EditedValue != null)
{
properties.Add(new PropertyValueModel
{
Alias = property.Alias,
Value = propertyValue.EditedValue,
Culture = propertyVariesByCulture ? propertyValue.Culture : null,
Segment = propertyVariesBySegment ? propertyValue.Segment : null,
});
}
}
// Fallback: if no values found in the Values collection, try the traditional approach
if (!property.Values.Any())
{
if (propertyVariesByCulture && content.AvailableCultures.Any())
{
// Handle culture variants
foreach (var culture in content.AvailableCultures)
{
var cultureValue = property.GetValue(culture);
if (cultureValue != null)
{
properties.Add(new PropertyValueModel
{
Alias = property.Alias, Value = cultureValue, Culture = culture, Segment = null,
});
}
}
// Also add the invariant value if it exists
var invariantValue = property.GetValue();
if (invariantValue != null)
{
properties.Add(new PropertyValueModel
{
Alias = property.Alias, Value = invariantValue, Culture = null, Segment = null,
});
}
}
else
{
// Handle invariant properties
var value = property.GetValue();
if (value != null)
{
properties.Add(new PropertyValueModel
{
Alias = property.Alias, Value = value, Culture = null, Segment = null,
});
}
}
}
}
}
}