Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V14: Fix templates not having set master template on package install #16978

Merged
merged 7 commits into from
Sep 17, 2024
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
2 changes: 0 additions & 2 deletions src/Umbraco.Core/Services/FileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,6 @@ public void SaveTemplate(ITemplate template, int userId = Constants.Security.Sup
/// </summary>
/// <param name="templates">List of <see cref="Template" /> to save</param>
/// <param name="userId">Optional id of the user</param>
// FIXME: we need to re-implement PackageDataInstallation.ImportTemplates so it imports templates in the correct order
// instead of relying on being able to save invalid templates (child templates whose master has yet to be created)
[Obsolete("Please use ITemplateService for template operations - will be removed in Umbraco 15")]
public void SaveTemplate(IEnumerable<ITemplate> templates, int userId = Constants.Security.SuperUserId)
{
Expand Down
12 changes: 12 additions & 0 deletions src/Umbraco.Core/Services/IPackageDataInstallation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,28 @@ IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dicti
/// <returns>An enumerable list of generated languages</returns>
IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId);

[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
IEnumerable<ITemplate> ImportTemplate(XElement templateElement, int userId);

Task<IEnumerable<ITemplate>> ImportTemplateAsync(XElement templateElement, int userId);

/// <summary>
/// Imports and saves package xml as <see cref="ITemplate"/>
/// </summary>
/// <param name="templateElements">Xml to import</param>
/// <param name="userId">Optional user id</param>
/// <returns>An enumerable list of generated Templates</returns>
[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId);

/// <summary>
/// Imports and saves package xml as <see cref="ITemplate"/>
/// </summary>
/// <param name="templateElements">Xml to import</param>
/// <param name="userId">Optional user id</param>
/// <returns>An enumerable list of generated Templates</returns>
Task<IReadOnlyList<ITemplate>> ImportTemplatesAsync(IReadOnlyCollection<XElement> templateElements, int userId);

Guid GetContentTypeKey(XElement contentType);

string? GetEntityTypeAlias(XElement entityType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ private static IPackageDataInstallation CreatePackageDataInstallation(IServicePr
factory.GetRequiredService<IShortStringHelper>(),
factory.GetRequiredService<IConfigurationEditorJsonSerializer>(),
factory.GetRequiredService<IMediaService>(),
factory.GetRequiredService<IMediaTypeService>());
factory.GetRequiredService<IMediaTypeService>(),
factory.GetRequiredService<ITemplateContentParserService>(),
factory.GetRequiredService<ITemplateService>());

private static LocalizedTextServiceFileSources CreateLocalizedTextServiceFileSourcesFactory(
IServiceProvider container)
Expand Down
91 changes: 76 additions & 15 deletions src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Globalization;

Check notice on line 1 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Lines of Code in a Single File

The lines of code increases from 1263 to 1314, improve code health by reducing it to 1000. The number of Lines of Code in a single file. More Lines of Code lowers the code health.

Check notice on line 1 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 5.60 to 5.30, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
using System.Net;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Collections;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
Expand Down Expand Up @@ -34,6 +36,8 @@
private readonly IConfigurationEditorJsonSerializer _serializer;
private readonly IMediaService _mediaService;
private readonly IMediaTypeService _mediaTypeService;
private readonly ITemplateContentParserService _templateContentParserService;
private readonly ITemplateService _templateService;
private readonly IEntityService _entityService;
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
Expand All @@ -52,22 +56,62 @@
IShortStringHelper shortStringHelper,
IConfigurationEditorJsonSerializer serializer,
IMediaService mediaService,
IMediaTypeService mediaTypeService)
IMediaTypeService mediaTypeService,
ITemplateContentParserService templateContentParserService,
ITemplateService templateService)
{
_dataValueEditorFactory = dataValueEditorFactory;
_logger = logger;
_fileService = fileService;
_localizationService = localizationService;
_dataTypeService = dataTypeService;
_entityService = entityService;
_contentTypeService = contentTypeService;
_contentService = contentService;
_propertyEditors = propertyEditors;
_scopeProvider = scopeProvider;
_shortStringHelper = shortStringHelper;
_serializer = serializer;
_mediaService = mediaService;
_mediaTypeService = mediaTypeService;
_templateContentParserService = templateContentParserService;
_templateService = templateService;
}

Check notice on line 79 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ Getting worse: Constructor Over-Injection

PackageDataInstallation increases from 14 to 16 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.

[Obsolete("Please use new constructor, scheduled for removal in v15")]
public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory,
ILogger<PackageDataInstallation> logger,
IFileService fileService,
ILocalizationService localizationService,
IDataTypeService dataTypeService,
IEntityService entityService,
IContentTypeService contentTypeService,
IContentService contentService,
PropertyEditorCollection propertyEditors,
IScopeProvider scopeProvider,
IShortStringHelper shortStringHelper,
IConfigurationEditorJsonSerializer serializer,
IMediaService mediaService,
IMediaTypeService mediaTypeService)
: this(
dataValueEditorFactory,
logger,
fileService,
localizationService,
dataTypeService,
entityService,
contentTypeService,
contentService,
propertyEditors,
scopeProvider,
shortStringHelper,
serializer,
mediaService,
mediaTypeService,
StaticServiceProvider.Instance.GetRequiredService<ITemplateContentParserService>(),
StaticServiceProvider.Instance.GetRequiredService<ITemplateService>())
{

Check notice on line 114 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ Getting better: Constructor Over-Injection

PackageDataInstallation decreases from 16 to 14 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
}

