v9: Move local xml package files to database instead (#11654)

* Move package to database

* Added migration and implemented new repository

* Updated migrations to use proper xml convert

* Fixed save function and renamed createDate to update date

* Updated dependencyInjection

* Updated UmbracoPlan.cs

* Apply suggestions from code review

Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>

* Added File check

* Tried using same context as create table

* Fix DTO

* Fix GetById and local package saving

* Fix images when migrating

* Implement deletion of all local files

* Only delete local repo file, not file snapshots

* Remove static package path and use the one we save

* Update package repo to export package and remove check for ids when exporting

* Minor fixes

* Fix so that you can download package after creating

* Update savePackage method to export package afterwards

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
This commit is contained in:
Nikolaj Geisle
2021-12-21 14:02:49 +01:00
committed by GitHub
parent 4abace7e51
commit 76cf5037e5
11 changed files with 953 additions and 12 deletions

View File

@@ -367,7 +367,7 @@ namespace Umbraco.Extensions
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <param name="serializer"></param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
internal static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer)
public static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer)
{
return serializer.Serialize(content, false, true);
}

View File

@@ -21,6 +21,7 @@ namespace Umbraco.Cms.Core.Packaging
/// <summary>
/// Manages the storage of installed/created package definitions
/// </summary>
[Obsolete("Packages have now been moved to the database instead of local files, please use CreatedPackageSchemaRepository instead")]
public class PackagesRepository : ICreatedPackagesRepository
{
private readonly IContentService _contentService;
@@ -744,5 +745,13 @@ namespace Umbraco.Cms.Core.Packaging
var packagesXml = XDocument.Load(packagesFile);
return packagesXml;
}
public void DeleteLocalRepositoryFiles()
{
var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile);
File.Delete(packagesFile);
var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath);
Directory.Delete(packagesFolder);
}
}
}

View File

@@ -81,6 +81,8 @@ namespace Umbraco.Cms.Core
public const string UserLogin = TableNamePrefix + "UserLogin";
public const string LogViewerQuery = TableNamePrefix + "LogViewerQuery";
public const string CreatedPackageSchema = TableNamePrefix + "CreatedPackageSchema";
}
}
}

View File

@@ -15,6 +15,8 @@ using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Infrastructure.Packaging;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
using Umbraco.Cms.Infrastructure.Services.Implement;
using Umbraco.Extensions;
@@ -74,16 +76,14 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique<ConflictingPackageData>();
builder.Services.AddUnique<CompiledPackageXmlParser>();
builder.Services.AddUnique<ICreatedPackagesRepository>(factory => CreatePackageRepository(factory, "createdPackages.config"));
builder.Services.AddUnique(factory => CreatePackageRepository(factory, "createdPackages.config"));
builder.Services.AddUnique<ICreatedPackagesRepository, CreatedPackageSchemaRepository>();
builder.Services.AddUnique<PackageDataInstallation>();
builder.Services.AddUnique<IPackageInstallation, PackageInstallation>();
return builder;
}
/// <summary>
/// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository
/// </summary>
private static PackagesRepository CreatePackageRepository(IServiceProvider factory, string packageRepoFileName)
=> new PackagesRepository(
factory.GetRequiredService<IContentService>(),

View File

@@ -78,7 +78,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install
typeof(ContentScheduleDto),
typeof(LogViewerQueryDto),
typeof(ContentVersionCleanupPolicyDto),
typeof(UserGroup2NodeDto)
typeof(UserGroup2NodeDto),
typeof(CreatedPackageSchemaDto)
};
private readonly IUmbracoDatabase _database;

View File

