Merge branch 'v10/dev' into v10/feature/variant-permissions

This commit is contained in:
Mads Rasmussen
2022-08-03 11:22:35 +02:00
166 changed files with 4212 additions and 2750 deletions

View File

@@ -1,3 +1,4 @@
using System.Xml;
using System.Globalization;
using System.Net.Mime;
using System.Text;
@@ -10,11 +11,17 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
using Umbraco.Cms.Infrastructure.Packaging;
using System.Xml.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
@@ -38,7 +45,34 @@ public class DictionaryController : BackOfficeNotificationsController
private readonly ILocalizedTextService _localizedTextService;
private readonly ILogger<DictionaryController> _logger;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IEntityXmlSerializer _serializer;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly PackageDataInstallation _packageDataInstallation;
[ActivatorUtilitiesConstructor]
public DictionaryController(
ILogger<DictionaryController> logger,
ILocalizationService localizationService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IOptionsSnapshot<GlobalSettings> globalSettings,
ILocalizedTextService localizedTextService,
IUmbracoMapper umbracoMapper,
IEntityXmlSerializer serializer,
IHostingEnvironment hostingEnvironment,
PackageDataInstallation packageDataInstallation)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation));
}
[Obsolete("Please use ctor that also takes an IEntityXmlSerializer, IHostingEnvironment & PackageDataInstallation instead, scheduled for removal in v12")]
public DictionaryController(
ILogger<DictionaryController> logger,
ILocalizationService localizationService,
@@ -46,14 +80,17 @@ public class DictionaryController : BackOfficeNotificationsController
IOptionsSnapshot<GlobalSettings> globalSettings,
ILocalizedTextService localizedTextService,
IUmbracoMapper umbracoMapper)
: this(
logger,
localizationService,
backofficeSecurityAccessor,
globalSettings,
localizedTextService,
umbracoMapper,
StaticServiceProvider.Instance.GetRequiredService<IEntityXmlSerializer>(),
StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>(),
StaticServiceProvider.Instance.GetRequiredService<PackageDataInstallation>())
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
_backofficeSecurityAccessor = backofficeSecurityAccessor ??
throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
}
/// <summary>
@@ -389,5 +426,134 @@ public class DictionaryController : BackOfficeNotificationsController
}
}
public IActionResult ExportDictionary(int id, bool includeChildren = false)
{
IDictionaryItem? dictionaryItem = _localizationService.GetDictionaryItemById(id);
if (dictionaryItem == null)
{
throw new NullReferenceException("No dictionary item found with id " + id);
}
XElement xml = _serializer.Serialize(dictionaryItem, includeChildren);
var fileName = $"{dictionaryItem.ItemKey}.udt";
// Set custom header so umbRequestHelper.downloadFile can save the correct filename
HttpContext.Response.Headers.Add("x-filename", fileName);
return File(Encoding.UTF8.GetBytes(xml.ToDataString()), MediaTypeNames.Application.Octet, fileName);
}
public IActionResult ImportDictionary(string file, int parentId)
{
if (string.IsNullOrWhiteSpace(file))
{
return NotFound();
}
var filePath = Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data), file);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var xd = new XmlDocument { XmlResolver = null };
xd.Load(filePath);
var userId = _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0;
var element = XElement.Parse(xd.InnerXml);
IDictionaryItem? parentDictionaryItem = _localizationService.GetDictionaryItemById(parentId);
IEnumerable<IDictionaryItem> dictionaryItems = _packageDataInstallation
.ImportDictionaryItem(element, userId, parentDictionaryItem?.Key);
// Try to clean up the temporary file.
try
{
System.IO.File.Delete(filePath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error cleaning up temporary udt file in {File}", filePath);
}
var model = _umbracoMapper.Map<IDictionaryItem, DictionaryDisplay>(dictionaryItems.FirstOrDefault());
return Content(model!.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
}
public ActionResult<DictionaryImportModel> Upload(IFormFile file)
{
if (file == null)
{
return ValidationProblem(
_localizedTextService.Localize("media", "failedFileUpload"),
_localizedTextService.Localize("speechBubbles", "fileErrorNotFound"));
}
var fileName = file.FileName.Trim(Constants.CharArrays.DoubleQuote);
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
var root = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads);
var tempPath = Path.Combine(root, fileName);
if (!Path.GetFullPath(tempPath).StartsWith(Path.GetFullPath(root)))
{
return ValidationProblem(
_localizedTextService.Localize("media", "failedFileUpload"),
_localizedTextService.Localize("media", "invalidFileName"));
}
if (!ext.InvariantEquals("udt"))
{
return ValidationProblem(
_localizedTextService.Localize("media", "failedFileUpload"),
_localizedTextService.Localize("media", "disallowedFileType"));
}
using (FileStream stream = System.IO.File.Create(tempPath))
{
file.CopyToAsync(stream).GetAwaiter().GetResult();
}
var xd = new XmlDocument { XmlResolver = null };
xd.Load(tempPath);
if (xd.DocumentElement == null)
{
return ValidationProblem(
_localizedTextService.Localize("media", "failedFileUpload"),
_localizedTextService.Localize("speechBubbles", "fileErrorNotFound"));
}
var model = new DictionaryImportModel()
{
TempFileName = tempPath,
DictionaryItems = new List<DictionaryPreviewImportModel>(),
};
var level = 1;
var currentParent = string.Empty;
foreach (XmlNode dictionaryItem in xd.GetElementsByTagName("DictionaryItem"))
{
var name = dictionaryItem.Attributes?.GetNamedItem("Name")?.Value ?? string.Empty;
var parentKey = dictionaryItem?.ParentNode?.Attributes?.GetNamedItem("Key")?.Value ?? string.Empty;
if (parentKey != currentParent || level == 1)
{
level += 1;
currentParent = parentKey;
}
model.DictionaryItems.Add(new DictionaryPreviewImportModel() { Level = level, Name = name });
}
if (!model.DictionaryItems.Any())
{
return ValidationProblem(
_localizedTextService.Localize("media", "failedFileUpload"),
_localizedTextService.Localize("dictionary", "noItemsInFile"));
}
return model;
}
private static Func<IDictionaryItem, string> ItemSort() => item => item.ItemKey;
}