2021-12-21 14:02:49 +01:00
using System.ComponentModel.DataAnnotations ;
using System.IO.Compression ;
using System.Xml.Linq ;
2023-06-06 12:00:53 +02:00
using Microsoft.Extensions.DependencyInjection ;
2021-12-21 14:02:49 +01:00
using Microsoft.Extensions.Options ;
using NPoco ;
using Umbraco.Cms.Core ;
using Umbraco.Cms.Core.Configuration.Models ;
2023-06-06 12:00:53 +02:00
using Umbraco.Cms.Core.DependencyInjection ;
2021-12-21 14:02:49 +01:00
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 ;
2023-06-06 12:00:53 +02:00
using Umbraco.Cms.Infrastructure.Scoping ;
2021-12-21 14:02:49 +01:00
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 ;
2023-06-06 12:00:53 +02:00
private readonly IScopeAccessor _scopeAccessor ;
2024-05-17 12:30:26 +02:00
private readonly ITemplateService _templateService ;
private readonly IDictionaryItemService _dictionaryItemService ;
private readonly ILanguageService _languageService ;
2022-06-02 08:18:31 +02:00
private readonly string _createdPackagesFolderPath ;
private readonly IDataTypeService _dataTypeService ;
private readonly IFileService _fileService ;
private readonly FileSystems _fileSystems ;
private readonly IHostingEnvironment _hostingEnvironment ;
private readonly MediaFileManager _mediaFileManager ;
private readonly IMediaService _mediaService ;
private readonly IMediaTypeService _mediaTypeService ;
private readonly IEntityXmlSerializer _serializer ;
private readonly string _tempFolderPath ;
private readonly PackageDefinitionXmlParser _xmlParser ;
/// <summary>
/// Initializes a new instance of the <see cref="CreatedPackageSchemaRepository" /> class.
/// </summary>
public CreatedPackageSchemaRepository (
IHostingEnvironment hostingEnvironment ,
FileSystems fileSystems ,
IEntityXmlSerializer serializer ,
IDataTypeService dataTypeService ,
IFileService fileService ,
IMediaService mediaService ,
IMediaTypeService mediaTypeService ,
IContentService contentService ,
MediaFileManager mediaFileManager ,
IContentTypeService contentTypeService ,
2023-06-06 12:00:53 +02:00
IScopeAccessor scopeAccessor ,
2024-05-17 12:30:26 +02:00
ITemplateService templateService ,
IDictionaryItemService dictionaryItemService ,
ILanguageService languageService ,
2022-06-02 08:18:31 +02:00
string? mediaFolderPath = null ,
string? tempFolderPath = null )
2021-12-21 14:02:49 +01:00
{
2022-06-02 08:18:31 +02:00
_hostingEnvironment = hostingEnvironment ;
_fileSystems = fileSystems ;
_serializer = serializer ;
_dataTypeService = dataTypeService ;
_fileService = fileService ;
_mediaService = mediaService ;
_mediaTypeService = mediaTypeService ;
_contentService = contentService ;
_mediaFileManager = mediaFileManager ;
_contentTypeService = contentTypeService ;
2023-06-06 12:00:53 +02:00
_scopeAccessor = scopeAccessor ;
2024-05-17 12:30:26 +02:00
_templateService = templateService ;
_dictionaryItemService = dictionaryItemService ;
_languageService = languageService ;
2022-06-02 08:18:31 +02:00
_xmlParser = new PackageDefinitionXmlParser ( ) ;
_createdPackagesFolderPath = mediaFolderPath ? ? Constants . SystemDirectories . CreatedPackages ;
_tempFolderPath = tempFolderPath ? ? Constants . SystemDirectories . TempData + "/PackageFiles" ;
}
2021-12-21 14:02:49 +01:00
2023-06-06 12:00:53 +02:00
[Obsolete("use ctor with all dependencies instead")]
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 ,
IContentTypeService contentTypeService ,
2024-05-17 12:30:26 +02:00
IScopeAccessor scopeAccessor ,
2023-06-06 12:00:53 +02:00
string? mediaFolderPath = null ,
string? tempFolderPath = null )
2024-05-17 12:30:26 +02:00
: this (
hostingEnvironment ,
fileSystems ,
serializer ,
dataTypeService ,
fileService ,
mediaService ,
mediaTypeService ,
contentService ,
mediaFileManager ,
contentTypeService ,
scopeAccessor ,
StaticServiceProvider . Instance . GetRequiredService < ITemplateService > ( ) ,
StaticServiceProvider . Instance . GetRequiredService < IDictionaryItemService > ( ) ,
StaticServiceProvider . Instance . GetRequiredService < ILanguageService > ( ) ,
mediaFolderPath ,
tempFolderPath )
{
}
[Obsolete("use ctor with all dependencies instead")]
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 ,
IContentTypeService contentTypeService ,
string? mediaFolderPath = null ,
string? tempFolderPath = null )
: this (
umbracoDatabaseFactory ,
hostingEnvironment ,
globalSettings ,
fileSystems ,
serializer ,
dataTypeService ,
localizationService ,
fileService ,
mediaService ,
mediaTypeService ,
contentService ,
mediaFileManager ,
contentTypeService ,
StaticServiceProvider . Instance . GetRequiredService < IScopeAccessor > ( ) ,
mediaFolderPath ,
tempFolderPath )
2023-06-06 12:00:53 +02:00
{
}
private IUmbracoDatabase Database = > _scopeAccessor . AmbientScope ? . Database ? ? throw new InvalidOperationException ( "A scope is required to query the database" ) ;
2022-06-02 08:18:31 +02:00
public IEnumerable < PackageDefinition > GetAll ( )
{
2023-06-06 12:00:53 +02:00
Sql < ISqlContext > query = new Sql < ISqlContext > ( Database . SqlContext )
2022-06-02 08:18:31 +02:00
. 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
2023-06-06 12:00:53 +02:00
List < CreatedPackageSchemaDto > xmlSchemas = Database . Fetch < CreatedPackageSchemaDto > ( query ) ;
2022-06-02 08:18:31 +02:00
foreach ( CreatedPackageSchemaDto packageSchema in xmlSchemas )
{
2023-02-23 14:36:21 +01:00
PackageDefinition ? packageDefinition = CreatePackageDefinitionFromSchema ( packageSchema ) ;
2022-02-24 09:24:56 +01:00
if ( packageDefinition is not null )
{
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 )
{
2023-06-06 12:00:53 +02:00
Sql < ISqlContext > query = new Sql < ISqlContext > ( Database . SqlContext )
2022-06-02 08:18:31 +02:00
. Select < CreatedPackageSchemaDto > ( )
. From < CreatedPackageSchemaDto > ( )
. Where < CreatedPackageSchemaDto > ( x = > x . Id = = id ) ;
2023-02-23 14:36:21 +01:00
2023-06-06 12:00:53 +02:00
List < CreatedPackageSchemaDto > schemaDtos = Database . Fetch < CreatedPackageSchemaDto > ( query ) ;
2022-06-02 08:18:31 +02:00
if ( schemaDtos . IsCollectionEmpty ( ) )
{
return null ;
2021-12-21 14:02:49 +01:00
}
2023-02-23 14:36:21 +01:00
return CreatePackageDefinitionFromSchema ( schemaDtos . First ( ) ) ;
}
public PackageDefinition ? GetByKey ( Guid key )
{
2023-06-08 09:35:46 +02:00
Sql < ISqlContext > query = new Sql < ISqlContext > ( Database . SqlContext )
2023-02-23 14:36:21 +01:00
. Select < CreatedPackageSchemaDto > ( )
. From < CreatedPackageSchemaDto > ( )
. Where < CreatedPackageSchemaDto > ( x = > x . PackageId = = key ) ;
2023-08-23 13:24:16 +02:00
if ( _scopeAccessor . AmbientScope is null )
{
return null ;
}
List < CreatedPackageSchemaDto > schemaDtos = _scopeAccessor . AmbientScope . Database . Fetch < CreatedPackageSchemaDto > ( query ) ;
2023-02-23 14:36:21 +01:00
if ( schemaDtos . IsCollectionEmpty ( ) )
2021-12-21 14:02:49 +01:00
{
2023-02-23 14:36:21 +01:00
return null ;
2022-06-02 08:18:31 +02:00
}
2021-12-21 14:02:49 +01:00
2023-02-23 14:36:21 +01:00
return CreatePackageDefinitionFromSchema ( schemaDtos . First ( ) ) ;
2022-06-02 08:18:31 +02:00
}
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
}
2023-06-06 12:00:53 +02:00
Sql < ISqlContext > query = new Sql < ISqlContext > ( Database . SqlContext )
2022-06-02 08:18:31 +02:00
. Delete < CreatedPackageSchemaDto > ( )
. Where < CreatedPackageSchemaDto > ( x = > x . Id = = id ) ;
2021-12-21 14:02:49 +01:00
2023-06-06 12:00:53 +02:00
Database . Execute ( query ) ;
2022-06-02 08:18:31 +02:00
}
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
2023-02-23 14:36:21 +01:00
if ( string . IsNullOrEmpty ( definition . Name ) )
2022-06-02 08:18:31 +02:00
{
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 )
{
2023-06-08 09:35:46 +02:00
Sql < ISqlContext > query = new Sql < ISqlContext > ( Database . SqlContext )
2023-02-23 14:36:21 +01:00
. SelectCount ( )
. From < CreatedPackageSchemaDto > ( )
. Where < CreatedPackageSchemaDto > ( x = > x . Name = = definition . Name ) ;
2023-08-23 13:24:16 +02:00
if ( _scopeAccessor . AmbientScope is null )
{
return false ;
}
var exists = _scopeAccessor . AmbientScope . Database . ExecuteScalar < int > ( query ) ;
2023-02-23 14:36:21 +01:00
if ( exists > 0 )
{
return false ;
}
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
2023-06-06 12:00:53 +02:00
Database ! . Insert ( dto ) ;
2022-06-02 08:18:31 +02:00
definition . Id = dto . Id ;
2021-12-21 14:02:49 +01:00
}
2023-02-23 14:36:21 +01:00
if ( definition . PackageId = = default )
{
definition . PackageId = Guid . NewGuid ( ) ;
}
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 ,
} ;
2023-06-06 12:00:53 +02:00
Database ? . Update ( updatedDto ) ;
2022-06-02 08:18:31 +02:00
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 ! ) ;
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 ( ' ' , '_' ) ) ) ;
2021-12-21 14:02:49 +01:00
2023-11-14 09:14:01 +01:00
var expectedRoot = Path . GetFullPath ( _hostingEnvironment . MapPathContentRoot ( _createdPackagesFolderPath ) ) ;
var finalPackagePath = Path . GetFullPath ( Path . Combine ( directoryName , fileName ) ) ;
2023-12-11 13:59:03 +01:00
if ( finalPackagePath . StartsWith ( expectedRoot ) = = false )
{
throw new IOException ( "Invalid path due to the package name" ) ;
}
2021-12-21 14:02:49 +01:00
2023-11-14 09:14:01 +01:00
Directory . CreateDirectory ( directoryName ) ;
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" ) ;
2024-05-17 12:30:26 +02:00
foreach ( var dataTypeId in definition . DataTypes )
2021-12-21 14:02:49 +01:00
{
2024-05-17 12:30:26 +02:00
if ( Guid . TryParse ( dataTypeId , out Guid dataTypeKey ) is false )
2022-06-02 08:18:31 +02:00
{
continue ;
}
2024-05-17 12:30:26 +02:00
IDataType ? dataType = _dataTypeService . GetAsync ( dataTypeKey ) . GetAwaiter ( ) . GetResult ( ) ;
if ( dataType is 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" ) ;
2024-05-17 12:30:26 +02:00
foreach ( var isoCode in definition . Languages )
2021-12-21 14:02:49 +01:00
{
2024-05-17 12:30:26 +02:00
ILanguage ? lang = _languageService . GetAsync ( isoCode ) . GetAwaiter ( ) . GetResult ( ) ;
2022-06-02 08:18:31 +02:00
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
{
2024-05-17 12:30:26 +02:00
if ( Guid . TryParse ( dictionaryId , out Guid dictionaryKey ) is false )
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
2024-05-17 12:30:26 +02:00
IDictionaryItem ? dictionaryItem = _dictionaryItemService . GetAsync ( dictionaryKey ) . GetAwaiter ( ) . GetResult ( ) ;
2021-12-21 14:02:49 +01:00
2024-05-17 12:30:26 +02:00
if ( dictionaryItem is null )
2022-06-02 08:18:31 +02:00
{
continue ;
2021-12-21 14:02:49 +01:00
}
2024-05-17 12:30:26 +02:00
items [ dictionaryItem . Key ] = ( dictionaryItem , _serializer . Serialize ( dictionaryItem , 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 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
{
2024-05-17 12:30:26 +02:00
if ( Guid . TryParse ( templateId , out Guid templateKey ) is false )
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
2024-05-17 12:30:26 +02:00
ITemplate ? template = _templateService . GetAsync ( templateKey ) . GetAwaiter ( ) . GetResult ( ) ;
2022-06-02 08:18:31 +02:00
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" ) ;
2024-05-17 12:30:26 +02:00
foreach ( var documentTypeIdentifierString in definition . DocumentTypes )
2021-12-21 14:02:49 +01:00
{
2024-05-17 12:30:26 +02:00
if ( Guid . TryParse ( documentTypeIdentifierString , out Guid documentTypeKey ) is false )
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
}
2024-05-17 12:30:26 +02:00
IContentType ? contentType = _contentTypeService . Get ( documentTypeKey ) ;
if ( contentType is 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 )
{
2024-05-17 12:30:26 +02:00
if ( Guid . TryParse ( mediaTypeId , out Guid mediaTypeKey ) is false )
2022-06-02 08:18:31 +02:00
{
continue ;
2021-12-21 14:02:49 +01:00
}
2024-05-17 12:30:26 +02:00
IMediaType ? mediaType = _mediaTypeService . Get ( mediaTypeKey ) ;
2022-06-02 08:18:31 +02:00
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
2024-05-17 12:30:26 +02:00
if ( string . IsNullOrWhiteSpace ( definition . ContentNodeId ) )
2022-06-02 08:18:31 +02:00
{
2024-05-17 12:30:26 +02:00
return ;
}
if ( Guid . TryParse ( definition . ContentNodeId , out Guid contentNodeKey ) is false )
{
return ;
2021-12-21 14:02:49 +01:00
}
2024-05-17 12:30:26 +02:00
IContent ? content = _contentService . GetById ( contentNodeKey ) ;
if ( content is null )
{
return ;
}
XElement 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 ) ) ) ;
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>
/// 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
}
}
2023-02-23 14:36:21 +01:00
private PackageDefinition ? CreatePackageDefinitionFromSchema ( CreatedPackageSchemaDto packageSchema )
{
var packageDefinition = _xmlParser . ToPackageDefinition ( XElement . Parse ( packageSchema . Value ) ) ;
if ( packageDefinition is not null )
{
packageDefinition . Id = packageSchema . Id ;
packageDefinition . Name = packageSchema . Name ;
packageDefinition . PackageId = packageSchema . PackageId ;
}
return packageDefinition ;
}
2021-12-21 14:02:49 +01:00
}