2021-12-21 14:02:49 +01:00
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
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;
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public class CreatedPackageSchemaRepository : ICreatedPackagesRepository
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
private readonly IContentService _contentService;
|
|
|
|
|
private readonly IContentTypeService _contentTypeService;
|
|
|
|
|
private readonly string _createdPackagesFolderPath;
|
|
|
|
|
private readonly IDataTypeService _dataTypeService;
|
|
|
|
|
private readonly IFileService _fileService;
|
|
|
|
|
private readonly FileSystems _fileSystems;
|
|
|
|
|
private readonly IHostingEnvironment _hostingEnvironment;
|
|
|
|
|
private readonly ILocalizationService _localizationService;
|
|
|
|
|
private readonly IMacroService _macroService;
|
|
|
|
|
private readonly MediaFileManager _mediaFileManager;
|
|
|
|
|
private readonly IMediaService _mediaService;
|
|
|
|
|
private readonly IMediaTypeService _mediaTypeService;
|
|
|
|
|
private readonly IEntityXmlSerializer _serializer;
|
|
|
|
|
private readonly string _tempFolderPath;
|
|
|
|
|
private readonly IUmbracoDatabase? _umbracoDatabase;
|
|
|
|
|
private readonly PackageDefinitionXmlParser _xmlParser;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes a new instance of the <see cref="CreatedPackageSchemaRepository" /> class.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public CreatedPackageSchemaRepository(
|
|
|
|
|
IUmbracoDatabaseFactory umbracoDatabaseFactory,
|
|
|
|
|
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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
_umbracoDatabase = umbracoDatabaseFactory.CreateDatabase();
|
|
|
|
|
_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();
|
|
|
|
|
_createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages;
|
|
|
|
|
_tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData + "/PackageFiles";
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public IEnumerable<PackageDefinition> GetAll()
|
|
|
|
|
{
|
|
|
|
|
Sql<ISqlContext> query = new Sql<ISqlContext>(_umbracoDatabase!.SqlContext)
|
|
|
|
|
.Select<CreatedPackageSchemaDto>()
|
|
|
|
|
.From<CreatedPackageSchemaDto>()
|
|
|
|
|
.OrderBy<CreatedPackageSchemaDto>(x => x.Id);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var packageDefinitions = new List<PackageDefinition>();
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
List<CreatedPackageSchemaDto> xmlSchemas = _umbracoDatabase.Fetch<CreatedPackageSchemaDto>(query);
|
|
|
|
|
foreach (CreatedPackageSchemaDto packageSchema in xmlSchemas)
|
|
|
|
|
{
|
2021-12-21 14:02:49 +01:00
|
|
|
var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value));
|
2022-02-24 09:24:56 +01:00
|
|
|
if (packageDefinition is not null)
|
|
|
|
|
{
|
|
|
|
|
packageDefinition.Id = packageSchema.Id;
|
|
|
|
|
packageDefinition.Name = packageSchema.Name;
|
|
|
|
|
packageDefinition.PackageId = packageSchema.PackageId;
|
2022-06-02 08:18:31 +02:00
|
|
|
packageDefinitions.Add(packageDefinition);
|
2022-02-24 09:24:56 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return packageDefinitions;
|
|
|
|
|
}
|
2022-02-24 09:24:56 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
CreatedPackageSchemaDto packageSchema = schemaDtos.First();
|
|
|
|
|
var packageDefinition = _xmlParser.ToPackageDefinition(XElement.Parse(packageSchema.Value));
|
|
|
|
|
if (packageDefinition is not null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
packageDefinition.Id = packageSchema.Id;
|
|
|
|
|
packageDefinition.Name = packageSchema.Name;
|
|
|
|
|
packageDefinition.PackageId = packageSchema.PackageId;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return packageDefinition;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public void Delete(int id)
|
|
|
|
|
{
|
|
|
|
|
// Delete package snapshot
|
|
|
|
|
PackageDefinition? packageDef = GetById(id);
|
|
|
|
|
if (File.Exists(packageDef?.PackagePath))
|
|
|
|
|
{
|
|
|
|
|
File.Delete(packageDef.PackagePath);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
Sql<ISqlContext> query = new Sql<ISqlContext>(_umbracoDatabase!.SqlContext)
|
|
|
|
|
.Delete<CreatedPackageSchemaDto>()
|
|
|
|
|
.Where<CreatedPackageSchemaDto>(x => x.Id == id);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
_umbracoDatabase.Execute(query);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
public bool SavePackage(PackageDefinition? definition)
|
|
|
|
|
{
|
|
|
|
|
if (definition == null)
|
|
|
|
|
{
|
|
|
|
|
throw new NullReferenceException("PackageDefinition cannot be null when saving");
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (string.IsNullOrEmpty(definition.Name) || definition.PackagePath == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Ensure it's valid
|
|
|
|
|
ValidatePackage(definition);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (definition.Id == default)
|
|
|
|
|
{
|
2021-12-21 14:02:49 +01:00
|
|
|
// Create dto from definition
|
2022-06-02 08:18:31 +02:00
|
|
|
var dto = new CreatedPackageSchemaDto
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
|
|
|
|
Name = definition.Name,
|
|
|
|
|
Value = _xmlParser.ToXml(definition).ToString(),
|
2022-06-02 08:18:31 +02:00
|
|
|
UpdateDate = DateTime.Now,
|
|
|
|
|
PackageId = Guid.NewGuid(),
|
2021-12-21 14:02:49 +01:00
|
|
|
};
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Set the ids, we have to save in database first to get the Id
|
|
|
|
|
_umbracoDatabase!.Insert(dto);
|
|
|
|
|
definition.Id = dto.Id;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Save snapshot locally, we do this to the updated packagePath
|
|
|
|
|
ExportPackage(definition);
|
|
|
|
|
|
|
|
|
|
// Create dto from definition
|
|
|
|
|
var updatedDto = new CreatedPackageSchemaDto
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
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);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Create a folder for building this package
|
|
|
|
|
var temporaryPath =
|
|
|
|
|
_hostingEnvironment.MapPathContentRoot(Path.Combine(_tempFolderPath, Guid.NewGuid().ToString()));
|
|
|
|
|
Directory.CreateDirectory(temporaryPath);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml");
|
|
|
|
|
using (Stream entryStream = packageXmlEntry.Open())
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
compiledPackageXml.Save(entryStream);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
mediaFile.Value.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
mediaFile.Value.CopyTo(entryStream);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fileName = "package.xml";
|
|
|
|
|
tempPackagePath = Path.Combine(temporaryPath, fileName);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
using (FileStream fileStream = File.OpenWrite(tempPackagePath))
|
|
|
|
|
{
|
|
|
|
|
compiledPackageXml.Save(fileStream);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var directoryName =
|
|
|
|
|
_hostingEnvironment.MapPathContentRoot(Path.Combine(
|
|
|
|
|
_createdPackagesFolderPath,
|
|
|
|
|
definition.Name.Replace(' ', '_')));
|
|
|
|
|
Directory.CreateDirectory(directoryName);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
var finalPackagePath = Path.Combine(directoryName, fileName);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Clean existing files
|
|
|
|
|
foreach (var packagePath in new[] { definition.PackagePath, finalPackagePath })
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(packagePath))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
File.Delete(packagePath);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// Move to final package path
|
|
|
|
|
File.Move(tempPackagePath, finalPackagePath);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
definition.PackagePath = finalPackagePath;
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return finalPackagePath;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
// Clean up
|
|
|
|
|
Directory.Delete(temporaryPath, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private XDocument CreateCompiledPackageXml(out XElement root)
|
|
|
|
|
{
|
|
|
|
|
root = new XElement("umbPackage");
|
|
|
|
|
var compiledPackageXml = new XDocument(root);
|
|
|
|
|
return compiledPackageXml;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private void ValidatePackage(PackageDefinition definition)
|
|
|
|
|
{
|
|
|
|
|
// Ensure it's valid
|
|
|
|
|
var context = new ValidationContext(definition, null, null);
|
|
|
|
|
var results = new List<ValidationResult>();
|
|
|
|
|
var isValid = Validator.TryValidateObject(definition, context, results);
|
|
|
|
|
if (!isValid)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
throw new InvalidOperationException("Validation failed, there is invalid data on the model: " +
|
|
|
|
|
string.Join(", ", results.Select(x => x.ErrorMessage)));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private void PackageDataTypes(PackageDefinition definition, XContainer root)
|
|
|
|
|
{
|
|
|
|
|
var dataTypes = new XElement("DataTypes");
|
|
|
|
|
foreach (var dtId in definition.DataTypes)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IDataType? dataType = _dataTypeService.GetDataType(outInt);
|
|
|
|
|
if (dataType == null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
|
|
|
|
|
dataTypes.Add(_serializer.Serialize(dataType));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(dataTypes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PackageLanguages(PackageDefinition definition, XContainer root)
|
|
|
|
|
{
|
|
|
|
|
var languages = new XElement("Languages");
|
|
|
|
|
foreach (var langId in definition.Languages)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
ILanguage? lang = _localizationService.GetLanguageById(outInt);
|
|
|
|
|
if (lang == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
languages.Add(_serializer.Serialize(lang));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
IDictionaryItem? di = _localizationService.GetDictionaryItemById(outInt);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (di == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
items[di.Key] = (di, _serializer.Serialize(di, false));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// organize them in hierarchy ...
|
|
|
|
|
var itemCount = items.Count;
|
|
|
|
|
var processed = new Dictionary<Guid, XElement>();
|
|
|
|
|
while (processed.Count < itemCount)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
foreach (Guid key in items.Keys.ToList())
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
(IDictionaryItem dictionaryItem, XElement serializedDictionaryValue) = items[key];
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!dictionaryItem.ParentId.HasValue)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// if it has no parent, its definitely just at the root
|
|
|
|
|
AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
else
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (processed.ContainsKey(dictionaryItem.ParentId.Value))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// 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
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// 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);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(rootDictionaryItems);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// append it
|
|
|
|
|
rootDictionaryItems.Add(serializedDictionaryValue);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// remove it so its not re-processed
|
|
|
|
|
items.Remove(key);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private void PackageMacros(PackageDefinition definition, XContainer root)
|
|
|
|
|
{
|
|
|
|
|
var packagedMacros = new List<IMacro>();
|
|
|
|
|
var macros = new XElement("Macros");
|
|
|
|
|
foreach (var macroId in definition.Macros)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(macroId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
XElement? macroXml = GetMacroXml(outInt, out IMacro? macro);
|
|
|
|
|
if (macroXml is null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
macros.Add(macroXml);
|
|
|
|
|
packagedMacros.Add(macro!);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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[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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (stylesheet.IsNullOrWhiteSpace())
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
XElement? xml = GetStylesheetXml(stylesheet, true);
|
|
|
|
|
if (xml != null)
|
|
|
|
|
{
|
|
|
|
|
stylesheetsXml.Add(xml);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (file.IsNullOrWhiteSpace())
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!fileSystem.FileExists(file))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("No file found with path " + file);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)));
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(scriptsXml);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PackageTemplates(PackageDefinition definition, XContainer root)
|
|
|
|
|
{
|
|
|
|
|
var templatesXml = new XElement("Templates");
|
|
|
|
|
foreach (var templateId in definition.Templates)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(templateId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
ITemplate? template = _fileService.GetTemplate(outInt);
|
|
|
|
|
if (template == null)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
templatesXml.Add(_serializer.Serialize(template));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
IContentType? contentType = _contentTypeService.Get(outInt);
|
|
|
|
|
if (contentType == null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
AddDocumentType(contentType, contentTypes);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
foreach (IContentType contentType in contentTypes)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
docTypesXml.Add(_serializer.Serialize(contentType));
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(docTypesXml);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
IMediaType? mediaType = _mediaTypeService.Get(outInt);
|
|
|
|
|
if (mediaType == null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
continue;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
AddMediaType(mediaType, mediaTypes);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
foreach (IMediaType mediaType in mediaTypes)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
mediaTypesXml.Add(_serializer.Serialize(mediaType));
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(mediaTypesXml);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
XElement contentXml = definition.ContentLoadChildNodes
|
|
|
|
|
? content.ToDeepXml(_serializer)
|
|
|
|
|
: content.ToXml(_serializer);
|
|
|
|
|
|
|
|
|
|
// Create the Documents/DocumentSet node
|
|
|
|
|
root.Add(
|
|
|
|
|
new XElement(
|
|
|
|
|
"Documents",
|
2021-12-21 14:02:49 +01:00
|
|
|
new XElement(
|
2022-06-02 08:18:31 +02:00
|
|
|
"DocumentSet",
|
|
|
|
|
new XAttribute("importMode", "root"),
|
|
|
|
|
contentXml)));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Dictionary<string, Stream> PackageMedia(PackageDefinition definition, XElement root)
|
|
|
|
|
{
|
|
|
|
|
var mediaStreams = new Dictionary<string, Stream>();
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
// callback that occurs on each serialized media item
|
|
|
|
|
void OnSerializedMedia(IMedia media, XElement xmlMedia)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
// 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);
|
2022-07-12 14:02:59 +02:00
|
|
|
if (mediaFilePath is not null)
|
|
|
|
|
{
|
|
|
|
|
xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath));
|
2022-06-02 08:18:31 +02:00
|
|
|
|
2022-07-12 14:02:59 +02:00
|
|
|
// add the stream to our outgoing stream
|
|
|
|
|
mediaStreams.Add(mediaFilePath, mediaStream);
|
|
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
IEnumerable<IMedia> medias = _mediaService.GetByIds(definition.MediaUdis);
|
|
|
|
|
|
|
|
|
|
var mediaXml = new XElement(
|
|
|
|
|
"MediaItems",
|
|
|
|
|
medias.Select(media =>
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
XElement serializedMedia = _serializer.Serialize(
|
|
|
|
|
media,
|
|
|
|
|
definition.MediaLoadChildNodes,
|
|
|
|
|
OnSerializedMedia);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return new XElement("MediaSet", serializedMedia);
|
|
|
|
|
}));
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
root.Add(mediaXml);
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return mediaStreams;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a macros xml node
|
|
|
|
|
/// </summary>
|
|
|
|
|
private XElement? GetMacroXml(int macroId, out IMacro? macro)
|
|
|
|
|
{
|
|
|
|
|
macro = _macroService.GetById(macroId);
|
|
|
|
|
if (macro == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
XElement xml = _serializer.Serialize(macro);
|
|
|
|
|
return xml;
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
/// <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));
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
IStylesheet? stylesheet = _fileService.GetStylesheet(path);
|
|
|
|
|
if (stylesheet == null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
return null;
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
return _serializer.Serialize(stylesheet, includeProperties);
|
|
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private void AddDocumentType(IContentType dt, HashSet<IContentType> dtl)
|
|
|
|
|
{
|
|
|
|
|
if (dt.ParentId > 0)
|
|
|
|
|
{
|
|
|
|
|
IContentType? parent = _contentTypeService.Get(dt.ParentId);
|
|
|
|
|
if (parent != null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
AddDocumentType(parent, dtl);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!dtl.Contains(dt))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
dtl.Add(dt);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
2022-06-02 08:18:31 +02:00
|
|
|
}
|
2021-12-21 14:02:49 +01:00
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
private void AddMediaType(IMediaType mediaType, HashSet<IMediaType> mediaTypes)
|
|
|
|
|
{
|
|
|
|
|
if (mediaType.ParentId > 0)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
IMediaType? parent = _mediaTypeService.Get(mediaType.ParentId);
|
|
|
|
|
if (parent != null)
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
AddMediaType(parent, mediaTypes);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 08:18:31 +02:00
|
|
|
if (!mediaTypes.Contains(mediaType))
|
2021-12-21 14:02:49 +01:00
|
|
|
{
|
2022-06-02 08:18:31 +02:00
|
|
|
mediaTypes.Add(mediaType);
|
2021-12-21 14:02:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|