From f7d0e417d6228ecfcff34d1a62bc5d96b625d29d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 7 Jul 2021 16:16:14 -0600 Subject: [PATCH] packages partial views associated with macros. --- .../Models/Packaging/CompiledPackage.cs | 1 + .../Packaging/CompiledPackageXmlParser.cs | 1 + .../Packaging/PackagesRepository.cs | 44 ++++++++++++++++++- .../Packaging/PackageDataInstallation.cs | 41 +++++++++++++++-- .../Controllers/MacrosController.cs | 9 +++- 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs index 5f6c3991a0..e3596a268e 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs @@ -14,6 +14,7 @@ namespace Umbraco.Cms.Core.Models.Packaging public string Name { get; set; } public InstallWarnings Warnings { get; set; } = new InstallWarnings(); public IEnumerable Macros { get; set; } // TODO: make strongly typed + public IEnumerable MacroPartialViews { get; set; } // TODO: make strongly typed public IEnumerable Templates { get; set; } // TODO: make strongly typed public IEnumerable Stylesheets { get; set; } // TODO: make strongly typed public IEnumerable DataTypes { get; set; } // TODO: make strongly typed diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs index 2a4929930e..f4ee9738ab 100644 --- a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs +++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs @@ -38,6 +38,7 @@ namespace Umbraco.Cms.Core.Packaging PackageFile = null, Name = package.Element("name")?.Value, Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty(), + MacroPartialViews = xml.Root.Element("MacroPartialViews")?.Elements("view") ?? Enumerable.Empty(), Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty(), Stylesheets = xml.Root.Element("Stylesheets")?.Elements("styleSheet") ?? Enumerable.Empty(), DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty(), diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index ae19da3036..f8e651c7fa 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.IO.Compression; using System.Linq; +using System.Text; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.Options; @@ -372,18 +373,30 @@ namespace Umbraco.Cms.Core.Packaging private void PackageMacros(PackageDefinition definition, XContainer root) { + var packagedMacros = new List(); var macros = new XElement("Macros"); foreach (var macroId in definition.Macros) { - if (!int.TryParse(macroId, out var outInt)) + if (!int.TryParse(macroId, out int outInt)) + { continue; + } - var macroXml = GetMacroXml(outInt, out var macro); + XElement macroXml = GetMacroXml(outInt, out IMacro macro); if (macroXml == null) + { continue; + } + macros.Add(macroXml); + packagedMacros.Add(macro); } + root.Add(macros); + + // get the partial views for macros and package those + IEnumerable views = packagedMacros.Select(x => x.MacroSource).Where(x => x.EndsWith(".cshtml")); + PackagePartialViews(views, root, "MacroPartialViews"); } private void PackageStylesheets(PackageDefinition definition, XContainer root) @@ -415,6 +428,33 @@ namespace Umbraco.Cms.Core.Packaging root.Add(templatesXml); } + private void PackagePartialViews(IEnumerable viewPaths, XContainer root, string elementName) + { + var viewsXml = new XElement(elementName); + foreach (var viewPath in viewPaths) + { + // TODO: See TODO note in MacrosController about the inconsistencies of usages of partial views + // and how paths are saved. We have no choice currently but to assume that all views are 100% always + // on the content path. + + var physicalPath = _hostingEnvironment.MapPathContentRoot(viewPath); + if (!File.Exists(physicalPath)) + { + throw new InvalidOperationException("Could not find partial view at path " + viewPath); + } + + var fileContents = File.ReadAllText(physicalPath, Encoding.UTF8); + + viewsXml.Add( + new XElement( + "view", + new XAttribute("path", viewPath), + new XCData(fileContents))); + } + + root.Add(viewsXml); + } + private void PackageDocumentTypes(PackageDefinition definition, XContainer root) { var contentTypes = new HashSet(); diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs index 79cbf00de0..8d87570229 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using System.Text; using System.Xml.Linq; using System.Xml.XPath; using Microsoft.Extensions.Logging; @@ -39,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging private readonly IConfigurationEditorJsonSerializer _serializer; private readonly IMediaService _mediaService; private readonly IMediaTypeService _mediaTypeService; + private readonly IHostingEnvironment _hostingEnvironment; private readonly IEntityService _entityService; private readonly IContentTypeService _contentTypeService; private readonly IContentService _contentService; @@ -59,7 +61,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging IOptions globalSettings, IConfigurationEditorJsonSerializer serializer, IMediaService mediaService, - IMediaTypeService mediaTypeService) + IMediaTypeService mediaTypeService, + IHostingEnvironment hostingEnvironment) { _dataValueEditorFactory = dataValueEditorFactory; _logger = logger; @@ -74,6 +77,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging _serializer = serializer; _mediaService = mediaService; _mediaTypeService = mediaTypeService; + _hostingEnvironment = hostingEnvironment; _entityService = entityService; _contentTypeService = contentTypeService; _contentService = contentService; @@ -91,7 +95,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging DataTypesInstalled = ImportDataTypes(compiledPackage.DataTypes.ToList(), userId), LanguagesInstalled = ImportLanguages(compiledPackage.Languages, userId), DictionaryItemsInstalled = ImportDictionaryItems(compiledPackage.DictionaryItems, userId), - MacrosInstalled = ImportMacros(compiledPackage.Macros, userId), + MacrosInstalled = ImportMacros(compiledPackage.Macros, compiledPackage.MacroPartialViews, userId), TemplatesInstalled = ImportTemplates(compiledPackage.Templates.ToList(), userId), DocumentTypesInstalled = ImportDocumentTypes(compiledPackage.DocumentTypes, userId), MediaTypesInstalled = ImportMediaTypes(compiledPackage.MediaTypes, userId), @@ -1235,6 +1239,30 @@ namespace Umbraco.Cms.Infrastructure.Packaging #endregion + private void ImportPartialViews(IEnumerable viewElements) + { + foreach(XElement element in viewElements) + { + var path = element.AttributeValue("path"); + if (path == null) + { + throw new InvalidOperationException("No path attribute found"); + } + var contents = element.Value; + if (contents.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException("No content found for partial view"); + } + + var physicalPath = _hostingEnvironment.MapPathContentRoot(path); + // TODO: Do we overwrite? IMO I don't think so since these will be views a user will change. + if (!System.IO.File.Exists(physicalPath)) + { + System.IO.File.WriteAllText(physicalPath, contents, Encoding.UTF8); + } + } + } + #region Macros /// @@ -1243,15 +1271,20 @@ namespace Umbraco.Cms.Infrastructure.Packaging /// Xml to import /// Optional id of the User performing the operation /// - public IReadOnlyList ImportMacros(IEnumerable macroElements, int userId) + public IReadOnlyList ImportMacros( + IEnumerable macroElements, + IEnumerable macroPartialViewsElements, + int userId) { var macros = macroElements.Select(ParseMacroElement).ToList(); - foreach (var macro in macros) + foreach (IMacro macro in macros) { _macroService.Save(macro, userId); } + ImportPartialViews(macroPartialViewsElements); + return macros; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs index ec91d76c8e..84ff7565cc 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs @@ -46,8 +46,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IBackOfficeSecurityAccessor backofficeSecurityAccessor, ILogger logger, IHostingEnvironment hostingEnvironment, - IUmbracoMapper umbracoMapper - ) + IUmbracoMapper umbracoMapper) { _parameterEditorCollection = parameterEditorCollection ?? throw new ArgumentNullException(nameof(parameterEditorCollection)); _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService)); @@ -315,6 +314,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// private IEnumerable FindPartialViewFilesInViewsFolder() { + // TODO: This is inconsistent. We have FileSystems.MacroPartialsFileSystem but we basically don't use + // that at all except to render the tree. In the future we may want to use it. This also means that + // we are storing the virtual path of the macro like ~/Views/MacroPartials/Login.cshtml instead of the + // relative path which would work with the FileSystems.MacroPartialsFileSystem, but these are incompatible. + // At some point this should all be made consistent and probably just use FileSystems.MacroPartialsFileSystem. + var partialsDir = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials); return this.FindPartialViewFilesInFolder(