// Also remove factory service registration when this constructor is removed
Expand Down Expand Up @@ -103,7 +147,9 @@
shortStringHelper,
serializer,
mediaService,
mediaTypeService)
mediaTypeService,
StaticServiceProvider.Instance.GetRequiredService<ITemplateContentParserService>(),
StaticServiceProvider.Instance.GetRequiredService<ITemplateService>())

Check notice on line 152 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ New issue: Constructor Over-Injection

PackageDataInstallation has 16 arguments, threshold = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
{ }

#region Install/Uninstall
Expand Down Expand Up @@ -1651,83 +1697,98 @@

#region Templates

[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
public IEnumerable<ITemplate> ImportTemplate(XElement templateElement, int userId)
=> ImportTemplates(new[] {templateElement}, userId);

public async Task<IEnumerable<ITemplate>> ImportTemplateAsync(XElement templateElement, int userId)
=> ImportTemplatesAsync(new[] {templateElement}, userId).GetAwaiter().GetResult();


[Obsolete("Use Async version instead, Scheduled to be removed in v17")]
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
=> ImportTemplatesAsync(templateElements, userId).GetAwaiter().GetResult();

/// <summary>
/// Imports and saves package xml as <see cref="ITemplate"/>
/// </summary>
/// <param name="templateElements">Xml to import</param>
/// <param name="userId">Optional user id</param>
/// <returns>An enumerable list of generated Templates</returns>
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
public async Task<IReadOnlyList<ITemplate>> ImportTemplatesAsync(IReadOnlyCollection<XElement> templateElements, int userId)
{
var templates = new List<ITemplate>();

var graph = new TopoGraph<string, TopoGraph.Node<string, XElement>>(x => x.Key, x => x.Dependencies);

foreach (XElement tempElement in templateElements)
{
var dependencies = new List<string>();
XElement elementCopy = tempElement;
//Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.
if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false &&
templateElements.Any(x => (string?)x.Element("Alias") == (string?)elementCopy.Element("Master")))

//Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.'
var masterTemplate = _templateContentParserService.MasterTemplateAlias(tempElement.Value);
if (masterTemplate is not null && templateElements.Any(x => (string?)x.Element("Alias") == masterTemplate))
{
dependencies.Add((string)elementCopy.Element("Master")!);
dependencies.Add(masterTemplate);
}
else if (string.IsNullOrEmpty((string?)elementCopy.Element("Master")) == false &&
templateElements.Any(x =>
(string?)x.Element("Alias") == (string?)elementCopy.Element("Master")) == false)
else
{
_logger.LogInformation(
"Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored.",
(string?)elementCopy.Element("Alias"),
(string?)elementCopy.Element("Master"));
masterTemplate);
}

graph.AddItem(TopoGraph.CreateNode((string)elementCopy.Element("Alias")!, elementCopy, dependencies));
}

//Sort templates by dependencies to a potential master template
IEnumerable<TopoGraph.Node<string, XElement>> sorted = graph.GetSortedItems();
foreach (TopoGraph.Node<string, XElement>? item in sorted)
{
XElement templateElement = item.Item;

var templateName = templateElement.Element("Name")?.Value;
var alias = templateElement.Element("Alias")!.Value;
var design = templateElement.Element("Design")?.Value;
XElement? masterElement = templateElement.Element("Master");

var existingTemplate = _fileService.GetTemplate(alias) as Template;
var existingTemplate = await _templateService.GetAsync(alias) as Template;

Template? template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias);
Template template = existingTemplate ?? new Template(_shortStringHelper, templateName, alias);

// For new templates, use the serialized key if avaialble.
if (existingTemplate == null && Guid.TryParse(templateElement.Element("Key")?.Value, out Guid key))
{
template.Key = key;
}

template.Content = design;

if (masterElement != null && string.IsNullOrEmpty((string)masterElement) == false)
{
template.MasterTemplateAlias = masterElement.Value;
ITemplate? masterTemplate = templates.FirstOrDefault(x => x.Alias == masterElement.Value);
if (masterTemplate != null)
{
template.MasterTemplateId = new Lazy<int>(() => masterTemplate.Id);
}
}

templates.Add(template);
}

if (templates.Any())
foreach (ITemplate template in templates)
{
_fileService.SaveTemplate(templates, userId);
if (template.Id > 0)
{
await _templateService.UpdateAsync(template, Constants.Security.SuperUserKey);
}
else
{
await _templateService.CreateAsync(template, Constants.Security.SuperUserKey);
}

Check notice on line 1791 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ No longer an issue: Bumpy Road Ahead

ImportTemplates is no longer above the threshold for logical blocks with deeply nested code. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check notice on line 1791 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ New issue: Bumpy Road Ahead

ImportTemplatesAsync has 4 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check notice on line 1791 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

✅ No longer an issue: Complex Method

ImportTemplates is no longer above the threshold for cyclomatic complexity. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check notice on line 1791 in src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v14/dev)

ℹ New issue: Complex Method

ImportTemplatesAsync has a cyclomatic complexity of 13, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

return templates;
Expand Down
Loading