@@ -268,6 +268,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
// TO 9.2.0
To<AddUserGroup2NodeTable>("{0571C395-8F0B-44E9-8E3F-47BDD08D817B}");
To<AddDefaultForNotificationsToggle>("{AD3D3B7F-8E74-45A4-85DB-7FFAD57F9243}");
To<MovePackageXMLToDb>("{A2F22F17-5870-4179-8A8D-2362AA4A0A5F}");
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Cms.Core.Packaging;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_2_0
{
public class MovePackageXMLToDb : MigrationBase
{
private readonly PackagesRepository _packagesRepository;
private readonly PackageDefinitionXmlParser _xmlParser;
/// <summary>
/// Initializes a new instance of the <see cref="MovePackageXMLToDb"/> class.
/// </summary>
public MovePackageXMLToDb(IMigrationContext context, PackagesRepository packagesRepository)
: base(context)
{
_packagesRepository = packagesRepository;
_xmlParser = new PackageDefinitionXmlParser();
}
private void CreateDatabaseTable()
{
// Add CreatedPackage table in database if it doesn't exist
IEnumerable<string> tables = SqlSyntax.GetTablesInSchema(Context.Database);
if (!tables.InvariantContains(CreatedPackageSchemaDto.TableName))
{
Create.Table<CreatedPackageSchemaDto>().Do();
}
}
private void MigrateCreatedPackageFilesToDb()
{
// Load data from file
IEnumerable<PackageDefinition> packages = _packagesRepository.GetAll();
var createdPackageDtos = new List<CreatedPackageSchemaDto>();
foreach (PackageDefinition package in packages)
{
// Create dto from xmlDocument
var dto = new CreatedPackageSchemaDto()
{
Name = package.Name,
Value = _xmlParser.ToXml(package).ToString(),
UpdateDate = DateTime.Now,
PackageId = Guid.NewGuid()
};
createdPackageDtos.Add(dto);
}
_packagesRepository.DeleteLocalRepositoryFiles();
if (createdPackageDtos.Any())
{
// Insert dto into CreatedPackage table
Database.InsertBulk(createdPackageDtos);
}
}
/// <inheritdoc/>
protected override void Migrate()
{
CreateDatabaseTable();
MigrateCreatedPackageFilesToDb();
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using NPoco;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
{
[TableName(TableName)]
[ExplicitColumns]
[PrimaryKey("id")]
public class CreatedPackageSchemaDto
{
public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.CreatedPackageSchema;
[Column("id")]
[PrimaryKeyColumn]
public int Id { get; set; }
[Column("name")]
[Length(255)]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.UniqueNonClustered, ForColumns = "name", Name = "IX_" + TableName + "_Name")]
public string Name { get; set; }
[Column("value")]
[SpecialDbType(SpecialDbTypes.NVARCHARMAX)]
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Value { get; set; }
[Column("updateDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime UpdateDate { get; set; }
[Column("packageId")]
[NullSetting(NullSetting = NullSettings.NotNull)]
public Guid PackageId { get; set; }
}
}

View File

@@ -0,0 +1,758 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Packaging;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
using File = System.IO.File;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
{
/// <inheritdoc />
public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
{
private readonly PackageDefinitionXmlParser _xmlParser;
private readonly IUmbracoDatabase _umbracoDatabase;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly FileSystems _fileSystems;
private readonly IEntityXmlSerializer _serializer;
private readonly IDataTypeService _dataTypeService;
private readonly ILocalizationService _localizationService;
private readonly IFileService _fileService;
private readonly IMediaService _mediaService;
private readonly IMediaTypeService _mediaTypeService;
private readonly IContentService _contentService;
private readonly MediaFileManager _mediaFileManager;
private readonly IMacroService _macroService;
private readonly IContentTypeService _contentTypeService;
private readonly string _tempFolderPath;
private readonly string _mediaFolderPath;
/// <summary>
/// Initializes a new instance of the <see cref="CreatedPackageSchemaRepository"/> class.
/// </summary>
public CreatedPackageSchemaRepository(
IUmbracoDatabase umbracoDatabase,
IHostingEnvironment hostingEnvironment,
IOptions<GlobalSettings> globalSettings,
FileSystems fileSystems,
IEntityXmlSerializer serializer,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
IFileService fileService,
IMediaService mediaService,
IMediaTypeService mediaTypeService,
IContentService contentService,
MediaFileManager mediaFileManager,
IMacroService macroService,
IContentTypeService contentTypeService,
string mediaFolderPath = null,
string tempFolderPath = null)
{
_umbracoDatabase = umbracoDatabase;
_hostingEnvironment = hostingEnvironment;
_fileSystems = fileSystems;
_serializer = serializer;
_dataTypeService = dataTypeService;
_localizationService = localizationService;
_fileService = fileService;
_mediaService = mediaService;
_mediaTypeService = mediaTypeService;
_contentService = contentService;
_mediaFileManager = mediaFileManager;
_macroService = macroService;
_contentTypeService = contentTypeService;
_xmlParser = new PackageDefinitionXmlParser();
_mediaFolderPath = mediaFolderPath ?? globalSettings.Value.UmbracoMediaPath + "/created-packages";
_tempFolderPath =
tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles";
}
public IEnumerable<PackageDefinition> GetAll()
{
Sql<ISqlContext> query = new Sql<ISqlContext>(_umbracoDatabase.SqlContext)
.Select<CreatedPackageSchemaDto>()
.From<CreatedPackageSchemaDto>()
.OrderBy<CreatedPackageSchemaDto>(x => x.Id);
var packageDefinitions = new List<PackageDefinition>();
List<CreatedPackageSchemaDto> xmlSchemas = _umbracoDatabase.Fetch<CreatedPackageSchemaDto>(query);
foreach (CreatedPackageSchemaDto packageSchema in xmlSchemas)
{
var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value));
packageDefinition.Id = packageSchema.Id;
packageDefinition.Name = packageSchema.Name;
packageDefinition.PackageId = packageSchema.PackageId;
packageDefinitions.Add(packageDefinition);
}
return packageDefinitions;
}
public PackageDefinition GetById(int id)
{
Sql<ISqlContext> query = new Sql<ISqlContext>(_umbracoDatabase.SqlContext)
.Select<CreatedPackageSchemaDto>()
.From<CreatedPackageSchemaDto>()
.Where<CreatedPackageSchemaDto>(x => x.Id == id);
List<CreatedPackageSchemaDto> schemaDtos = _umbracoDatabase.Fetch<CreatedPackageSchemaDto>(query);
if (schemaDtos.IsCollectionEmpty())
{
return null;
}
var packageSchema = schemaDtos.First();
var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value));
packageDefinition.Id = packageSchema.Id;
packageDefinition.Name = packageSchema.Name;
packageDefinition.PackageId = packageSchema.PackageId;
return packageDefinition;
}
public void Delete(int id)
{
// Delete package snapshot
var packageDef = GetById(id);
if (File.Exists(packageDef.PackagePath))
{
File.Delete(packageDef.PackagePath);
}
Sql<ISqlContext> query = new Sql<ISqlContext>(_umbracoDatabase.SqlContext)
.Delete<CreatedPackageSchemaDto>()
.Where<CreatedPackageSchemaDto>(x => x.Id == id);
_umbracoDatabase.Delete<CreatedPackageSchemaDto>(query);
}
public bool SavePackage(PackageDefinition definition)
{
if (definition == null)
{
throw new NullReferenceException("PackageDefinition cannot be null when saving");
}
if (definition.Name == null || string.IsNullOrEmpty(definition.Name) || definition.PackagePath == null)
{
return false;
}
// Ensure it's valid
ValidatePackage(definition);
if (definition.Id == default)
{
// Create dto from definition
var dto = new CreatedPackageSchemaDto()
{
Name = definition.Name,
Value = _xmlParser.ToXml(definition).ToString(),
UpdateDate = DateTime.Now,
PackageId = Guid.NewGuid()
};
// Set the ids, we have to save in database first to get the Id
definition.PackageId = dto.PackageId;
var result = _umbracoDatabase.Insert(dto);
var decimalResult = result.SafeCast<decimal>();
definition.Id = decimal.ToInt32(decimalResult);
}
// Save snapshot locally, we do this to the updated packagePath
ExportPackage(definition);
// Create dto from definition
var updatedDto = new CreatedPackageSchemaDto()
{
Name = definition.Name,
Value = _xmlParser.ToXml(definition).ToString(),
Id = definition.Id,
PackageId = definition.PackageId,
UpdateDate = DateTime.Now
};
_umbracoDatabase.Update(updatedDto);
return true;
}
public string ExportPackage(PackageDefinition definition)
{
// Ensure it's valid
ValidatePackage(definition);
// Create a folder for building this package
var temporaryPath =
_hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid());
if (Directory.Exists(temporaryPath) == false)
{
Directory.CreateDirectory(temporaryPath);
}
try
{
// Init package file
XDocument compiledPackageXml = CreateCompiledPackageXml(out XElement root);
// Info section
root.Add(GetPackageInfoXml(definition));
PackageDocumentsAndTags(definition, root);
PackageDocumentTypes(definition, root);
PackageMediaTypes(definition, root);
PackageTemplates(definition, root);
PackageStylesheets(definition, root);
PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem);
PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View",
_fileSystems.PartialViewsFileSystem);
PackageMacros(definition, root);
PackageDictionaryItems(definition, root);
PackageLanguages(definition, root);
PackageDataTypes(definition, root);
Dictionary<string, Stream> mediaFiles = PackageMedia(definition, root);
string fileName;
string tempPackagePath;
if (mediaFiles.Count > 0)
{
fileName = "package.zip";
tempPackagePath = Path.Combine(temporaryPath, fileName);
using (FileStream fileStream = File.OpenWrite(tempPackagePath))
using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml");
using (Stream entryStream = packageXmlEntry.Open())
{
compiledPackageXml.Save(entryStream);
}
foreach (KeyValuePair<string, Stream> mediaFile in mediaFiles)
{
var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}";
ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath);
using (Stream entryStream = mediaEntry.Open())
using (mediaFile.Value)
{
mediaFile.Value.Seek(0, SeekOrigin.Begin);
mediaFile.Value.CopyTo(entryStream);
}
}
}
}
else
{
fileName = "package.xml";
tempPackagePath = Path.Combine(temporaryPath, fileName);
using (FileStream fileStream = File.OpenWrite(tempPackagePath))
{
compiledPackageXml.Save(fileStream);
}
}
var directoryName =
_hostingEnvironment.MapPathWebRoot(
Path.Combine(_mediaFolderPath, definition.Name.Replace(' ', '_')));
if (Directory.Exists(directoryName) == false)
{
Directory.CreateDirectory(directoryName);
}
var finalPackagePath = Path.Combine(directoryName, fileName);
if (File.Exists(finalPackagePath))
{
File.Delete(finalPackagePath);
}
if (File.Exists(finalPackagePath.Replace("zip", "xml")))
{
File.Delete(finalPackagePath.Replace("zip", "xml"));
}
File.Move(tempPackagePath, finalPackagePath);
definition.PackagePath = finalPackagePath;
return finalPackagePath;
}
finally
{
// Clean up
Directory.Delete(temporaryPath, true);
}
}
private XDocument CreateCompiledPackageXml(out XElement root)
{
root = new XElement("umbPackage");
var compiledPackageXml = new XDocument(root);
return compiledPackageXml;
}
private void ValidatePackage(PackageDefinition definition)
{
// Ensure it's valid
var context = new ValidationContext(definition, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(definition, context, results);
if (!isValid)
{
throw new InvalidOperationException("Validation failed, there is invalid data on the model: " +
string.Join(", ", results.Select(x => x.ErrorMessage)));
}
}
private void PackageDataTypes(PackageDefinition definition, XContainer root)
{
var dataTypes = new XElement("DataTypes");
foreach (var dtId in definition.DataTypes)
{
if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
IDataType dataType = _dataTypeService.GetDataType(outInt);
if (dataType == null)
{
continue;
}
dataTypes.Add(_serializer.Serialize(dataType));
}
root.Add(dataTypes);
}
private void PackageLanguages(PackageDefinition definition, XContainer root)
{
var languages = new XElement("Languages");
foreach (var langId in definition.Languages)
{
if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
ILanguage lang = _localizationService.GetLanguageById(outInt);
if (lang == null)
{
continue;
}
languages.Add(_serializer.Serialize(lang));
}
root.Add(languages);
}
private void PackageDictionaryItems(PackageDefinition definition, XContainer root)
{
var rootDictionaryItems = new XElement("DictionaryItems");
var items = new Dictionary<Guid, (IDictionaryItem dictionaryItem, XElement serializedDictionaryValue)>();
foreach (var dictionaryId in definition.DictionaryItems)
{
if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
IDictionaryItem di = _localizationService.GetDictionaryItemById(outInt);
if (di == null)
{
continue;
}
items[di.Key] = (di, _serializer.Serialize(di, false));
}
// organize them in hierarchy ...
var itemCount = items.Count;
var processed = new Dictionary<Guid, XElement>();
while (processed.Count < itemCount)
{
foreach (Guid key in items.Keys.ToList())
{
(IDictionaryItem dictionaryItem, XElement serializedDictionaryValue) = items[key];
if (!dictionaryItem.ParentId.HasValue)
{
// if it has no parent, its definitely just at the root
AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue);
}
else
{
if (processed.ContainsKey(dictionaryItem.ParentId.Value))
{
// we've processed this parent element already so we can just append this xml child to it
AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key,
serializedDictionaryValue);
}
else if (items.ContainsKey(dictionaryItem.ParentId.Value))
{
// we know the parent exists in the dictionary but
// we haven't processed it yet so we'll leave it for the next loop
continue;
}
else
{
// in this case, the parent of this item doesn't exist in our collection, we have no
// choice but to add it to the root.
AppendDictionaryElement(rootDictionaryItems, items, processed, key,
serializedDictionaryValue);
}
}
}
}
root.Add(rootDictionaryItems);
static void AppendDictionaryElement(XElement rootDictionaryItems,
Dictionary<Guid, (IDictionaryItem dictionaryItem, XElement serializedDictionaryValue)> items,
Dictionary<Guid, XElement> processed, Guid key, XElement serializedDictionaryValue)
{
// track it
processed.Add(key, serializedDictionaryValue);
// append it
rootDictionaryItems.Add(serializedDictionaryValue);
// remove it so its not re-processed
items.Remove(key);
}
}
private void PackageMacros(PackageDefinition definition, XContainer root)
{
var packagedMacros = new List<IMacro>();
var macros = new XElement("Macros");
foreach (var macroId in definition.Macros)
{
if (!int.TryParse(macroId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int outInt))
{
continue;
}
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 (exclude views outside of the default directory, e.g. App_Plugins\*\Views)
IEnumerable<string> views = packagedMacros
.Where(x => x.MacroSource.StartsWith(Constants.SystemDirectories.MacroPartials))
.Select(x =>
x.MacroSource.Substring(Constants.SystemDirectories.MacroPartials.Length).Replace('/', '\\'));
PackageStaticFiles(views, root, "MacroPartialViews", "View", _fileSystems.MacroPartialsFileSystem);
}
private void PackageStylesheets(PackageDefinition definition, XContainer root)
{
var stylesheetsXml = new XElement("Stylesheets");
foreach (var stylesheet in definition.Stylesheets)
{
if (stylesheet.IsNullOrWhiteSpace())
{
continue;
}
XElement xml = GetStylesheetXml(stylesheet, true);
if (xml != null)
{
stylesheetsXml.Add(xml);
}
}
root.Add(stylesheetsXml);
}
private void PackageStaticFiles(
IEnumerable<string> filePaths,
XContainer root,
string containerName,
string elementName,
IFileSystem fileSystem)
{
var scriptsXml = new XElement(containerName);
foreach (var file in filePaths)
{
if (file.IsNullOrWhiteSpace())
{
continue;
}
if (!fileSystem.FileExists(file))
{
throw new InvalidOperationException("No file found with path " + file);
}
using (Stream stream = fileSystem.OpenFile(file))
using (var reader = new StreamReader(stream))
{
var fileContents = reader.ReadToEnd();
scriptsXml.Add(
new XElement(
elementName,
new XAttribute("path", file),
new XCData(fileContents)));
}
}
root.Add(scriptsXml);
}
private void PackageTemplates(PackageDefinition definition, XContainer root)
{
var templatesXml = new XElement("Templates");
foreach (var templateId in definition.Templates)
{
if (!int.TryParse(templateId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
ITemplate template = _fileService.GetTemplate(outInt);
if (template == null)
{
continue;
}
templatesXml.Add(_serializer.Serialize(template));
}
root.Add(templatesXml);
}
private void PackageDocumentTypes(PackageDefinition definition, XContainer root)
{
var contentTypes = new HashSet<IContentType>();
var docTypesXml = new XElement("DocumentTypes");
foreach (var dtId in definition.DocumentTypes)
{
if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
IContentType contentType = _contentTypeService.Get(outInt);
if (contentType == null)
{
continue;
}
AddDocumentType(contentType, contentTypes);
}
foreach (IContentType contentType in contentTypes)
{
docTypesXml.Add(_serializer.Serialize(contentType));
}
root.Add(docTypesXml);
}
private void PackageMediaTypes(PackageDefinition definition, XContainer root)
{
var mediaTypes = new HashSet<IMediaType>();
var mediaTypesXml = new XElement("MediaTypes");
foreach (var mediaTypeId in definition.MediaTypes)
{
if (!int.TryParse(mediaTypeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
{
continue;
}
IMediaType mediaType = _mediaTypeService.Get(outInt);
if (mediaType == null)
{
continue;
}
AddMediaType(mediaType, mediaTypes);
}
foreach (IMediaType mediaType in mediaTypes)
{
mediaTypesXml.Add(_serializer.Serialize(mediaType));
}
root.Add(mediaTypesXml);
}
private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root)
{
// Documents and tags
if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId,
NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId))
{
if (contentNodeId > 0)
{
// load content from umbraco.
IContent content = _contentService.GetById(contentNodeId);
if (content != null)
{
var contentXml = definition.ContentLoadChildNodes
? content.ToDeepXml(_serializer)
: content.ToXml(_serializer);
// Create the Documents/DocumentSet node
root.Add(
new XElement(
"Documents",
new XElement(
"DocumentSet",
new XAttribute("importMode", "root"),
contentXml)));
}
}
}
}
private Dictionary<string, Stream> PackageMedia(PackageDefinition definition, XElement root)
{
var mediaStreams = new Dictionary<string, Stream>();
// callback that occurs on each serialized media item
void OnSerializedMedia(IMedia media, XElement xmlMedia)
{
// get the media file path and store that separately in the XML.
// the media file path is different from the URL and is specifically
// extracted using the property editor for this media file and the current media file system.
Stream mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath);
if (mediaStream != null)
{
xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath));
// add the stream to our outgoing stream
mediaStreams.Add(mediaFilePath, mediaStream);
}
}
IEnumerable<IMedia> medias = _mediaService.GetByIds(definition.MediaUdis);
var mediaXml = new XElement(
"MediaItems",
medias.Select(media =>
{
XElement serializedMedia = _serializer.Serialize(
media,
definition.MediaLoadChildNodes,
OnSerializedMedia);
return new XElement("MediaSet", serializedMedia);
}));
root.Add(mediaXml);
return mediaStreams;
}
/// <summary>
/// Gets a macros xml node
/// </summary>
private XElement GetMacroXml(int macroId, out IMacro macro)
{
macro = _macroService.GetById(macroId);
if (macro == null)
{
return null;
}
XElement xml = _serializer.Serialize(macro);
return xml;
}
/// <summary>
/// Converts a umbraco stylesheet to a package xml node
/// </summary>
/// <param name="path">The path of the stylesheet.</param>
/// <param name="includeProperties">if set to <c>true</c> [include properties].</param>
private XElement GetStylesheetXml(string path, bool includeProperties)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(path));
}
IStylesheet stylesheet = _fileService.GetStylesheet(path);
if (stylesheet == null)
{
return null;
}
return _serializer.Serialize(stylesheet, includeProperties);
}
private void AddDocumentType(IContentType dt, HashSet<IContentType> dtl)
{
if (dt.ParentId > 0)
{
IContentType parent = _contentTypeService.Get(dt.ParentId);
if (parent != null)
{
AddDocumentType(parent, dtl);
}
}
if (!dtl.Contains(dt))
{
dtl.Add(dt);
}
}
private void AddMediaType(IMediaType mediaType, HashSet<IMediaType> mediaTypes)
{
if (mediaType.ParentId > 0)
{
IMediaType parent = _mediaTypeService.Get(mediaType.ParentId);
if (parent != null)
{
AddMediaType(parent, mediaTypes);
}
}
if (!mediaTypes.Contains(mediaType))
{
mediaTypes.Add(mediaType);
}
}
private static XElement GetPackageInfoXml(PackageDefinition definition)
{
var info = new XElement("info");
// Package info
var package = new XElement("package");
package.Add(new XElement("name", definition.Name));
info.Add(package);
return info;
}
}
}

