2019-01-14 17:46:12 +11:00
using System ;
using System.Collections.Generic ;
2019-01-15 22:08:08 +11:00
using System.Globalization ;
2019-01-14 17:46:12 +11:00
using System.Linq ;
2019-11-26 13:28:46 +01:00
using System.Net ;
2019-01-14 17:46:12 +11:00
using System.Xml.Linq ;
using System.Xml.XPath ;
using Umbraco.Core.Collections ;
2019-12-09 14:12:06 +01:00
using Umbraco.Core.Configuration ;
2019-01-14 17:46:12 +11:00
using Umbraco.Core.Logging ;
using Umbraco.Core.Models ;
using Umbraco.Core.Models.Entities ;
2019-01-15 22:08:08 +11:00
using Umbraco.Core.Models.Packaging ;
2019-01-14 17:46:12 +11:00
using Umbraco.Core.PropertyEditors ;
2019-09-16 11:47:05 +10:00
using Umbraco.Core.Scoping ;
2019-01-14 17:46:12 +11:00
using Umbraco.Core.Services ;
using Umbraco.Core.Services.Implement ;
2019-12-09 09:00:00 +01:00
using Umbraco.Core.Strings ;
2019-01-14 17:46:12 +11:00
namespace Umbraco.Core.Packaging
{
internal class PackageDataInstallation
{
private readonly ILogger _logger ;
private readonly IFileService _fileService ;
private readonly IMacroService _macroService ;
private readonly ILocalizationService _localizationService ;
private readonly IDataTypeService _dataTypeService ;
private readonly PropertyEditorCollection _propertyEditors ;
2019-09-16 11:47:05 +10:00
private readonly IScopeProvider _scopeProvider ;
2019-12-09 09:00:00 +01:00
private readonly IShortStringHelper _shortStringHelper ;
2019-12-09 14:12:06 +01:00
private readonly IGlobalSettings _globalSettings ;
2019-12-11 08:13:51 +01:00
private readonly ILocalizedTextService _localizedTextService ;
2019-01-14 17:46:12 +11:00
private readonly IEntityService _entityService ;
private readonly IContentTypeService _contentTypeService ;
private readonly IContentService _contentService ;
public PackageDataInstallation ( ILogger logger , IFileService fileService , IMacroService macroService , ILocalizationService localizationService ,
IDataTypeService dataTypeService , IEntityService entityService , IContentTypeService contentTypeService ,
2019-12-11 08:13:51 +01:00
IContentService contentService , PropertyEditorCollection propertyEditors , IScopeProvider scopeProvider , IShortStringHelper shortStringHelper , IGlobalSettings globalSettings ,
ILocalizedTextService localizedTextService )
2019-01-14 17:46:12 +11:00
{
_logger = logger ;
_fileService = fileService ;
_macroService = macroService ;
_localizationService = localizationService ;
_dataTypeService = dataTypeService ;
_propertyEditors = propertyEditors ;
2019-09-16 11:47:05 +10:00
_scopeProvider = scopeProvider ;
2019-12-09 09:00:00 +01:00
_shortStringHelper = shortStringHelper ;
2019-12-09 14:12:06 +01:00
_globalSettings = globalSettings ;
2019-12-11 08:13:51 +01:00
_localizedTextService = localizedTextService ;
2019-01-14 17:46:12 +11:00
_entityService = entityService ;
_contentTypeService = contentTypeService ;
_contentService = contentService ;
}
2019-09-16 16:31:16 +10:00
#region Install / Uninstall
2019-01-15 22:08:08 +11:00
public UninstallationSummary UninstallPackageData ( PackageDefinition package , int userId )
{
if ( package = = null ) throw new ArgumentNullException ( nameof ( package ) ) ;
var removedTemplates = new List < ITemplate > ( ) ;
var removedMacros = new List < IMacro > ( ) ;
var removedContentTypes = new List < IContentType > ( ) ;
var removedDictionaryItems = new List < IDictionaryItem > ( ) ;
var removedDataTypes = new List < IDataType > ( ) ;
var removedLanguages = new List < ILanguage > ( ) ;
2019-09-16 11:47:05 +10:00
using ( var scope = _scopeProvider . CreateScope ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
//Uninstall templates
foreach ( var item in package . Templates . ToArray ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var found = _fileService . GetTemplate ( nId ) ;
if ( found ! = null )
{
removedTemplates . Add ( found ) ;
_fileService . DeleteTemplate ( found . Alias , userId ) ;
}
package . Templates . Remove ( nId . ToString ( ) ) ;
2019-01-15 22:08:08 +11:00
}
2019-09-16 11:47:05 +10:00
//Uninstall macros
foreach ( var item in package . Macros . ToArray ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var macro = _macroService . GetById ( nId ) ;
if ( macro ! = null )
{
removedMacros . Add ( macro ) ;
_macroService . Delete ( macro , userId ) ;
}
package . Macros . Remove ( nId . ToString ( ) ) ;
2019-01-15 22:08:08 +11:00
}
2019-09-16 11:47:05 +10:00
//Remove Document Types
var contentTypes = new List < IContentType > ( ) ;
var contentTypeService = _contentTypeService ;
foreach ( var item in package . DocumentTypes . ToArray ( ) )
{
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var contentType = contentTypeService . Get ( nId ) ;
if ( contentType = = null ) continue ;
contentTypes . Add ( contentType ) ;
package . DocumentTypes . Remove ( nId . ToString ( CultureInfo . InvariantCulture ) ) ;
}
2019-01-15 22:08:08 +11:00
2019-09-16 11:47:05 +10:00
//Order the DocumentTypes before removing them
if ( contentTypes . Any ( ) )
{
// TODO: I don't think this ordering is necessary
var orderedTypes = ( from contentType in contentTypes
orderby contentType . ParentId descending , contentType . Id descending
select contentType ) . ToList ( ) ;
removedContentTypes . AddRange ( orderedTypes ) ;
contentTypeService . Delete ( orderedTypes , userId ) ;
}
2019-01-15 22:08:08 +11:00
2019-09-16 11:47:05 +10:00
//Remove Dictionary items
foreach ( var item in package . DictionaryItems . ToArray ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var di = _localizationService . GetDictionaryItemById ( nId ) ;
if ( di ! = null )
{
removedDictionaryItems . Add ( di ) ;
_localizationService . Delete ( di , userId ) ;
}
package . DictionaryItems . Remove ( nId . ToString ( ) ) ;
2019-01-15 22:08:08 +11:00
}
2019-09-16 11:47:05 +10:00
//Remove Data types
foreach ( var item in package . DataTypes . ToArray ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var dtd = _dataTypeService . GetDataType ( nId ) ;
if ( dtd ! = null )
{
removedDataTypes . Add ( dtd ) ;
_dataTypeService . Delete ( dtd , userId ) ;
}
package . DataTypes . Remove ( nId . ToString ( ) ) ;
2019-01-15 22:08:08 +11:00
}
2019-09-16 11:47:05 +10:00
//Remove Langs
foreach ( var item in package . Languages . ToArray ( ) )
2019-01-15 22:08:08 +11:00
{
2019-09-16 11:47:05 +10:00
if ( int . TryParse ( item , out var nId ) = = false ) continue ;
var lang = _localizationService . GetLanguageById ( nId ) ;
if ( lang ! = null )
{
removedLanguages . Add ( lang ) ;
_localizationService . Delete ( lang , userId ) ;
}
package . Languages . Remove ( nId . ToString ( ) ) ;
2019-01-15 22:08:08 +11:00
}
2019-09-16 11:47:05 +10:00
scope . Complete ( ) ;
2019-01-15 22:08:08 +11:00
}
// create a summary of what was actually removed, for PackagingService.UninstalledPackage
var summary = new UninstallationSummary
{
MetaData = package ,
TemplatesUninstalled = removedTemplates ,
MacrosUninstalled = removedMacros ,
DocumentTypesUninstalled = removedContentTypes ,
DictionaryItemsUninstalled = removedDictionaryItems ,
DataTypesUninstalled = removedDataTypes ,
LanguagesUninstalled = removedLanguages ,
2019-02-11 22:13:04 +01:00
2019-01-15 22:08:08 +11:00
} ;
return summary ;
}
2019-09-16 16:31:16 +10:00
public InstallationSummary InstallPackageData ( CompiledPackage compiledPackage , int userId )
{
using ( var scope = _scopeProvider . CreateScope ( ) )
{
var installationSummary = new InstallationSummary
{
DataTypesInstalled = ImportDataTypes ( compiledPackage . DataTypes . ToList ( ) , userId ) ,
LanguagesInstalled = ImportLanguages ( compiledPackage . Languages , userId ) ,
DictionaryItemsInstalled = ImportDictionaryItems ( compiledPackage . DictionaryItems , userId ) ,
MacrosInstalled = ImportMacros ( compiledPackage . Macros , userId ) ,
TemplatesInstalled = ImportTemplates ( compiledPackage . Templates . ToList ( ) , userId ) ,
DocumentTypesInstalled = ImportDocumentTypes ( compiledPackage . DocumentTypes , userId )
} ;
//we need a reference to the imported doc types to continue
var importedDocTypes = installationSummary . DocumentTypesInstalled . ToDictionary ( x = > x . Alias , x = > x ) ;
installationSummary . StylesheetsInstalled = ImportStylesheets ( compiledPackage . Stylesheets , userId ) ;
installationSummary . ContentInstalled = ImportContent ( compiledPackage . Documents , importedDocTypes , userId ) ;
2019-09-18 00:10:02 +10:00
scope . Complete ( ) ;
2019-09-16 16:31:16 +10:00
return installationSummary ;
}
}
2019-01-15 22:08:08 +11:00
#endregion
2019-01-14 17:46:12 +11:00
#region Content
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IContent > ImportContent ( IEnumerable < CompiledPackageDocument > docs , IDictionary < string , IContentType > importedDocumentTypes , int userId )
2019-01-14 17:46:12 +11:00
{
2019-09-17 23:31:32 +10:00
return docs . SelectMany ( x = > ImportContent ( x , - 1 , importedDocumentTypes , userId ) ) . ToList ( ) ;
2019-01-14 17:46:12 +11:00
}
/// <summary>
/// Imports and saves package xml as <see cref="IContent"/>
/// </summary>
2019-01-16 00:13:40 +11:00
/// <param name="packageDocument">Xml to import</param>
2019-01-14 17:46:12 +11:00
/// <param name="parentId">Optional parent Id for the content being imported</param>
/// <param name="importedDocumentTypes">A dictionary of already imported document types (basically used as a cache)</param>
/// <param name="userId">Optional Id of the user performing the import</param>
2019-01-22 18:03:39 -05:00
/// <returns>An enumerable list of generated content</returns>
2019-01-16 00:13:40 +11:00
public IEnumerable < IContent > ImportContent ( CompiledPackageDocument packageDocument , int parentId , IDictionary < string , IContentType > importedDocumentTypes , int userId )
2019-01-14 17:46:12 +11:00
{
2019-01-16 00:13:40 +11:00
var element = packageDocument . XmlData ;
2019-01-14 17:46:12 +11:00
2019-01-16 00:13:40 +11:00
var roots = from doc in element . Elements ( )
2019-02-11 22:13:04 +01:00
where ( string ) doc . Attribute ( "isDoc" ) = = ""
select doc ;
2019-01-16 00:13:40 +11:00
var contents = ParseDocumentRootXml ( roots , parentId , importedDocumentTypes ) . ToList ( ) ;
if ( contents . Any ( ) )
_contentService . Save ( contents , userId ) ;
return contents ;
//var attribute = element.Attribute("isDoc");
//if (attribute != null)
//{
// //This is a single doc import
// var elements = new List<XElement> { element };
// var contents = ParseDocumentRootXml(elements, parentId, importedDocumentTypes).ToList();
// if (contents.Any())
// _contentService.Save(contents, userId);
// return contents;
//}
2019-01-14 17:46:12 +11:00
2019-01-16 00:13:40 +11:00
//throw new ArgumentException(
// "The passed in XElement is not valid! It does not contain a root element called " +
// "'DocumentSet' (for structured imports) nor is the first element a Document (for single document import).");
2019-01-14 17:46:12 +11:00
}
private IEnumerable < IContent > ParseDocumentRootXml ( IEnumerable < XElement > roots , int parentId , IDictionary < string , IContentType > importedContentTypes )
{
var contents = new List < IContent > ( ) ;
foreach ( var root in roots )
{
var contentTypeAlias = root . Name . LocalName ;
if ( ! importedContentTypes . ContainsKey ( contentTypeAlias ) )
{
var contentType = FindContentTypeByAlias ( contentTypeAlias ) ;
importedContentTypes . Add ( contentTypeAlias , contentType ) ;
}
var content = CreateContentFromXml ( root , importedContentTypes [ contentTypeAlias ] , null , parentId ) ;
2019-01-16 17:25:46 +11:00
if ( content = = null ) continue ;
2019-01-14 17:46:12 +11:00
contents . Add ( content ) ;
var children = ( from child in root . Elements ( )
where ( string ) child . Attribute ( "isDoc" ) = = ""
select child )
. ToList ( ) ;
2019-01-16 17:25:46 +11:00
if ( children . Count > 0 )
contents . AddRange ( CreateContentFromXml ( children , content , importedContentTypes ) . WhereNotNull ( ) ) ;
2019-01-14 17:46:12 +11:00
}
return contents ;
}
private IEnumerable < IContent > CreateContentFromXml ( IEnumerable < XElement > children , IContent parent , IDictionary < string , IContentType > importedContentTypes )
{
var list = new List < IContent > ( ) ;
foreach ( var child in children )
{
string contentTypeAlias = child . Name . LocalName ;
if ( importedContentTypes . ContainsKey ( contentTypeAlias ) = = false )
{
var contentType = FindContentTypeByAlias ( contentTypeAlias ) ;
importedContentTypes . Add ( contentTypeAlias , contentType ) ;
}
//Create and add the child to the list
var content = CreateContentFromXml ( child , importedContentTypes [ contentTypeAlias ] , parent , default ) ;
list . Add ( content ) ;
//Recursive call
2019-01-16 17:25:46 +11:00
var child1 = child ;
2019-01-14 17:46:12 +11:00
var grandChildren = ( from grand in child1 . Elements ( )
where ( string ) grand . Attribute ( "isDoc" ) = = ""
select grand ) . ToList ( ) ;
if ( grandChildren . Any ( ) )
list . AddRange ( CreateContentFromXml ( grandChildren , content , importedContentTypes ) ) ;
}
return list ;
}
private IContent CreateContentFromXml ( XElement element , IContentType contentType , IContent parent , int parentId )
{
2019-01-16 17:25:46 +11:00
var key = Guid . Empty ;
if ( element . Attribute ( "key" ) ! = null & & Guid . TryParse ( element . Attribute ( "key" ) . Value , out key ) )
{
//if a Key is supplied, then we need to check if the content already exists and if so we ignore the installation for this item
if ( _contentService . GetById ( key ) ! = null )
return null ;
}
2019-01-14 17:46:12 +11:00
var id = element . Attribute ( "id" ) . Value ;
var level = element . Attribute ( "level" ) . Value ;
var sortOrder = element . Attribute ( "sortOrder" ) . Value ;
var nodeName = element . Attribute ( "nodeName" ) . Value ;
var path = element . Attribute ( "path" ) . Value ;
2019-01-16 17:25:46 +11:00
var templateId = element . AttributeValue < int? > ( "template" ) ;
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
var properties = from property in element . Elements ( )
where property . Attribute ( "isDoc" ) = = null
select property ;
2019-02-12 01:58:42 +11:00
//TODO: This will almost never work, we can't reference a template by an INT Id within a package manifest, we need to change the
// packager to package templates by UDI and resolve by the same, in 98% of cases, this isn't going to work, or it will resolve the wrong template.
2019-01-16 17:25:46 +11:00
var template = templateId . HasValue ? _fileService . GetTemplate ( templateId . Value ) : null ;
2019-02-12 01:58:42 +11:00
//now double check this is correct since its an INT it could very well be pointing to an invalid template :/
if ( template ! = null )
{
if ( ! contentType . IsAllowedTemplate ( template . Alias ) )
{
//well this is awkward, we'll set the template to null and it will be wired up to the default template
// when it's persisted in the document repository
template = null ;
}
}
2019-01-14 17:46:12 +11:00
IContent content = parent = = null
? new Content ( nodeName , parentId , contentType )
{
Level = int . Parse ( level ) ,
2019-01-16 17:25:46 +11:00
SortOrder = int . Parse ( sortOrder ) ,
TemplateId = template ? . Id ,
Key = key
2019-01-14 17:46:12 +11:00
}
: new Content ( nodeName , parent , contentType )
{
Level = int . Parse ( level ) ,
2019-01-16 17:25:46 +11:00
SortOrder = int . Parse ( sortOrder ) ,
TemplateId = template ? . Id ,
Key = key
2019-01-14 17:46:12 +11:00
} ;
2019-02-11 22:13:04 +01:00
//Here we make sure that we take composition properties in account as well
//otherwise we would skip them and end up losing content
var propTypes = contentType . CompositionPropertyTypes . Any ( )
? contentType . CompositionPropertyTypes . ToDictionary ( x = > x . Alias , x = > x )
: contentType . PropertyTypes . ToDictionary ( x = > x . Alias , x = > x ) ;
2019-02-12 01:58:42 +11:00
2019-01-14 17:46:12 +11:00
foreach ( var property in properties )
{
string propertyTypeAlias = property . Name . LocalName ;
if ( content . HasProperty ( propertyTypeAlias ) )
{
var propertyValue = property . Value ;
2019-02-12 01:58:42 +11:00
if ( propTypes . TryGetValue ( propertyTypeAlias , out var propertyType ) )
{
//set property value
content . SetValue ( propertyTypeAlias , propertyValue ) ;
}
2019-01-14 17:46:12 +11:00
}
}
return content ;
}
#endregion
2019-01-15 22:08:08 +11:00
#region DocumentTypes
2019-01-14 17:46:12 +11:00
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IContentType > ImportDocumentType ( XElement docTypeElement , int userId )
2019-01-14 17:46:12 +11:00
{
2019-02-11 22:13:04 +01:00
return ImportDocumentTypes ( new [ ] { docTypeElement } , userId ) ;
2019-01-14 17:46:12 +11:00
}
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="docTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
2019-01-22 18:03:39 -05:00
/// <returns>An enumerable list of generated ContentTypes</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IContentType > ImportDocumentTypes ( IEnumerable < XElement > docTypeElements , int userId )
2019-01-14 17:46:12 +11:00
{
return ImportDocumentTypes ( docTypeElements . ToList ( ) , true , userId ) ;
}
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="unsortedDocumentTypes">Xml to import</param>
/// <param name="importStructure">Boolean indicating whether or not to import the </param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
2019-01-22 18:03:39 -05:00
/// <returns>An enumerable list of generated ContentTypes</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IContentType > ImportDocumentTypes ( IReadOnlyCollection < XElement > unsortedDocumentTypes , bool importStructure , int userId )
2019-01-14 17:46:12 +11:00
{
var importedContentTypes = new Dictionary < string , IContentType > ( ) ;
2019-02-11 22:13:04 +01:00
2019-01-22 18:03:39 -05:00
//When you are importing a single doc type we have to assume that the dependencies are already there.
2019-01-14 17:46:12 +11:00
//Otherwise something like uSync won't work.
var graph = new TopoGraph < string , TopoGraph . Node < string , XElement > > ( x = > x . Key , x = > x . Dependencies ) ;
var isSingleDocTypeImport = unsortedDocumentTypes . Count = = 1 ;
var importedFolders = CreateContentTypeFolderStructure ( unsortedDocumentTypes ) ;
if ( isSingleDocTypeImport = = false )
{
//NOTE Here we sort the doctype XElements based on dependencies
//before creating the doc types - this should also allow for a better structure/inheritance support.
foreach ( var documentType in unsortedDocumentTypes )
{
var elementCopy = documentType ;
var infoElement = elementCopy . Element ( "Info" ) ;
var dependencies = new HashSet < string > ( ) ;
//Add the Master as a dependency
if ( string . IsNullOrEmpty ( ( string ) infoElement . Element ( "Master" ) ) = = false )
{
dependencies . Add ( infoElement . Element ( "Master" ) . Value ) ;
}
//Add compositions as dependencies
var compositionsElement = infoElement . Element ( "Compositions" ) ;
if ( compositionsElement ! = null & & compositionsElement . HasElements )
{
var compositions = compositionsElement . Elements ( "Composition" ) ;
if ( compositions . Any ( ) )
{
foreach ( var composition in compositions )
{
dependencies . Add ( composition . Value ) ;
}
}
}
graph . AddItem ( TopoGraph . CreateNode ( infoElement . Element ( "Alias" ) . Value , elementCopy , dependencies . ToArray ( ) ) ) ;
}
}
//Sorting the Document Types based on dependencies - if its not a single doc type import ref. #U4-5921
var documentTypes = isSingleDocTypeImport
? unsortedDocumentTypes . ToList ( )
: graph . GetSortedItems ( ) . Select ( x = > x . Item ) . ToList ( ) ;
//Iterate the sorted document types and create them as IContentType objects
foreach ( var documentType in documentTypes )
{
var alias = documentType . Element ( "Info" ) . Element ( "Alias" ) . Value ;
if ( importedContentTypes . ContainsKey ( alias ) = = false )
{
var contentType = _contentTypeService . Get ( alias ) ;
importedContentTypes . Add ( alias , contentType = = null
? CreateContentTypeFromXml ( documentType , importedContentTypes )
: UpdateContentTypeFromXml ( documentType , contentType , importedContentTypes ) ) ;
}
}
foreach ( var contentType in importedContentTypes )
{
var ct = contentType . Value ;
if ( importedFolders . ContainsKey ( ct . Alias ) )
{
ct . ParentId = importedFolders [ ct . Alias ] ;
}
}
//Save the newly created/updated IContentType objects
var list = importedContentTypes . Select ( x = > x . Value ) . ToList ( ) ;
_contentTypeService . Save ( list , userId ) ;
//Now we can finish the import by updating the 'structure',
//which requires the doc types to be saved/available in the db
if ( importStructure )
{
var updatedContentTypes = new List < IContentType > ( ) ;
2019-01-22 18:03:39 -05:00
//Update the structure here - we can't do it until all DocTypes have been created
2019-01-14 17:46:12 +11:00
foreach ( var documentType in documentTypes )
{
var alias = documentType . Element ( "Info" ) . Element ( "Alias" ) . Value ;
var structureElement = documentType . Element ( "Structure" ) ;
//Ensure that we only update ContentTypes which has actual structure-elements
if ( structureElement = = null | | structureElement . Elements ( "DocumentType" ) . Any ( ) = = false ) continue ;
var updated = UpdateContentTypesStructure ( importedContentTypes [ alias ] , structureElement , importedContentTypes ) ;
updatedContentTypes . Add ( updated ) ;
}
//Update ContentTypes with a newly added structure/list of allowed children
if ( updatedContentTypes . Any ( ) )
_contentTypeService . Save ( updatedContentTypes , userId ) ;
}
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
return list ;
}
private Dictionary < string , int > CreateContentTypeFolderStructure ( IEnumerable < XElement > unsortedDocumentTypes )
{
var importedFolders = new Dictionary < string , int > ( ) ;
foreach ( var documentType in unsortedDocumentTypes )
{
var foldersAttribute = documentType . Attribute ( "Folders" ) ;
var infoElement = documentType . Element ( "Info" ) ;
if ( foldersAttribute ! = null & & infoElement ! = null
//don't import any folder if this is a child doc type - the parent doc type will need to
//exist which contains it's folders
& & ( ( string ) infoElement . Element ( "Master" ) ) . IsNullOrWhiteSpace ( ) )
{
var alias = documentType . Element ( "Info" ) . Element ( "Alias" ) . Value ;
var folders = foldersAttribute . Value . Split ( '/' ) ;
2019-11-26 13:28:46 +01:00
var rootFolder = WebUtility . UrlDecode ( folders [ 0 ] ) ;
2019-01-14 17:46:12 +11:00
//level 1 = root level folders, there can only be one with the same name
var current = _contentTypeService . GetContainers ( rootFolder , 1 ) . FirstOrDefault ( ) ;
if ( current = = null )
{
var tryCreateFolder = _contentTypeService . CreateContainer ( - 1 , rootFolder ) ;
if ( tryCreateFolder = = false )
{
_logger . Error < PackagingService > ( tryCreateFolder . Exception , "Could not create folder: {FolderName}" , rootFolder ) ;
throw tryCreateFolder . Exception ;
}
var rootFolderId = tryCreateFolder . Result . Entity . Id ;
current = _contentTypeService . GetContainer ( rootFolderId ) ;
}
importedFolders . Add ( alias , current . Id ) ;
for ( var i = 1 ; i < folders . Length ; i + + )
{
2019-11-26 13:28:46 +01:00
var folderName = WebUtility . UrlDecode ( folders [ i ] ) ;
2019-01-14 17:46:12 +11:00
current = CreateContentTypeChildFolder ( folderName , current ) ;
importedFolders [ alias ] = current . Id ;
}
}
}
return importedFolders ;
}
private EntityContainer CreateContentTypeChildFolder ( string folderName , IUmbracoEntity current )
{
var children = _entityService . GetChildren ( current . Id ) . ToArray ( ) ;
var found = children . Any ( x = > x . Name . InvariantEquals ( folderName ) ) ;
if ( found )
{
var containerId = children . Single ( x = > x . Name . InvariantEquals ( folderName ) ) . Id ;
return _contentTypeService . GetContainer ( containerId ) ;
}
var tryCreateFolder = _contentTypeService . CreateContainer ( current . Id , folderName ) ;
if ( tryCreateFolder = = false )
{
_logger . Error < PackagingService > ( tryCreateFolder . Exception , "Could not create folder: {FolderName}" , folderName ) ;
throw tryCreateFolder . Exception ;
}
return _contentTypeService . GetContainer ( tryCreateFolder . Result . Entity . Id ) ;
}
private IContentType CreateContentTypeFromXml ( XElement documentType , IReadOnlyDictionary < string , IContentType > importedContentTypes )
{
var infoElement = documentType . Element ( "Info" ) ;
//Name of the master corresponds to the parent
var masterElement = infoElement . Element ( "Master" ) ;
IContentType parent = null ;
if ( masterElement ! = null )
{
var masterAlias = masterElement . Value ;
parent = importedContentTypes . ContainsKey ( masterAlias )
? importedContentTypes [ masterAlias ]
: _contentTypeService . Get ( masterAlias ) ;
}
var alias = infoElement . Element ( "Alias" ) . Value ;
var contentType = parent = = null
2019-12-09 14:12:06 +01:00
? new ContentType ( _shortStringHelper , - 1 ) { Alias = alias }
: new ContentType ( _shortStringHelper , parent , alias ) ;
2019-01-14 17:46:12 +11:00
if ( parent ! = null )
contentType . AddContentType ( parent ) ;
return UpdateContentTypeFromXml ( documentType , contentType , importedContentTypes ) ;
}
private IContentType UpdateContentTypeFromXml ( XElement documentType , IContentType contentType , IReadOnlyDictionary < string , IContentType > importedContentTypes )
{
var infoElement = documentType . Element ( "Info" ) ;
var defaultTemplateElement = infoElement . Element ( "DefaultTemplate" ) ;
contentType . Name = infoElement . Element ( "Name" ) . Value ;
contentType . Icon = infoElement . Element ( "Icon" ) . Value ;
contentType . Thumbnail = infoElement . Element ( "Thumbnail" ) . Value ;
contentType . Description = infoElement . Element ( "Description" ) . Value ;
2019-08-02 14:54:01 +01:00
//NOTE AllowAtRoot, IsListView, IsElement and Variations are new properties in the package xml so we need to verify it exists before using it.
2019-01-14 17:46:12 +11:00
var allowAtRoot = infoElement . Element ( "AllowAtRoot" ) ;
if ( allowAtRoot ! = null )
contentType . AllowedAsRoot = allowAtRoot . Value . InvariantEquals ( "true" ) ;
var isListView = infoElement . Element ( "IsListView" ) ;
if ( isListView ! = null )
contentType . IsContainer = isListView . Value . InvariantEquals ( "true" ) ;
2019-01-16 02:18:02 +11:00
var isElement = infoElement . Element ( "IsElement" ) ;
if ( isElement ! = null )
contentType . IsElement = isElement . Value . InvariantEquals ( "true" ) ;
2019-08-02 14:54:01 +01:00
var variationsElement = infoElement . Element ( "Variations" ) ;
if ( variationsElement ! = null )
contentType . Variations = ( ContentVariation ) Enum . Parse ( typeof ( ContentVariation ) , variationsElement . Value ) ;
2019-01-14 17:46:12 +11:00
//Name of the master corresponds to the parent and we need to ensure that the Parent Id is set
var masterElement = infoElement . Element ( "Master" ) ;
if ( masterElement ! = null )
{
var masterAlias = masterElement . Value ;
IContentType parent = importedContentTypes . ContainsKey ( masterAlias )
? importedContentTypes [ masterAlias ]
: _contentTypeService . Get ( masterAlias ) ;
contentType . SetParent ( parent ) ;
}
//Update Compositions on the ContentType to ensure that they are as is defined in the package xml
var compositionsElement = infoElement . Element ( "Compositions" ) ;
if ( compositionsElement ! = null & & compositionsElement . HasElements )
{
var compositions = compositionsElement . Elements ( "Composition" ) ;
if ( compositions . Any ( ) )
{
foreach ( var composition in compositions )
{
var compositionAlias = composition . Value ;
var compositionContentType = importedContentTypes . ContainsKey ( compositionAlias )
? importedContentTypes [ compositionAlias ]
: _contentTypeService . Get ( compositionAlias ) ;
2019-08-02 14:54:01 +01:00
contentType . AddContentType ( compositionContentType ) ;
2019-01-14 17:46:12 +11:00
}
}
}
UpdateContentTypesAllowedTemplates ( contentType , infoElement . Element ( "AllowedTemplates" ) , defaultTemplateElement ) ;
UpdateContentTypesTabs ( contentType , documentType . Element ( "Tabs" ) ) ;
UpdateContentTypesProperties ( contentType , documentType . Element ( "GenericProperties" ) ) ;
return contentType ;
}
private void UpdateContentTypesAllowedTemplates ( IContentType contentType ,
XElement allowedTemplatesElement , XElement defaultTemplateElement )
{
if ( allowedTemplatesElement ! = null & & allowedTemplatesElement . Elements ( "Template" ) . Any ( ) )
{
var allowedTemplates = contentType . AllowedTemplates . ToList ( ) ;
foreach ( var templateElement in allowedTemplatesElement . Elements ( "Template" ) )
{
var alias = templateElement . Value ;
var template = _fileService . GetTemplate ( alias . ToSafeAlias ( ) ) ;
if ( template ! = null )
{
if ( allowedTemplates . Any ( x = > x . Id = = template . Id ) ) continue ;
allowedTemplates . Add ( template ) ;
}
else
{
_logger . Warn < PackagingService > ( "Packager: Error handling allowed templates. Template with alias '{TemplateAlias}' could not be found." , alias ) ;
}
}
contentType . AllowedTemplates = allowedTemplates ;
}
if ( string . IsNullOrEmpty ( ( string ) defaultTemplateElement ) = = false )
{
var defaultTemplate = _fileService . GetTemplate ( defaultTemplateElement . Value . ToSafeAlias ( ) ) ;
if ( defaultTemplate ! = null )
{
contentType . SetDefaultTemplate ( defaultTemplate ) ;
}
else
{
_logger . Warn < PackagingService > ( "Packager: Error handling default template. Default template with alias '{DefaultTemplateAlias}' could not be found." , defaultTemplateElement . Value ) ;
}
}
}
private void UpdateContentTypesTabs ( IContentType contentType , XElement tabElement )
{
if ( tabElement = = null )
return ;
var tabs = tabElement . Elements ( "Tab" ) ;
foreach ( var tab in tabs )
{
var id = tab . Element ( "Id" ) . Value ; //Do we need to use this for tracking?
var caption = tab . Element ( "Caption" ) . Value ;
if ( contentType . PropertyGroups . Contains ( caption ) = = false )
{
contentType . AddPropertyGroup ( caption ) ;
}
int sortOrder ;
if ( tab . Element ( "SortOrder" ) ! = null & & int . TryParse ( tab . Element ( "SortOrder" ) . Value , out sortOrder ) )
{
// Override the sort order with the imported value
contentType . PropertyGroups [ caption ] . SortOrder = sortOrder ;
}
}
}
private void UpdateContentTypesProperties ( IContentType contentType , XElement genericPropertiesElement )
{
var properties = genericPropertiesElement . Elements ( "GenericProperty" ) ;
foreach ( var property in properties )
{
var dataTypeDefinitionId = new Guid ( property . Element ( "Definition" ) . Value ) ; //Unique Id for a DataTypeDefinition
var dataTypeDefinition = _dataTypeService . GetDataType ( dataTypeDefinitionId ) ;
//If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id
//We look up a DataTypeDefinition that matches
//get the alias as a string for use below
var propertyEditorAlias = property . Element ( "Type" ) . Value . Trim ( ) ;
//If no DataTypeDefinition with the guid from the xml wasn't found OR the ControlId on the DataTypeDefinition didn't match the DataType Id
//We look up a DataTypeDefinition that matches
if ( dataTypeDefinition = = null )
{
var dataTypeDefinitions = _dataTypeService . GetByEditorAlias ( propertyEditorAlias ) ;
if ( dataTypeDefinitions ! = null & & dataTypeDefinitions . Any ( ) )
{
dataTypeDefinition = dataTypeDefinitions . FirstOrDefault ( ) ;
}
}
else if ( dataTypeDefinition . EditorAlias ! = propertyEditorAlias )
{
var dataTypeDefinitions = _dataTypeService . GetByEditorAlias ( propertyEditorAlias ) ;
if ( dataTypeDefinitions ! = null & & dataTypeDefinitions . Any ( ) )
{
dataTypeDefinition = dataTypeDefinitions . FirstOrDefault ( ) ;
}
}
// For backwards compatibility, if no datatype with that ID can be found, we're letting this fail silently.
// This means that the property will not be created.
if ( dataTypeDefinition = = null )
{
2019-01-27 01:17:32 -05:00
// TODO: We should expose this to the UI during install!
2019-01-14 17:46:12 +11:00
_logger . Warn < PackagingService > ( "Packager: Error handling creation of PropertyType '{PropertyType}'. Could not find DataTypeDefintion with unique id '{DataTypeDefinitionId}' nor one referencing the DataType with a property editor alias (or legacy control id) '{PropertyEditorAlias}'. Did the package creator forget to package up custom datatypes? This property will be converted to a label/readonly editor if one exists." ,
property . Element ( "Name" ) . Value , dataTypeDefinitionId , property . Element ( "Type" ) . Value . Trim ( ) ) ;
//convert to a label!
2019-02-15 11:42:29 +01:00
dataTypeDefinition = _dataTypeService . GetByEditorAlias ( Constants . PropertyEditors . Aliases . Label ) . FirstOrDefault ( ) ;
2019-01-14 17:46:12 +11:00
//if for some odd reason this isn't there then ignore
if ( dataTypeDefinition = = null ) continue ;
}
var sortOrder = 0 ;
var sortOrderElement = property . Element ( "SortOrder" ) ;
if ( sortOrderElement ! = null )
int . TryParse ( sortOrderElement . Value , out sortOrder ) ;
2019-12-09 14:12:06 +01:00
var propertyType = new PropertyType ( _shortStringHelper , dataTypeDefinition , property . Element ( "Alias" ) . Value )
2019-01-14 17:46:12 +11:00
{
Name = property . Element ( "Name" ) . Value ,
Description = ( string ) property . Element ( "Description" ) ,
2019-08-02 14:54:01 +01:00
Mandatory = property . Element ( "Mandatory" ) ! = null
? property . Element ( "Mandatory" ) . Value . ToLowerInvariant ( ) . Equals ( "true" )
: false ,
2019-09-30 18:45:00 +02:00
MandatoryMessage = property . Element ( "MandatoryMessage" ) ! = null
? ( string ) property . Element ( "MandatoryMessage" )
: string . Empty ,
2019-01-14 17:46:12 +11:00
ValidationRegExp = ( string ) property . Element ( "Validation" ) ,
2019-09-30 18:45:00 +02:00
ValidationRegExpMessage = property . Element ( "ValidationRegExpMessage" ) ! = null
? ( string ) property . Element ( "ValidationRegExpMessage" )
: string . Empty ,
2019-08-02 14:54:01 +01:00
SortOrder = sortOrder ,
Variations = property . Element ( "Variations" ) ! = null
? ( ContentVariation ) Enum . Parse ( typeof ( ContentVariation ) , property . Element ( "Variations" ) . Value )
: ContentVariation . Nothing
2019-01-14 17:46:12 +11:00
} ;
var tab = ( string ) property . Element ( "Tab" ) ;
if ( string . IsNullOrEmpty ( tab ) )
{
contentType . AddPropertyType ( propertyType ) ;
}
else
{
contentType . AddPropertyType ( propertyType , tab ) ;
}
}
}
private IContentType UpdateContentTypesStructure ( IContentType contentType , XElement structureElement , IReadOnlyDictionary < string , IContentType > importedContentTypes )
{
var allowedChildren = contentType . AllowedContentTypes . ToList ( ) ;
int sortOrder = allowedChildren . Any ( ) ? allowedChildren . Last ( ) . SortOrder : 0 ;
foreach ( var element in structureElement . Elements ( "DocumentType" ) )
{
var alias = element . Value ;
var allowedChild = importedContentTypes . ContainsKey ( alias ) ? importedContentTypes [ alias ] : _contentTypeService . Get ( alias ) ;
if ( allowedChild = = null )
{
_logger . Warn < PackagingService > (
"Packager: Error handling DocumentType structure. DocumentType with alias '{DoctypeAlias}' could not be found and was not added to the structure for '{DoctypeStructureAlias}'." ,
alias , contentType . Alias ) ;
continue ;
}
if ( allowedChildren . Any ( x = > x . Id . IsValueCreated & & x . Id . Value = = allowedChild . Id ) ) continue ;
allowedChildren . Add ( new ContentTypeSort ( new Lazy < int > ( ( ) = > allowedChild . Id ) , sortOrder , allowedChild . Alias ) ) ;
sortOrder + + ;
}
contentType . AllowedContentTypes = allowedChildren ;
return contentType ;
}
/// <summary>
/// Used during Content import to ensure that the ContentType of a content item exists
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <returns></returns>
private IContentType FindContentTypeByAlias ( string contentTypeAlias )
{
var contentType = _contentTypeService . Get ( contentTypeAlias ) ;
if ( contentType = = null )
throw new Exception ( $"ContentType matching the passed in Alias: '{contentTypeAlias}' was null" ) ;
return contentType ;
}
#endregion
#region DataTypes
/// <summary>
/// Imports and saves package xml as <see cref="IDataType"/>
/// </summary>
/// <param name="dataTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the user</param>
2019-01-22 18:03:39 -05:00
/// <returns>An enumerable list of generated DataTypeDefinitions</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IDataType > ImportDataTypes ( IReadOnlyCollection < XElement > dataTypeElements , int userId )
2019-01-14 17:46:12 +11:00
{
var dataTypes = new List < IDataType > ( ) ;
var importedFolders = CreateDataTypeFolderStructure ( dataTypeElements ) ;
foreach ( var dataTypeElement in dataTypeElements )
{
var dataTypeDefinitionName = dataTypeElement . AttributeValue < string > ( "Name" ) ;
var dataTypeDefinitionId = dataTypeElement . AttributeValue < Guid > ( "Definition" ) ;
var databaseTypeAttribute = dataTypeElement . Attribute ( "DatabaseType" ) ;
var parentId = - 1 ;
if ( importedFolders . ContainsKey ( dataTypeDefinitionName ) )
parentId = importedFolders [ dataTypeDefinitionName ] ;
var definition = _dataTypeService . GetDataType ( dataTypeDefinitionId ) ;
2019-01-22 18:03:39 -05:00
//If the datatype definition doesn't already exist we create a new according to the one in the package xml
2019-01-14 17:46:12 +11:00
if ( definition = = null )
{
var databaseType = databaseTypeAttribute ? . Value . EnumParse < ValueStorageType > ( true ) ? ? ValueStorageType . Ntext ;
// the Id field is actually the string property editor Alias
// however, the actual editor with this alias could be installed with the package, and
// therefore not yet part of the _propertyEditors collection, so we cannot try and get
// the actual editor - going with a void editor
var editorAlias = dataTypeElement . Attribute ( "Id" ) ? . Value ? . Trim ( ) ;
if ( ! _propertyEditors . TryGet ( editorAlias , out var editor ) )
2019-12-11 08:13:51 +01:00
editor = new VoidEditor ( _logger , _dataTypeService , _localizationService , _localizedTextService , _shortStringHelper ) { Alias = editorAlias } ;
2019-01-14 17:46:12 +11:00
var dataType = new DataType ( editor )
{
Key = dataTypeDefinitionId ,
Name = dataTypeDefinitionName ,
DatabaseType = databaseType ,
ParentId = parentId
} ;
var configurationAttributeValue = dataTypeElement . Attribute ( "Configuration" ) ? . Value ;
if ( ! string . IsNullOrWhiteSpace ( configurationAttributeValue ) )
dataType . Configuration = editor . GetConfigurationEditor ( ) . FromDatabase ( configurationAttributeValue ) ;
dataTypes . Add ( dataType ) ;
}
else
{
definition . ParentId = parentId ;
_dataTypeService . Save ( definition , userId ) ;
}
}
if ( dataTypes . Count > 0 )
{
_dataTypeService . Save ( dataTypes , userId , true ) ;
}
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
return dataTypes ;
}
private Dictionary < string , int > CreateDataTypeFolderStructure ( IEnumerable < XElement > datatypeElements )
{
var importedFolders = new Dictionary < string , int > ( ) ;
foreach ( var datatypeElement in datatypeElements )
{
var foldersAttribute = datatypeElement . Attribute ( "Folders" ) ;
if ( foldersAttribute ! = null )
{
var name = datatypeElement . Attribute ( "Name" ) . Value ;
var folders = foldersAttribute . Value . Split ( '/' ) ;
2019-11-26 13:28:46 +01:00
var rootFolder = WebUtility . UrlDecode ( folders [ 0 ] ) ;
2019-01-14 17:46:12 +11:00
//there will only be a single result by name for level 1 (root) containers
var current = _dataTypeService . GetContainers ( rootFolder , 1 ) . FirstOrDefault ( ) ;
if ( current = = null )
{
var tryCreateFolder = _dataTypeService . CreateContainer ( - 1 , rootFolder ) ;
if ( tryCreateFolder = = false )
{
_logger . Error < PackagingService > ( tryCreateFolder . Exception , "Could not create folder: {FolderName}" , rootFolder ) ;
throw tryCreateFolder . Exception ;
}
current = _dataTypeService . GetContainer ( tryCreateFolder . Result . Entity . Id ) ;
}
importedFolders . Add ( name , current . Id ) ;
for ( var i = 1 ; i < folders . Length ; i + + )
{
2019-11-26 13:28:46 +01:00
var folderName = WebUtility . UrlDecode ( folders [ i ] ) ;
2019-01-14 17:46:12 +11:00
current = CreateDataTypeChildFolder ( folderName , current ) ;
importedFolders [ name ] = current . Id ;
}
}
}
return importedFolders ;
}
private EntityContainer CreateDataTypeChildFolder ( string folderName , IUmbracoEntity current )
{
var children = _entityService . GetChildren ( current . Id ) . ToArray ( ) ;
var found = children . Any ( x = > x . Name . InvariantEquals ( folderName ) ) ;
if ( found )
{
var containerId = children . Single ( x = > x . Name . InvariantEquals ( folderName ) ) . Id ;
return _dataTypeService . GetContainer ( containerId ) ;
}
var tryCreateFolder = _dataTypeService . CreateContainer ( current . Id , folderName ) ;
if ( tryCreateFolder = = false )
{
_logger . Error < PackagingService > ( tryCreateFolder . Exception , "Could not create folder: {FolderName}" , folderName ) ;
throw tryCreateFolder . Exception ;
}
return _dataTypeService . GetContainer ( tryCreateFolder . Result . Entity . Id ) ;
}
#endregion
#region Dictionary Items
/// <summary>
/// Imports and saves the 'DictionaryItems' part of the package xml as a list of <see cref="IDictionaryItem"/>
/// </summary>
/// <param name="dictionaryItemElementList">Xml to import</param>
/// <param name="userId"></param>
/// <returns>An enumerable list of dictionary items</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IDictionaryItem > ImportDictionaryItems ( IEnumerable < XElement > dictionaryItemElementList , int userId )
2019-01-14 17:46:12 +11:00
{
var languages = _localizationService . GetAllLanguages ( ) . ToList ( ) ;
return ImportDictionaryItems ( dictionaryItemElementList , languages , null , userId ) ;
}
2019-09-17 23:31:32 +10:00
private IReadOnlyList < IDictionaryItem > ImportDictionaryItems ( IEnumerable < XElement > dictionaryItemElementList , List < ILanguage > languages , Guid ? parentId , int userId )
2019-01-14 17:46:12 +11:00
{
var items = new List < IDictionaryItem > ( ) ;
foreach ( var dictionaryItemElement in dictionaryItemElementList )
items . AddRange ( ImportDictionaryItem ( dictionaryItemElement , languages , parentId , userId ) ) ;
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
return items ;
}
private IEnumerable < IDictionaryItem > ImportDictionaryItem ( XElement dictionaryItemElement , List < ILanguage > languages , Guid ? parentId , int userId )
{
var items = new List < IDictionaryItem > ( ) ;
IDictionaryItem dictionaryItem ;
var key = dictionaryItemElement . Attribute ( "Key" ) . Value ;
if ( _localizationService . DictionaryItemExists ( key ) )
dictionaryItem = GetAndUpdateDictionaryItem ( key , dictionaryItemElement , languages ) ;
else
dictionaryItem = CreateNewDictionaryItem ( key , dictionaryItemElement , languages , parentId ) ;
_localizationService . Save ( dictionaryItem , userId ) ;
items . Add ( dictionaryItem ) ;
items . AddRange ( ImportDictionaryItems ( dictionaryItemElement . Elements ( "DictionaryItem" ) , languages , dictionaryItem . Key , userId ) ) ;
return items ;
}
private IDictionaryItem GetAndUpdateDictionaryItem ( string key , XElement dictionaryItemElement , List < ILanguage > languages )
{
var dictionaryItem = _localizationService . GetDictionaryItemByKey ( key ) ;
var translations = dictionaryItem . Translations . ToList ( ) ;
foreach ( var valueElement in dictionaryItemElement . Elements ( "Value" ) . Where ( v = > DictionaryValueIsNew ( translations , v ) ) )
AddDictionaryTranslation ( translations , valueElement , languages ) ;
dictionaryItem . Translations = translations ;
return dictionaryItem ;
}
private static DictionaryItem CreateNewDictionaryItem ( string key , XElement dictionaryItemElement , List < ILanguage > languages , Guid ? parentId )
{
var dictionaryItem = parentId . HasValue ? new DictionaryItem ( parentId . Value , key ) : new DictionaryItem ( key ) ;
var translations = new List < IDictionaryTranslation > ( ) ;
foreach ( var valueElement in dictionaryItemElement . Elements ( "Value" ) )
AddDictionaryTranslation ( translations , valueElement , languages ) ;
dictionaryItem . Translations = translations ;
return dictionaryItem ;
}
private static bool DictionaryValueIsNew ( IEnumerable < IDictionaryTranslation > translations , XElement valueElement )
{
return translations . All ( t = >
String . Compare ( t . Language . IsoCode , valueElement . Attribute ( "LanguageCultureAlias" ) . Value ,
StringComparison . InvariantCultureIgnoreCase ) ! = 0
) ;
}
private static void AddDictionaryTranslation ( ICollection < IDictionaryTranslation > translations , XElement valueElement , IEnumerable < ILanguage > languages )
{
var languageId = valueElement . Attribute ( "LanguageCultureAlias" ) . Value ;
var language = languages . SingleOrDefault ( l = > l . IsoCode = = languageId ) ;
if ( language = = null )
return ;
var translation = new DictionaryTranslation ( language , valueElement . Value ) ;
translations . Add ( translation ) ;
}
#endregion
#region Languages
/// <summary>
/// Imports and saves the 'Languages' part of a package xml as a list of <see cref="ILanguage"/>
/// </summary>
/// <param name="languageElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation</param>
/// <returns>An enumerable list of generated languages</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < ILanguage > ImportLanguages ( IEnumerable < XElement > languageElements , int userId )
2019-01-14 17:46:12 +11:00
{
var list = new List < ILanguage > ( ) ;
foreach ( var languageElement in languageElements )
{
var isoCode = languageElement . AttributeValue < string > ( "CultureAlias" ) ;
var existingLanguage = _localizationService . GetLanguageByIsoCode ( isoCode ) ;
if ( existingLanguage ! = null ) continue ;
2019-12-09 14:12:06 +01:00
var langauge = new Language ( _globalSettings , isoCode )
2019-01-14 17:46:12 +11:00
{
CultureName = languageElement . AttributeValue < string > ( "FriendlyName" )
} ;
_localizationService . Save ( langauge , userId ) ;
list . Add ( langauge ) ;
}
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
return list ;
}
#endregion
#region Macros
/// <summary>
/// Imports and saves the 'Macros' part of a package xml as a list of <see cref="IMacro"/>
/// </summary>
/// <param name="macroElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation</param>
/// <returns></returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IMacro > ImportMacros ( IEnumerable < XElement > macroElements , int userId )
2019-01-14 17:46:12 +11:00
{
var macros = macroElements . Select ( ParseMacroElement ) . ToList ( ) ;
foreach ( var macro in macros )
{
var existing = _macroService . GetByAlias ( macro . Alias ) ;
if ( existing ! = null )
macro . Id = existing . Id ;
_macroService . Save ( macro , userId ) ;
}
return macros ;
}
private IMacro ParseMacroElement ( XElement macroElement )
{
var macroName = macroElement . Element ( "name" ) . Value ;
var macroAlias = macroElement . Element ( "alias" ) . Value ;
var macroType = Enum < MacroTypes > . Parse ( macroElement . Element ( "macroType" ) . Value ) ;
var macroSource = macroElement . Element ( "macroSource" ) . Value ;
//Following xml elements are treated as nullable properties
var useInEditorElement = macroElement . Element ( "useInEditor" ) ;
var useInEditor = false ;
if ( useInEditorElement ! = null & & string . IsNullOrEmpty ( ( string ) useInEditorElement ) = = false )
{
useInEditor = bool . Parse ( useInEditorElement . Value ) ;
}
var cacheDurationElement = macroElement . Element ( "refreshRate" ) ;
var cacheDuration = 0 ;
if ( cacheDurationElement ! = null & & string . IsNullOrEmpty ( ( string ) cacheDurationElement ) = = false )
{
cacheDuration = int . Parse ( cacheDurationElement . Value ) ;
}
var cacheByMemberElement = macroElement . Element ( "cacheByMember" ) ;
var cacheByMember = false ;
if ( cacheByMemberElement ! = null & & string . IsNullOrEmpty ( ( string ) cacheByMemberElement ) = = false )
{
cacheByMember = bool . Parse ( cacheByMemberElement . Value ) ;
}
var cacheByPageElement = macroElement . Element ( "cacheByPage" ) ;
var cacheByPage = false ;
if ( cacheByPageElement ! = null & & string . IsNullOrEmpty ( ( string ) cacheByPageElement ) = = false )
{
cacheByPage = bool . Parse ( cacheByPageElement . Value ) ;
}
var dontRenderElement = macroElement . Element ( "dontRender" ) ;
var dontRender = true ;
if ( dontRenderElement ! = null & & string . IsNullOrEmpty ( ( string ) dontRenderElement ) = = false )
{
dontRender = bool . Parse ( dontRenderElement . Value ) ;
}
var existingMacro = _macroService . GetByAlias ( macroAlias ) as Macro ;
2019-12-09 14:12:06 +01:00
var macro = existingMacro ? ? new Macro ( _shortStringHelper , macroAlias , macroName , macroSource , macroType ,
2019-01-14 17:46:12 +11:00
cacheByPage , cacheByMember , dontRender , useInEditor , cacheDuration ) ;
var properties = macroElement . Element ( "properties" ) ;
if ( properties ! = null )
{
int sortOrder = 0 ;
foreach ( var property in properties . Elements ( ) )
{
var propertyName = property . Attribute ( "name" ) . Value ;
var propertyAlias = property . Attribute ( "alias" ) . Value ;
var editorAlias = property . Attribute ( "propertyType" ) . Value ;
var sortOrderAttribute = property . Attribute ( "sortOrder" ) ;
if ( sortOrderAttribute ! = null )
{
sortOrder = int . Parse ( sortOrderAttribute . Value ) ;
}
if ( macro . Properties . Values . Any ( x = > string . Equals ( x . Alias , propertyAlias , StringComparison . OrdinalIgnoreCase ) ) ) continue ;
macro . Properties . Add ( new MacroProperty ( propertyAlias , propertyName , sortOrder , editorAlias ) ) ;
sortOrder + + ;
}
}
return macro ;
}
#endregion
#region Stylesheets
2019-09-17 23:31:32 +10:00
public IReadOnlyList < IFile > ImportStylesheets ( IEnumerable < XElement > stylesheetElements , int userId )
2019-01-14 17:46:12 +11:00
{
var result = new List < IFile > ( ) ;
foreach ( var n in stylesheetElements )
{
var stylesheetName = n . Element ( "Name" ) ? . Value ;
if ( stylesheetName . IsNullOrWhiteSpace ( ) ) continue ;
var s = _fileService . GetStylesheetByName ( stylesheetName ) ;
if ( s = = null )
{
var fileName = n . Element ( "FileName" ) ? . Value ;
if ( fileName = = null ) continue ;
var content = n . Element ( "Content" ) ? . Value ;
if ( content = = null ) continue ;
s = new Stylesheet ( fileName ) { Content = content } ;
_fileService . SaveStylesheet ( s ) ;
}
foreach ( var prop in n . XPathSelectElements ( "Properties/Property" ) )
{
var alias = prop . Element ( "Alias" ) ? . Value ;
var sp = s . Properties . SingleOrDefault ( p = > p ! = null & & p . Alias = = alias ) ;
var name = prop . Element ( "Name" ) ? . Value ;
if ( sp = = null )
{
sp = new StylesheetProperty ( name , "#" + name . ToSafeAlias ( ) , "" ) ;
s . AddProperty ( sp ) ;
}
else
{
//sp.Text = name;
//Changing the name requires removing the current property and then adding another new one
if ( sp . Name ! = name )
{
s . RemoveProperty ( sp . Name ) ;
var newProp = new StylesheetProperty ( name , sp . Alias , sp . Value ) ;
s . AddProperty ( newProp ) ;
sp = newProp ;
}
}
sp . Alias = alias ;
sp . Value = prop . Element ( "Value" ) ? . Value ;
}
_fileService . SaveStylesheet ( s ) ;
result . Add ( s ) ;
}
return result ;
}
#endregion
#region Templates
public IEnumerable < ITemplate > ImportTemplate ( XElement templateElement , int userId )
{
2019-02-11 22:13:04 +01:00
return ImportTemplates ( new [ ] { templateElement } , userId ) ;
2019-01-14 17:46:12 +11:00
}
/// <summary>
/// Imports and saves package xml as <see cref="ITemplate"/>
/// </summary>
/// <param name="templateElements">Xml to import</param>
/// <param name="userId">Optional user id</param>
2019-01-22 18:03:39 -05:00
/// <returns>An enumerable list of generated Templates</returns>
2019-09-17 23:31:32 +10:00
public IReadOnlyList < ITemplate > ImportTemplates ( IReadOnlyCollection < XElement > templateElements , int userId )
2019-01-14 17:46:12 +11:00
{
var templates = new List < ITemplate > ( ) ;
var graph = new TopoGraph < string , TopoGraph . Node < string , XElement > > ( x = > x . Key , x = > x . Dependencies ) ;
foreach ( var tempElement in templateElements )
{
var dependencies = new List < string > ( ) ;
var elementCopy = tempElement ;
//Ensure that the Master of the current template is part of the import, otherwise we ignore this dependency as part of the dependency sorting.
if ( string . IsNullOrEmpty ( ( string ) elementCopy . Element ( "Master" ) ) = = false & &
templateElements . Any ( x = > ( string ) x . Element ( "Alias" ) = = ( string ) elementCopy . Element ( "Master" ) ) )
{
dependencies . Add ( ( string ) elementCopy . Element ( "Master" ) ) ;
}
else if ( string . IsNullOrEmpty ( ( string ) elementCopy . Element ( "Master" ) ) = = false & &
templateElements . Any ( x = > ( string ) x . Element ( "Alias" ) = = ( string ) elementCopy . Element ( "Master" ) ) = = false )
{
_logger . Info < PackageDataInstallation > (
"Template '{TemplateAlias}' has an invalid Master '{TemplateMaster}', so the reference has been ignored." ,
( string ) elementCopy . Element ( "Alias" ) ,
( string ) elementCopy . Element ( "Master" ) ) ;
}
graph . AddItem ( TopoGraph . CreateNode ( ( string ) elementCopy . Element ( "Alias" ) , elementCopy , dependencies ) ) ;
}
//Sort templates by dependencies to a potential master template
var sorted = graph . GetSortedItems ( ) ;
foreach ( var item in sorted )
{
var templateElement = item . Item ;
var templateName = templateElement . Element ( "Name" ) . Value ;
var alias = templateElement . Element ( "Alias" ) . Value ;
var design = templateElement . Element ( "Design" ) . Value ;
var masterElement = templateElement . Element ( "Master" ) ;
2019-02-11 22:13:04 +01:00
2019-01-14 17:46:12 +11:00
var existingTemplate = _fileService . GetTemplate ( alias ) as Template ;
2019-12-09 14:12:06 +01:00
var template = existingTemplate ? ? new Template ( _shortStringHelper , templateName , alias ) ;
2019-01-14 17:46:12 +11:00
template . Content = design ;
if ( masterElement ! = null & & string . IsNullOrEmpty ( ( string ) masterElement ) = = false )
{
template . MasterTemplateAlias = masterElement . Value ;
var masterTemplate = templates . FirstOrDefault ( x = > x . Alias = = masterElement . Value ) ;
if ( masterTemplate ! = null )
template . MasterTemplateId = new Lazy < int > ( ( ) = > masterTemplate . Id ) ;
}
templates . Add ( template ) ;
}
if ( templates . Any ( ) )
_fileService . SaveTemplate ( templates , userId ) ;
return templates ;
}
private string ViewPath ( string alias )
{
2019-11-13 11:26:03 +01:00
return Constants . SystemDirectories . MvcViews + "/" + alias . Replace ( " " , "" ) + ".cshtml" ;
2019-01-14 17:46:12 +11:00
}
#endregion
}
}