View File

@@ -69,7 +69,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (ModelState.IsValid == false)
return ValidationProblem(ModelState);
//save it
// Save it
if (!_packagingService.SaveCreatedPackage(model))
{
return ValidationProblem(
@@ -78,9 +78,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
: $"The package with id {model.Id} was not found");
}
_packagingService.ExportCreatedPackage(model);
//the packagePath will be on the model
// The packagePath will be on the model
return model;
}
@@ -112,11 +110,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return ValidationErrorResult.CreateNotificationValidationErrorResult(
$"Package migration failed on package {packageName} with error: {ex.Message}. Check log for full details.");
}
}
}
[HttpGet]
public IActionResult DownloadCreatedPackage(int id)
public IActionResult DownloadCreatedPackage(int id)
{
var package = _packagingService.GetCreatedPackageById(id);
if (package == null)

View File

@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Packaging;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class CreatedPackageSchemaTests : UmbracoIntegrationTest
{
private ICreatedPackagesRepository CreatedPackageSchemaRepository =>
GetRequiredService<ICreatedPackagesRepository>();
[Test]
public void PackagesRepository_Can_Save_PackageDefinition()
{
var packageDefinition = new PackageDefinition()
{
Name = "NewPack", DocumentTypes = new List<string>() { "Root" }
};
var result = CreatedPackageSchemaRepository.SavePackage(packageDefinition);
Assert.IsTrue(result);
}
[Test]
public void PackageRepository_GetAll_Returns_All_PackageDefinitions()
{
var packageDefinitionList = new List<PackageDefinition>()
{
new () { Name = "PackOne" },
new () { Name = "PackTwo" },
new () { Name = "PackThree" }
};
foreach (PackageDefinition packageDefinition in packageDefinitionList)
{
CreatedPackageSchemaRepository.SavePackage(packageDefinition);
}
var loadedPackageDefinitions = CreatedPackageSchemaRepository.GetAll().ToList();
CollectionAssert.IsNotEmpty(loadedPackageDefinitions);
CollectionAssert.AllItemsAreUnique(loadedPackageDefinitions);
Assert.AreEqual(loadedPackageDefinitions.Count, 3);
}
[Test]
public void PackageRepository_Can_Update_Package()
{
var packageDefinition = new PackageDefinition() { Name = "TestPackage" };
CreatedPackageSchemaRepository.SavePackage(packageDefinition);
packageDefinition.Name = "UpdatedName";
CreatedPackageSchemaRepository.SavePackage(packageDefinition);
var result = CreatedPackageSchemaRepository.GetAll().ToList();
Assert.AreEqual(result.Count, 1);
Assert.AreEqual(result.FirstOrDefault()?.Name, "UpdatedName");
}
}
}