diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 565d693979..3cb3d0d875 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -22,7 +22,7 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
-
+
diff --git a/src/Umbraco.Core/Components/Composers.cs b/src/Umbraco.Core/Components/Composers.cs
index 1c836e9e5c..89deed934e 100644
--- a/src/Umbraco.Core/Components/Composers.cs
+++ b/src/Umbraco.Core/Components/Composers.cs
@@ -76,11 +76,13 @@ namespace Umbraco.Core.Components
var composerTypeList = _composerTypes
.Where(x =>
{
- // use the min level specified by the attribute if any
- // otherwise, user composers have Run min level, anything else is Unknown (always run)
+ // use the min/max levels specified by the attribute if any
+ // otherwise, min: user composers are Run, anything else is Unknown (always run)
+ // max: everything is Run (always run)
var attr = x.GetCustomAttribute();
var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
- return _composition.RuntimeState.Level >= minLevel;
+ var maxLevel = attr?.MaxLevel ?? RuntimeLevel.Run;
+ return _composition.RuntimeState.Level >= minLevel && _composition.RuntimeState.Level <= maxLevel;
})
.ToList();
diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Components/Composition.cs
index dd0b83dcb3..6df86d793f 100644
--- a/src/Umbraco.Core/Components/Composition.cs
+++ b/src/Umbraco.Core/Components/Composition.cs
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Components
public class Composition : IRegister
{
private readonly Dictionary _builders = new Dictionary();
- private readonly Dictionary _uniques = new Dictionary();
+ private readonly Dictionary> _uniques = new Dictionary>();
private readonly IRegister _register;
///
@@ -83,11 +83,32 @@ namespace Umbraco.Core.Components
///
public void Register(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
=> _register.Register(factory, lifetime);
///
- public void RegisterInstance(Type serviceType, object instance)
- => _register.RegisterInstance(serviceType, instance);
+ public void Register(Type serviceType, object instance)
+ => _register.Register(serviceType, instance);
+
+ ///
+ public void RegisterFor(Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ => _register.RegisterFor(lifetime);
+
+ ///
+ public void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ => _register.RegisterFor(implementingType, lifetime);
+
+ ///
+ public void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ => _register.RegisterFor(factory, lifetime);
+
+ ///
+ public void RegisterFor(TService instance)
+ where TService : class
+ => _register.RegisterFor(instance);
///
public void RegisterAuto(Type serviceBaseType)
@@ -104,10 +125,12 @@ namespace Umbraco.Core.Components
onCreating();
foreach (var unique in _uniques.Values)
- unique.RegisterWith(_register);
+ unique(_register);
+ _uniques.Clear(); // no point keep them around
foreach (var builder in _builders.Values)
builder.RegisterWith(_register);
+ _builders.Clear(); // no point keep them around
Configs.RegisterWith(_register);
@@ -123,74 +146,78 @@ namespace Umbraco.Core.Components
#region Unique
+ private string GetUniqueName()
+ => GetUniqueName(typeof(TService));
+
+ private string GetUniqueName(Type serviceType)
+ => serviceType.FullName;
+
+ private string GetUniqueName()
+ => GetUniqueName(typeof(TService), typeof(TTarget));
+
+ private string GetUniqueName(Type serviceType, Type targetType)
+ => serviceType.FullName + "::" + targetType.FullName;
+
///
- /// Registers a unique service.
+ /// Registers a unique service as its own implementation.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUnique(Type serviceType)
+ => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, Lifetime.Singleton);
+
+ ///
+ /// Registers a unique service with an implementation type.
///
/// Unique services have one single implementation, and a Singleton lifetime.
public void RegisterUnique(Type serviceType, Type implementingType)
- => _uniques[serviceType] = new Unique(serviceType, implementingType);
+ => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, implementingType, Lifetime.Singleton);
///
- /// Registers a unique service.
- ///
- /// Unique services have one single implementation, and a Singleton lifetime.
- public void RegisterUnique(Type serviceType, object instance)
- => _uniques[serviceType] = new Unique(serviceType, instance);
-
- ///
- /// Registers a unique service.
+ /// Registers a unique service with an implementation factory.
///
/// Unique services have one single implementation, and a Singleton lifetime.
public void RegisterUnique(Func factory)
- => _uniques[typeof(TService)] = new Unique(factory);
+ where TService : class
+ => _uniques[GetUniqueName()] = register => register.Register(factory, Lifetime.Singleton);
- private class Unique
- {
- private readonly Type _serviceType;
- private readonly Type _implementingType;
- private readonly object _instance;
+ ///
+ /// Registers a unique service with an implementing instance.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUnique(Type serviceType, object instance)
+ => _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, instance);
- protected Unique(Type serviceType)
- {
- _serviceType = serviceType;
- }
+ ///
+ /// Registers a unique service for a target, as its own implementation.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUniqueFor()
+ where TService : class
+ => _uniques[GetUniqueName()] = register => register.RegisterFor(Lifetime.Singleton);
- public Unique(Type serviceType, Type implementingType)
- : this(serviceType)
- {
- _implementingType = implementingType;
- }
+ ///
+ /// Registers a unique service for a target, with an implementing type.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUniqueFor(Type implementingType)
+ where TService : class
+ => _uniques[GetUniqueName()] = register => register.RegisterFor(implementingType, Lifetime.Singleton);
- public Unique(Type serviceType, object instance)
- : this(serviceType)
- {
- _instance = instance;
- }
+ ///
+ /// Registers a unique service for a target, with an implementation factory.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUniqueFor(Func factory)
+ where TService : class
+ => _uniques[GetUniqueName()] = register => register.RegisterFor(factory, Lifetime.Singleton);
- public virtual void RegisterWith(IRegister register)
- {
- if (_implementingType != null)
- register.Register(_serviceType, _implementingType, Lifetime.Singleton);
- else if (_instance != null)
- register.RegisterInstance(_serviceType, _instance);
- }
- }
-
- private class Unique : Unique
- {
- private readonly Func _factory;
-
- public Unique(Func factory)
- : base(typeof(TService))
- {
- _factory = factory;
- }
-
- public override void RegisterWith(IRegister register)
- {
- register.Register(_factory, Lifetime.Singleton);
- }
- }
+ ///
+ /// Registers a unique service for a target, with an implementing instance.
+ ///
+ /// Unique services have one single implementation, and a Singleton lifetime.
+ public void RegisterUniqueFor(TService instance)
+ where TService : class
+ => _uniques[GetUniqueName()] = register => register.RegisterFor(instance);
#endregion
diff --git a/src/Umbraco.Core/Components/CompositionExtensions.cs b/src/Umbraco.Core/Components/CompositionExtensions.cs
index 93d190d17e..bb23e89b81 100644
--- a/src/Umbraco.Core/Components/CompositionExtensions.cs
+++ b/src/Umbraco.Core/Components/CompositionExtensions.cs
@@ -26,15 +26,16 @@ namespace Umbraco.Core.Components
/// The type of the filesystem.
/// The implementing type.
/// The composition.
- /// A factory method creating the supporting filesystem.
/// The register.
- public static void RegisterFileSystem(this Composition composition, Func supportingFileSystemFactory)
+ public static void RegisterFileSystem(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
+ where TFileSystem : class
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance();
- return fileSystems.GetFileSystem(supportingFileSystemFactory(factory));
+ var supporting = factory.GetInstance();
+ return fileSystems.GetFileSystem(supporting.For());
});
}
@@ -43,15 +44,15 @@ namespace Umbraco.Core.Components
///
/// The type of the filesystem.
/// The composition.
- /// A factory method creating the supporting filesystem.
/// The register.
- public static void RegisterFileSystem(this Composition composition, Func supportingFileSystemFactory)
+ public static void RegisterFileSystem(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance();
- return fileSystems.GetFileSystem(supportingFileSystemFactory(factory));
+ var supporting = factory.GetInstance();
+ return fileSystems.GetFileSystem(supporting.For());
});
}
@@ -280,6 +281,22 @@ namespace Umbraco.Core.Components
composition.RegisterUnique(_ => helper);
}
+ ///
+ /// Sets the underlying media filesystem.
+ ///
+ /// A composition.
+ /// A filesystem factory.
+ public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory)
+ => composition.RegisterUniqueFor(filesystemFactory);
+
+ ///
+ /// Sets the underlying media filesystem.
+ ///
+ /// A composition.
+ /// A filesystem factory.
+ public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory)
+ => composition.RegisterUniqueFor(_ => filesystemFactory());
+
#endregion
}
}
diff --git a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs
index 51920660d4..2c698a671a 100644
--- a/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs
+++ b/src/Umbraco.Core/Components/RuntimeLevelAttribute.cs
@@ -1,13 +1,22 @@
using System;
+using Umbraco.Core.Composing;
namespace Umbraco.Core.Components
{
+ ///
+ /// Marks a composer to indicate a minimum and/or maximum runtime level for which the composer would compose.
+ ///
[AttributeUsage(AttributeTargets.Class /*, AllowMultiple = false, Inherited = true*/)]
public class RuntimeLevelAttribute : Attribute
{
- //public RuntimeLevelAttribute()
- //{ }
+ ///
+ /// Gets or sets the minimum runtime level for which the composer would compose.
+ ///
+ public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Install;
- public RuntimeLevel MinLevel { get; set; } = RuntimeLevel.Boot;
+ ///
+ /// Gets or sets the maximum runtime level for which the composer would compose.
+ ///
+ public RuntimeLevel MaxLevel { get; set; } = RuntimeLevel.Run;
}
}
diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
index 7633f6b001..41038ea4e9 100644
--- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// The type of the items.
public abstract class CollectionBuilderBase : ICollectionBuilder
where TBuilder: CollectionBuilderBase
- where TCollection : IBuilderCollection
+ where TCollection : class, IBuilderCollection
{
private readonly List _types = new List();
private readonly object _locker = new object();
diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
index f1fb095406..4c598f27e4 100644
--- a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
+++ b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
@@ -12,10 +12,9 @@ namespace Umbraco.Core.Composing.Composers
*
* Create a component and use it to modify the composition by adding something like:
*
- * composition.Container.RegisterFileSystem(
- * factory => new PhysicalFileSystem("~/somewhere"));
+ * composition.RegisterUniqueFor(...);
*
- * return whatever supporting filesystem you like.
+ * and register whatever supporting filesystem you like.
*
*
* HOW TO IMPLEMENT MY OWN FILESYSTEM
@@ -30,12 +29,15 @@ namespace Umbraco.Core.Composing.Composers
* { }
* }
*
- * The ctor can have more parameters that will be resolved by the container.
+ * The ctor can have more parameters, that will be resolved by the container.
*
* Register your filesystem, in a component:
*
- * composition.Container.RegisterFileSystem(
- * factory => new PhysicalFileSystem("~/my"));
+ * composition.RegisterFileSystem();
+ *
+ * Register the underlying filesystem:
+ *
+ * composition.RegisterUniqueFor(...);
*
* And that's it, you can inject MyFileSystem wherever it's needed.
*
@@ -48,8 +50,8 @@ namespace Umbraco.Core.Composing.Composers
* Make the class implement the interface, then
* register your filesystem, in a component:
*
- * composition.Container.RegisterFileSystem(
- * factory => new PhysicalFileSystem("~/my"));
+ * composition.RegisterFileSystem();
+ * composition.RegisterUniqueFor(...);
*
* And that's it, you can inject IMyFileSystem wherever it's needed.
*
@@ -79,9 +81,16 @@ namespace Umbraco.Core.Composing.Composers
// register the scheme for media paths
composition.RegisterUnique();
- // register the IMediaFileSystem implementation with a supporting filesystem
- composition.RegisterFileSystem(
- factory => new PhysicalFileSystem("~/media"));
+ // register the IMediaFileSystem implementation
+ composition.RegisterFileSystem();
+
+ // register the supporting filesystems provider
+ composition.Register(factory => new SupportingFileSystems(factory), Lifetime.Singleton);
+
+ // register the IFileSystem supporting the IMediaFileSystem
+ // THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
+ // and, SupportingFileSystem.For() returns the underlying filesystem
+ composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
return composition;
}
diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
index 1b77aaa7d6..8c9ccd1088 100644
--- a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
+++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
@@ -6,6 +6,7 @@ using Umbraco.Core.Components;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Packaging;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
@@ -56,6 +57,22 @@ namespace Umbraco.Core.Composing.Composers
factory.GetInstance>(),
factory.GetInstance()));
+ composition.RegisterUnique();
+
+ composition.RegisterUnique();
+
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique(factory => CreatePackageRepository(factory, "createdPackages.config"));
+ composition.RegisterUnique(factory => CreatePackageRepository(factory, "installedPackages.config"));
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique(factory => //factory required because we need to pass in a string path
+ new PackageInstallation(
+ factory.GetInstance(), factory.GetInstance(),
+ factory.GetInstance(), factory.GetInstance(),
+ new DirectoryInfo(IOHelper.GetRootDirectorySafe())));
+
//TODO: These are replaced in the web project - we need to declare them so that
// something is wired up, just not sure this is very nice but will work for now.
composition.RegisterUnique();
@@ -64,6 +81,17 @@ namespace Umbraco.Core.Composing.Composers
return composition;
}
+ ///
+ /// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository
+ ///
+ ///
+ ///
+ ///
+ private static PackagesRepository CreatePackageRepository(IFactory factory, string packageRepoFileName)
+ => new PackagesRepository(
+ factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(), factory.GetInstance(),
+ packageRepoFileName);
+
private static LocalizedTextServiceFileSources SourcesFactory(IFactory container)
{
var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/"));
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs
index cfc465b59d..2307d757c9 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs
@@ -51,6 +51,13 @@ namespace Umbraco.Core.Composing
public static void RegisterUnique(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
+ ///
+ /// Registers a unique service with an implementation type, for a target.
+ ///
+ public static void RegisterUniqueFor(this Composition composition)
+ where TService : class
+ => composition.RegisterUniqueFor(typeof(TImplementing));
+
///
/// Registers a unique service with an implementing instance.
///
diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs
index cf67409925..5c8351924f 100644
--- a/src/Umbraco.Core/Composing/Current.cs
+++ b/src/Umbraco.Core/Composing/Current.cs
@@ -5,6 +5,7 @@ using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
@@ -161,6 +162,9 @@ namespace Umbraco.Core.Composing
internal static PackageActionCollection PackageActions
=> Factory.GetInstance();
+ internal static IPackageActionRunner PackageActionRunner
+ => Factory.GetInstance();
+
internal static PropertyValueConverterCollection PropertyValueConverters
=> Factory.GetInstance();
diff --git a/src/Umbraco.Core/Composing/FactoryExtensions.cs b/src/Umbraco.Core/Composing/FactoryExtensions.cs
index 2640b7f7e6..8027f2c7a1 100644
--- a/src/Umbraco.Core/Composing/FactoryExtensions.cs
+++ b/src/Umbraco.Core/Composing/FactoryExtensions.cs
@@ -17,6 +17,7 @@ namespace Umbraco.Core.Composing
/// An instance of the specified type.
/// Throws an exception if the factory failed to get an instance of the specified type.
public static T GetInstance(this IFactory factory)
+ where T : class
=> (T)factory.GetInstance(typeof(T));
///
@@ -28,6 +29,7 @@ namespace Umbraco.Core.Composing
/// of the specified type. Throws an exception if the factory does know how
/// to get an instance of the specified type, but failed to do so.
public static T TryGetInstance(this IFactory factory)
+ where T : class
=> (T)factory.TryGetInstance(typeof(T));
///
@@ -42,6 +44,7 @@ namespace Umbraco.Core.Composing
/// The arguments are used as dependencies by the factory.
///
public static T CreateInstance(this IFactory factory, params object[] args)
+ where T : class
=> (T)factory.CreateInstance(typeof(T), args);
///
diff --git a/src/Umbraco.Core/Composing/IFactory.cs b/src/Umbraco.Core/Composing/IFactory.cs
index 9a59b1c052..768b9207a3 100644
--- a/src/Umbraco.Core/Composing/IFactory.cs
+++ b/src/Umbraco.Core/Composing/IFactory.cs
@@ -3,13 +3,6 @@ using System.Collections.Generic;
namespace Umbraco.Core.Composing
{
- // Implementing:
- //
- // The factory
- // - always picks the constructor with the most parameters
- // - supports Lazy parameters (and prefers them over non-Lazy) in constructors
- // - what happens with 'releasing' is unclear
-
///
/// Defines a service factory for Umbraco.
///
@@ -28,6 +21,15 @@ namespace Umbraco.Core.Composing
/// Throws an exception if the container failed to get an instance of the specified type.
object GetInstance(Type type);
+ ///
+ /// Gets a targeted instance of a service.
+ ///
+ /// The type of the service.
+ /// The type of the target.
+ /// The instance of the specified type for the specified target.
+ /// Throws an exception if the container failed to get an instance of the specified type.
+ TService GetInstanceFor();
+
///
/// Tries to get an instance of a service.
///
@@ -48,7 +50,8 @@ namespace Umbraco.Core.Composing
/// Gets all instances of a service.
///
/// The type of the service.
- IEnumerable GetAllInstances();
+ IEnumerable GetAllInstances()
+ where TService : class;
///
/// Releases an instance.
diff --git a/src/Umbraco.Core/Composing/IRegister.cs b/src/Umbraco.Core/Composing/IRegister.cs
index 8ad3db5409..cbf12f54a3 100644
--- a/src/Umbraco.Core/Composing/IRegister.cs
+++ b/src/Umbraco.Core/Composing/IRegister.cs
@@ -2,17 +2,6 @@
namespace Umbraco.Core.Composing
{
- // Implementing:
- //
- // The register
- // - supports registering a service, even after some instances of other services have been created
- // - supports re-registering a service, as long as no instance of that service has been created
- // - throws when re-registering a service, and an instance of that service has been created
- //
- // - registers only one implementation of a nameless service, re-registering replaces the previous
- // registration - names are required to register multiple implementations - and getting an
- // IEnumerable of the service, nameless, returns them all
-
///
/// Defines a service register for Umbraco.
///
@@ -36,12 +25,53 @@ namespace Umbraco.Core.Composing
///
/// Registers a service with an implementation factory.
///
- void Register(Func factory, Lifetime lifetime = Lifetime.Transient);
+ void Register(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class;
///
/// Registers a service with an implementing instance.
///
- void RegisterInstance(Type serviceType, object instance);
+ void Register(Type serviceType, object instance);
+
+ ///
+ /// Registers a service for a target, as its own implementation.
+ ///
+ ///
+ /// There can only be one implementation or instanced registered for a service and target;
+ /// what happens if many are registered is not specified.
+ ///
+ void RegisterFor(Lifetime lifetime = Lifetime.Transient)
+ where TService : class;
+
+ ///
+ /// Registers a service for a target, with an implementation type.
+ ///
+ ///
+ /// There can only be one implementation or instanced registered for a service and target;
+ /// what happens if many are registered is not specified.
+ ///
+ void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient)
+ where TService : class;
+
+ ///
+ /// Registers a service for a target, with an implementation factory.
+ ///
+ ///
+ /// There can only be one implementation or instanced registered for a service and target;
+ /// what happens if many are registered is not specified.
+ ///
+ void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class;
+
+ ///
+ /// Registers a service for a target, with an implementing instance.
+ ///
+ ///
+ /// There can only be one implementation or instanced registered for a service and target;
+ /// what happens if many are registered is not specified.
+ ///
+ void RegisterFor(TService instance)
+ where TService : class;
///
/// Registers a base type for auto-registration.
diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs
index a1a06621e9..46b06daf7d 100644
--- a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// The type of the items.
public abstract class LazyCollectionBuilderBase : CollectionBuilderBase
where TBuilder : LazyCollectionBuilderBase
- where TCollection : IBuilderCollection
+ where TCollection : class, IBuilderCollection
{
private readonly List>> _producers = new List>>();
private readonly List _excluded = new List();
diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs
index b39622f66a..d8a554ee8c 100644
--- a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs
+++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs
@@ -102,18 +102,25 @@ namespace Umbraco.Core.Composing.LightInject
///
public IFactory CreateFactory() => this;
+ private static string GetTargetedServiceName() => "TARGET:" + typeof(TTarget).FullName;
+
#region Factory
///
public object GetInstance(Type type)
=> Container.GetInstance(type);
+ ///
+ public TService GetInstanceFor()
+ => Container.GetInstance(GetTargetedServiceName());
+
///
public object TryGetInstance(Type type)
=> Container.TryGetInstance(type);
///
public IEnumerable GetAllInstances()
+ where T : class
=> Container.GetAllInstances();
///
@@ -138,21 +145,7 @@ namespace Umbraco.Core.Composing.LightInject
///
public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient)
- {
- switch (lifetime)
- {
- case Lifetime.Transient:
- Container.Register(serviceType);
- break;
- case Lifetime.Request:
- case Lifetime.Scope:
- case Lifetime.Singleton:
- Container.Register(serviceType, GetLifetime(lifetime));
- break;
- default:
- throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
- }
- }
+ => Container.Register(serviceType, GetLifetime(lifetime));
///
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
@@ -174,22 +167,41 @@ namespace Umbraco.Core.Composing.LightInject
///
public void Register(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
{
- switch (lifetime)
- {
- case Lifetime.Transient:
- Container.Register(f => factory(this));
- break;
- case Lifetime.Request:
- case Lifetime.Scope:
- case Lifetime.Singleton:
- Container.Register(f => factory(this), GetLifetime(lifetime));
- break;
- default:
- throw new NotSupportedException($"Lifetime {lifetime} is not supported.");
- }
+ Container.Register(f => factory(this), GetLifetime(lifetime));
}
+ ///
+ public void Register(Type serviceType, object instance)
+ => Container.RegisterInstance(serviceType, instance);
+
+ ///
+ public void RegisterFor(Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ => RegisterFor(typeof(TService), lifetime);
+
+ ///
+ public void RegisterFor(Type implementingType, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ {
+ // note that there can only be one implementation or instance registered "for" a service
+ Container.Register(typeof(TService), implementingType, GetTargetedServiceName(), GetLifetime(lifetime));
+ }
+
+ ///
+ public void RegisterFor(Func factory, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ {
+ // note that there can only be one implementation or instance registered "for" a service
+ Container.Register(f => factory(this), GetTargetedServiceName(), GetLifetime(lifetime));
+ }
+
+ ///
+ public void RegisterFor(TService instance)
+ where TService : class
+ => Container.RegisterInstance(typeof(TService), instance, GetTargetedServiceName());
+
private ILifetime GetLifetime(Lifetime lifetime)
{
switch (lifetime)
@@ -207,10 +219,6 @@ namespace Umbraco.Core.Composing.LightInject
}
}
- ///
- public void RegisterInstance(Type serviceType, object instance)
- => Container.RegisterInstance(serviceType, instance);
-
///
public void RegisterAuto(Type serviceBaseType)
{
@@ -223,17 +231,6 @@ namespace Umbraco.Core.Composing.LightInject
}, null);
}
- // was the Light-Inject specific way of dealing with args, but we've replaced it with our own
- // beware! does NOT work on singletons, see https://github.com/seesharper/LightInject/issues/294
- //
- /////
- //public void RegisterConstructorDependency(Func factory)
- // => Container.RegisterConstructorDependency((f, x) => factory(this, x));
- //
- /////
- //public void RegisterConstructorDependency(Func factory)
- // => Container.RegisterConstructorDependency((f, x, a) => factory(this, x, a));
-
#endregion
#region Control
@@ -256,21 +253,14 @@ namespace Umbraco.Core.Composing.LightInject
private class AssemblyScanner : IAssemblyScanner
{
- //private readonly IAssemblyScanner _scanner;
-
- //public AssemblyScanner(IAssemblyScanner scanner)
- //{
- // _scanner = scanner;
- //}
-
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry, Func lifetime, Func shouldRegister, Func serviceNameProvider)
{
- // nothing - we *could* scan non-Umbraco assemblies, though
+ // nothing - we don't want LightInject to scan
}
public void Scan(Assembly assembly, IServiceRegistry serviceRegistry)
{
- // nothing - we *could* scan non-Umbraco assemblies, though
+ // nothing - we don't want LightInject to scan
}
}
diff --git a/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs
index 470079c6c0..897c58dd43 100644
--- a/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs
+++ b/src/Umbraco.Core/Composing/LightInject/MixedLightInjectScopeManagerProvider.cs
@@ -13,6 +13,9 @@ namespace Umbraco.Core.Composing.LightInject
// of PerWebRequestScopeManagerProvider - but all delegates see is the mixed one - and therefore
// they can transition without issues.
//
+ // The PerWebRequestScopeManager maintains the scope in HttpContext and LightInject registers a
+ // module (PreApplicationStartMethod) which disposes it on EndRequest
+ //
// the mixed provider is installed in container.ConfigureUmbracoCore() and then,
// when doing eg container.EnableMvc() or anything that does container.EnablePerWebRequestScope()
// we need to take great care to preserve the mixed scope manager provider!
diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs
index bde1bf96c5..241b84d8d2 100644
--- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Composing
/// The type of the items.
public abstract class OrderedCollectionBuilderBase : CollectionBuilderBase
where TBuilder : OrderedCollectionBuilderBase
- where TCollection : IBuilderCollection
+ where TCollection : class, IBuilderCollection
{
protected abstract TBuilder This { get; }
diff --git a/src/Umbraco.Core/Composing/RegisterExtensions.cs b/src/Umbraco.Core/Composing/RegisterExtensions.cs
index 4db1a2e9e4..d1eacc0c0f 100644
--- a/src/Umbraco.Core/Composing/RegisterExtensions.cs
+++ b/src/Umbraco.Core/Composing/RegisterExtensions.cs
@@ -11,22 +11,32 @@
public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient)
=> register.Register(typeof(TService), typeof(TImplementing), lifetime);
+ ///
+ /// Registers a service with an implementation type, for a target.
+ ///
+ public static void RegisterFor(this IRegister register, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
+ => register.RegisterFor(typeof(TImplementing), lifetime);
+
///
/// Registers a service as its own implementation.
///
public static void Register(this IRegister register, Lifetime lifetime = Lifetime.Transient)
+ where TService : class
=> register.Register(typeof(TService), lifetime);
///
/// Registers a service with an implementing instance.
///
- public static void RegisterInstance(this IRegister register, TService instance)
- => register.RegisterInstance(typeof(TService), instance);
+ public static void Register(this IRegister register, TService instance)
+ where TService : class
+ => register.Register(typeof(TService), instance);
///
/// Registers a base type for auto-registration.
///
public static void RegisterAuto(this IRegister register)
+ where TServiceBase : class
=> register.RegisterAuto(typeof(TServiceBase));
}
}
diff --git a/src/Umbraco.Core/Composing/TargetedServiceFactory.cs b/src/Umbraco.Core/Composing/TargetedServiceFactory.cs
new file mode 100644
index 0000000000..53022c0043
--- /dev/null
+++ b/src/Umbraco.Core/Composing/TargetedServiceFactory.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Provides a base class for targeted service factories.
+ ///
+ ///
+ public abstract class TargetedServiceFactory
+ {
+ private readonly IFactory _factory;
+
+ protected TargetedServiceFactory(IFactory factory)
+ {
+ _factory = factory;
+ }
+
+ public TService For() => _factory.GetInstanceFor();
+ }
+}
diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs
index 3121e869c3..acb12ab575 100644
--- a/src/Umbraco.Core/Composing/TypeLoader.cs
+++ b/src/Umbraco.Core/Composing/TypeLoader.cs
@@ -405,7 +405,7 @@ namespace Umbraco.Core.Composing
break;
case LocalTempStorage.Default:
default:
- var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache");
+ var tempFolder = IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache");
_fileBasePath = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName);
break;
}
diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs
index da47c53bf8..f8ecc11d98 100644
--- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// The type of the items.
public abstract class WeightedCollectionBuilderBase : CollectionBuilderBase
where TBuilder : WeightedCollectionBuilderBase
- where TCollection : IBuilderCollection
+ where TCollection : class, IBuilderCollection
{
protected abstract TBuilder This { get; }
diff --git a/src/Umbraco.Core/Configuration/Configs.cs b/src/Umbraco.Core/Configuration/Configs.cs
index 3dbbe5d4ff..51e1a327a0 100644
--- a/src/Umbraco.Core/Configuration/Configs.cs
+++ b/src/Umbraco.Core/Configuration/Configs.cs
@@ -100,7 +100,7 @@ namespace Umbraco.Core.Configuration
if (_registerings == null)
throw new InvalidOperationException("Configurations have already been registered.");
- register.RegisterInstance(this);
+ register.Register(this);
foreach (var registering in _registerings.Values)
registering(register);
diff --git a/src/Umbraco.Core/Constants-Packaging.cs b/src/Umbraco.Core/Constants-Packaging.cs
deleted file mode 100644
index 37f2c23fa3..0000000000
--- a/src/Umbraco.Core/Constants-Packaging.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-namespace Umbraco.Core
-{
- public static partial class Constants
- {
- ///
- /// Defines the constants used for Umbraco packages in the package.config xml
- ///
- public static class Packaging
- {
- public const string UmbPackageNodeName = "umbPackage";
- public const string DataTypesNodeName = "DataTypes";
- public const string PackageXmlFileName = "package.xml";
- public const string UmbracoPackageExtention = ".umb";
- public const string DataTypeNodeName = "DataType";
- public const string LanguagesNodeName = "Languages";
- public const string FilesNodeName = "files";
- public const string StylesheetsNodeName = "Stylesheets";
- public const string TemplatesNodeName = "Templates";
- public const string NameNodeName = "Name";
- public const string TemplateNodeName = "Template";
- public const string AliasNodeNameSmall = "alias";
- public const string AliasNodeNameCapital = "Alias";
- public const string DictionaryItemsNodeName = "DictionaryItems";
- public const string DictionaryItemNodeName = "DictionaryItem";
- public const string MacrosNodeName = "Macros";
- public const string DocumentsNodeName = "Documents";
- public const string DocumentSetNodeName = "DocumentSet";
- public const string DocumentTypesNodeName = "DocumentTypes";
- public const string DocumentTypeNodeName = "DocumentType";
- public const string FileNodeName = "file";
- public const string OrgNameNodeName = "orgName";
- public const string OrgPathNodeName = "orgPath";
- public const string GuidNodeName = "guid";
- public const string StylesheetNodeName = "styleSheet";
- public const string MacroNodeName = "macro";
- public const string InfoNodeName = "info";
- public const string PackageRequirementsMajorXpath = "./package/requirements/major";
- public const string PackageRequirementsMinorXpath = "./package/requirements/minor";
- public const string PackageRequirementsPatchXpath = "./package/requirements/patch";
- public const string PackageNameXpath = "./package/name";
- public const string PackageVersionXpath = "./package/version";
- public const string PackageUrlXpath = "./package/url";
- public const string PackageLicenseXpath = "./package/license";
- public const string PackageLicenseXpathUrlAttribute = "url";
- public const string AuthorNameXpath = "./author/name";
- public const string AuthorWebsiteXpath = "./author/website";
- public const string ReadmeXpath = "./readme";
- public const string ControlNodeName = "control";
- public const string ActionNodeName = "Action";
- public const string ActionsNodeName = "Actions";
- public const string UndoNodeAttribute = "undo";
- public const string RunatNodeAttribute = "runat";
- }
- }
-}
diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs
index 5e2d44c90d..8c27c23604 100644
--- a/src/Umbraco.Core/ContentExtensions.cs
+++ b/src/Umbraco.Core/ContentExtensions.cs
@@ -308,84 +308,45 @@ namespace Umbraco.Core
/// Creates the full xml representation for the object and all of it's descendants
///
/// to generate xml for
- ///
+ ///
/// Xml representation of the passed in
- internal static XElement ToDeepXml(this IContent content, IPackagingService packagingService)
+ internal static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer)
{
- return packagingService.Export(content, true, raiseEvents: false);
- }
-
-
- [Obsolete("Use the overload that declares the IPackagingService to use")]
- public static XElement ToXml(this IContent content)
- {
- return Current.Services.PackagingService.Export(content, raiseEvents: false);
+ return serializer.Serialize(content, false, true);
}
///
/// Creates the xml representation for the object
///
/// to generate xml for
- ///
+ ///
/// Xml representation of the passed in
- public static XElement ToXml(this IContent content, IPackagingService packagingService)
+ public static XElement ToXml(this IContent content, IEntityXmlSerializer serializer)
{
- return packagingService.Export(content, raiseEvents: false);
- }
-
- [Obsolete("Use the overload that declares the IPackagingService to use")]
- public static XElement ToXml(this IMedia media)
- {
- return Current.Services.PackagingService.Export(media, raiseEvents: false);
+ return serializer.Serialize(content, false, false);
}
+
///
/// Creates the xml representation for the object
///
/// to generate xml for
- ///
+ ///
/// Xml representation of the passed in
- public static XElement ToXml(this IMedia media, IPackagingService packagingService)
+ public static XElement ToXml(this IMedia media, IEntityXmlSerializer serializer)
{
- return packagingService.Export(media, raiseEvents: false);
+ return serializer.Serialize(media);
}
- ///
- /// Creates the full xml representation for the object and all of it's descendants
- ///
- /// to generate xml for
- ///
- /// Xml representation of the passed in
- internal static XElement ToDeepXml(this IMedia media, IPackagingService packagingService)
- {
- return packagingService.Export(media, true, raiseEvents: false);
- }
-
-
- ///
- /// Creates the xml representation for the object
- ///
- /// to generate xml for
- ///
- /// Boolean indicating whether the xml should be generated for preview
- /// Xml representation of the passed in
- public static XElement ToXml(this IContent content, IPackagingService packagingService, bool isPreview)
- {
- //TODO Do a proper implementation of this
- //If current IContent is published we should get latest unpublished version
- return content.ToXml(packagingService);
- }
-
-
///
/// Creates the xml representation for the object
///
/// to generate xml for
- ///
+ ///
/// Xml representation of the passed in
- public static XElement ToXml(this IMember member, IPackagingService packagingService)
+ public static XElement ToXml(this IMember member, IEntityXmlSerializer serializer)
{
- return ((PackagingService)(packagingService)).Export(member);
+ return serializer.Serialize(member);
}
#endregion
diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs
index 35304e3fde..92589ab6cf 100644
--- a/src/Umbraco.Core/Deploy/IValueConnector.cs
+++ b/src/Umbraco.Core/Deploy/IValueConnector.cs
@@ -17,19 +17,19 @@ namespace Umbraco.Core.Deploy
IEnumerable PropertyEditorAliases { get; }
///
- /// Gets the deploy property corresponding to a content property.
+ /// Gets the deploy property value corresponding to a content property value, and gather dependencies.
///
- /// The content property.
+ /// The content property value.
/// The content dependencies.
/// The deploy property value.
- string GetValue(Property property, ICollection dependencies);
+ string ToArtifact(object value, ICollection dependencies);
///
- /// Sets a content property value using a deploy property.
+ /// Gets the content property value corresponding to a deploy property value.
///
- /// The content item.
- /// The property alias.
/// The deploy property value.
- void SetValue(IContentBase content, string alias, string value);
+ /// The current content property value.
+ /// The content property value.
+ object FromArtifact(string value, object currentValue);
}
}
diff --git a/src/Umbraco.Core/Events/ExportEventArgs.cs b/src/Umbraco.Core/Events/ExportEventArgs.cs
deleted file mode 100644
index f46cccf05c..0000000000
--- a/src/Umbraco.Core/Events/ExportEventArgs.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Xml.Linq;
-
-namespace Umbraco.Core.Events
-{
- public class ExportEventArgs : CancellableObjectEventArgs>, IEquatable>
- {
- ///
- /// Constructor accepting a single entity instance
- ///
- ///
- ///
- ///
- public ExportEventArgs(TEntity eventObject, XElement xml, bool canCancel)
- : base(new List { eventObject }, canCancel)
- {
- Xml = xml;
- }
-
- ///
- /// Constructor accepting a single entity instance
- /// and cancellable by default
- ///
- ///
- ///
- public ExportEventArgs(TEntity eventObject, string elementName) : base(new List {eventObject}, true)
- {
- Xml = new XElement(elementName);
- }
-
- protected ExportEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel)
- {
- }
-
- protected ExportEventArgs(IEnumerable eventObject) : base(eventObject)
- {
- }
-
- ///
- /// Returns all entities that were exported during the operation
- ///
- public IEnumerable ExportedEntities
- {
- get { return EventObject; }
- }
-
- ///
- /// Returns the xml relating to the export event
- ///
- public XElement Xml { get; private set; }
-
- public bool Equals(ExportEventArgs other)
- {
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
- return base.Equals(other) && Equals(Xml, other.Xml);
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((ExportEventArgs) obj);
- }
-
- public override int GetHashCode()
- {
- unchecked
- {
- return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0);
- }
- }
-
- public static bool operator ==(ExportEventArgs left, ExportEventArgs right)
- {
- return Equals(left, right);
- }
-
- public static bool operator !=(ExportEventArgs left, ExportEventArgs right)
- {
- return !Equals(left, right);
- }
- }
-}
diff --git a/src/Umbraco.Core/Events/ImportEventArgs.cs b/src/Umbraco.Core/Events/ImportEventArgs.cs
deleted file mode 100644
index fb8f7d4936..0000000000
--- a/src/Umbraco.Core/Events/ImportEventArgs.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Xml.Linq;
-
-namespace Umbraco.Core.Events
-{
- public class ImportEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>
- {
- ///
- /// Constructor accepting an XElement with the xml being imported
- ///
- ///
- public ImportEventArgs(XElement xml) : base(new List(), true)
- {
- Xml = xml;
- }
-
- ///
- /// Constructor accepting a list of entities and an XElement with the imported xml
- ///
- ///
- ///
- ///
- public ImportEventArgs(IEnumerable eventObject, XElement xml, bool canCancel)
- : base(eventObject, canCancel)
- {
- Xml = xml;
- }
-
- protected ImportEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel)
- {
- }
-
- protected ImportEventArgs(IEnumerable eventObject) : base(eventObject)
- {
- }
-
- ///
- /// Returns all entities that were imported during the operation
- ///
- public IEnumerable ImportedEntities
- {
- get { return EventObject; }
- }
-
- ///
- /// Returns the xml relating to the import event
- ///
- public XElement Xml { get; private set; }
-
- public bool Equals(ImportEventArgs other)
- {
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
- return base.Equals(other) && Equals(Xml, other.Xml);
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((ImportEventArgs) obj);
- }
-
- public override int GetHashCode()
- {
- unchecked
- {
- return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0);
- }
- }
-
- public static bool operator ==(ImportEventArgs left, ImportEventArgs right)
- {
- return Equals(left, right);
- }
-
- public static bool operator !=(ImportEventArgs left, ImportEventArgs right)
- {
- return !Equals(left, right);
- }
- }
-}
diff --git a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
index 2e56757a25..61369af59d 100644
--- a/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
+++ b/src/Umbraco.Core/Events/ImportPackageEventArgs.cs
@@ -7,44 +7,28 @@ namespace Umbraco.Core.Events
{
public class ImportPackageEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>
{
- private readonly MetaData _packageMetaData;
-
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Use the overload specifying packageMetaData instead")]
- public ImportPackageEventArgs(TEntity eventObject, bool canCancel)
+ public ImportPackageEventArgs(TEntity eventObject, IPackageInfo packageMetaData, bool canCancel)
: base(new[] { eventObject }, canCancel)
{
+ PackageMetaData = packageMetaData ?? throw new ArgumentNullException(nameof(packageMetaData));
}
- public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData, bool canCancel)
- : base(new[] { eventObject }, canCancel)
- {
- if (packageMetaData == null) throw new ArgumentNullException("packageMetaData");
- _packageMetaData = packageMetaData;
- }
-
- public ImportPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
+ public ImportPackageEventArgs(TEntity eventObject, IPackageInfo packageMetaData)
: this(eventObject, packageMetaData, true)
{
}
- public MetaData PackageMetaData
- {
- get { return _packageMetaData; }
- }
+ public IPackageInfo PackageMetaData { get; }
- public IEnumerable InstallationSummary
- {
- get { return EventObject; }
- }
+ public IEnumerable InstallationSummary => EventObject;
public bool Equals(ImportPackageEventArgs other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
//TODO: MetaData for package metadata has no equality operators :/
- return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData);
+ return base.Equals(other) && PackageMetaData.Equals(other.PackageMetaData);
}
public override bool Equals(object obj)
@@ -59,7 +43,7 @@ namespace Umbraco.Core.Events
{
unchecked
{
- return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode();
+ return (base.GetHashCode() * 397) ^ PackageMetaData.GetHashCode();
}
}
diff --git a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs
index 13c260bf3e..63c5ceaba0 100644
--- a/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs
+++ b/src/Umbraco.Core/Events/UninstallPackageEventArgs.cs
@@ -3,20 +3,13 @@ using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Events
{
- public class UninstallPackageEventArgs : CancellableObjectEventArgs>
+ public class UninstallPackageEventArgs: CancellableObjectEventArgs>
{
- public UninstallPackageEventArgs(TEntity eventObject, bool canCancel)
- : base(new[] { eventObject }, canCancel)
- { }
-
- public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
- : base(new[] { eventObject })
+ public UninstallPackageEventArgs(IEnumerable eventObject, bool canCancel)
+ : base(eventObject, canCancel)
{
- PackageMetaData = packageMetaData;
}
- public MetaData PackageMetaData { get; }
-
- public IEnumerable UninstallationSummary => EventObject;
+ public IEnumerable UninstallationSummary => EventObject;
}
}
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index 7773f378a5..76e7631482 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -31,16 +31,6 @@ namespace Umbraco.Core.IO
public static char DirSepChar => Path.DirectorySeparatorChar;
- internal static void UnZip(string zipFilePath, string unPackDirectory, bool deleteZipFile)
- {
- // Unzip
- var tempDir = unPackDirectory;
- Directory.CreateDirectory(tempDir);
- ZipFile.ExtractToDirectory(zipFilePath, unPackDirectory);
- if (deleteZipFile)
- File.Delete(zipFilePath);
- }
-
//helper to try and match the old path to a new virtual one
public static string FindFile(string virtualPath)
{
@@ -123,11 +113,6 @@ namespace Umbraco.Core.IO
return MapPath(path, true);
}
- public static string MapPathIfVirtual(string path)
- {
- return path.StartsWith("~/") ? MapPath(path) : path;
- }
-
//use a tilde character instead of the complete path
internal static string ReturnPath(string settingsKey, string standardPath, bool useTilde)
{
@@ -156,20 +141,6 @@ namespace Umbraco.Core.IO
return VerifyEditPath(filePath, new[] { validDir });
}
- ///
- /// Validates that the current filepath matches a directory where the user is allowed to edit a file.
- ///
- /// The filepath to validate.
- /// The valid directory.
- /// True, if the filepath is valid, else an exception is thrown.
- /// The filepath is invalid.
- internal static bool ValidateEditPath(string filePath, string validDir)
- {
- if (VerifyEditPath(filePath, validDir) == false)
- throw new FileSecurityException(String.Format("The filepath '{0}' is not within an allowed directory for this type of files", filePath.Replace(MapPath(SystemDirectories.Root), "")));
- return true;
- }
-
///
/// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file.
///
@@ -221,20 +192,6 @@ namespace Umbraco.Core.IO
return ext != null && validFileExtensions.Contains(ext.TrimStart('.'));
}
- ///
- /// Validates that the current filepath has one of several authorized extensions.
- ///
- /// The filepath to validate.
- /// The valid extensions.
- /// True, if the filepath is valid, else an exception is thrown.
- /// The filepath is invalid.
- internal static bool ValidateFileExtension(string filePath, List validFileExtensions)
- {
- if (VerifyFileExtension(filePath, validFileExtensions) == false)
- throw new FileSecurityException(String.Format("The extension for the current file '{0}' is not of an allowed type for this editor. This is typically controlled from either the installed MacroEngines or based on configuration in /config/umbracoSettings.config", filePath.Replace(MapPath(SystemDirectories.Root), "")));
- return true;
- }
-
public static bool PathStartsWith(string path, string root, char separator)
{
// either it is identical to root,
@@ -329,17 +286,6 @@ namespace Umbraco.Core.IO
Directory.CreateDirectory(absolutePath);
}
- public static void EnsureFileExists(string path, string contents)
- {
- var absolutePath = IOHelper.MapPath(path);
- if (File.Exists(absolutePath)) return;
-
- using (var writer = File.CreateText(absolutePath))
- {
- writer.Write(contents);
- }
- }
-
///
/// Checks if a given path is a full path including drive letter
///
diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs
index 6493238391..d71f328713 100644
--- a/src/Umbraco.Core/IO/ShadowWrapper.cs
+++ b/src/Umbraco.Core/IO/ShadowWrapper.cs
@@ -7,7 +7,7 @@ namespace Umbraco.Core.IO
{
internal class ShadowWrapper : IFileSystem
{
- private const string ShadowFsPath = "~/App_Data/TEMP/ShadowFs";
+ private static readonly string ShadowFsPath = SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs";
private readonly Func _isScoped;
private readonly IFileSystem _innerFileSystem;
diff --git a/src/Umbraco.Core/IO/SupportingFileSystems.cs b/src/Umbraco.Core/IO/SupportingFileSystems.cs
new file mode 100644
index 0000000000..43ac2ba85a
--- /dev/null
+++ b/src/Umbraco.Core/IO/SupportingFileSystems.cs
@@ -0,0 +1,11 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.IO
+{
+ public class SupportingFileSystems : TargetedServiceFactory
+ {
+ public SupportingFileSystems(IFactory factory)
+ : base(factory)
+ { }
+ }
+}
diff --git a/src/Umbraco.Core/IO/SystemDirectories.cs b/src/Umbraco.Core/IO/SystemDirectories.cs
index 183d48e3d9..4ea3ed64d5 100644
--- a/src/Umbraco.Core/IO/SystemDirectories.cs
+++ b/src/Umbraco.Core/IO/SystemDirectories.cs
@@ -12,6 +12,10 @@ namespace Umbraco.Core.IO
public static string Data => "~/App_Data";
+ public static string TempData => Data + "/TEMP";
+
+ public static string TempFileUploads => TempData + "/FileUploads";
+
public static string Install => "~/install";
//fixme: remove this
@@ -43,9 +47,9 @@ namespace Umbraco.Core.IO
[Obsolete("Only used by legacy load balancing which is obsolete and should be removed")]
public static string WebServices => IOHelper.ReturnPath("umbracoWebservicesPath", Umbraco.EnsureEndsWith("/") + "webservices");
- public static string Packages => Data + IOHelper.DirSepChar + "packages";
+ public static string Packages => Data + "/packages";
- public static string Preview => Data + IOHelper.DirSepChar + "preview";
+ public static string Preview => Data + "/preview";
private static string _root;
diff --git a/src/Umbraco.Core/IRuntimeState.cs b/src/Umbraco.Core/IRuntimeState.cs
index 5fcfab1518..30c768ab01 100644
--- a/src/Umbraco.Core/IRuntimeState.cs
+++ b/src/Umbraco.Core/IRuntimeState.cs
@@ -57,6 +57,11 @@ namespace Umbraco.Core
///
RuntimeLevel Level { get; }
+ ///
+ /// Gets the reason for the runtime level of execution.
+ ///
+ RuntimeLevelReason Reason { get; }
+
///
/// Gets the current migration state.
///
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
index 5525cc4a50..f9f3e5da30 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Migrations.Install
///
/// Creates the initial database schema during install.
///
- internal class DatabaseSchemaCreator
+ public class DatabaseSchemaCreator
{
private readonly IUmbracoDatabase _database;
private readonly ILogger _logger;
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Migrations.Install
private ISqlSyntaxProvider SqlSyntax => _database.SqlContext.SqlSyntax;
// all tables, in order
- public static readonly List OrderedTables = new List
+ internal static readonly List OrderedTables = new List
{
typeof (UserDto),
typeof (NodeDto),
@@ -138,7 +138,7 @@ namespace Umbraco.Core.Migrations.Install
///
/// Validates the schema of the current database.
///
- public DatabaseSchemaResult ValidateSchema()
+ internal DatabaseSchemaResult ValidateSchema()
{
var result = new DatabaseSchemaResult(SqlSyntax);
@@ -387,7 +387,7 @@ namespace Umbraco.Core.Migrations.Install
/// If has been decorated with an , the name from that
/// attribute will be used for the table name. If the attribute is not present, the name
/// will be used instead.
- ///
+ ///
/// If a table with the same name already exists, the parameter will determine
/// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
/// not do anything if the parameter is false.
@@ -409,14 +409,14 @@ namespace Umbraco.Core.Migrations.Install
/// If has been decorated with an , the name from
/// that attribute will be used for the table name. If the attribute is not present, the name
/// will be used instead.
- ///
+ ///
/// If a table with the same name already exists, the parameter will determine
/// whether the table is overwritten. If true, the table will be overwritten, whereas this method will
/// not do anything if the parameter is false.
///
/// This need to execute as part of a transaction.
///
- public void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
+ internal void CreateTable(bool overwrite, Type modelType, DatabaseDataCreator dataCreation)
{
if (!_database.InTransaction)
throw new InvalidOperationException("Database is not in a transaction.");
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index b469c02a3c..51935e6517 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -121,6 +121,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version
To("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}");
To("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}");
+ To("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}");
+
//FINAL
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs
new file mode 100644
index 0000000000..1df11a3e99
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentTypeIsElementColumn.cs
@@ -0,0 +1,15 @@
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
+{
+ public class AddContentTypeIsElementColumn : MigrationBase
+ {
+ public AddContentTypeIsElementColumn(IMigrationContext context) : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ AddColumn("isElement");
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index 88b1179f6d..b6ea9f50a0 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -26,6 +26,7 @@ namespace Umbraco.Core.Models
private string _thumbnail = "folder.png";
private bool _allowedAsRoot; // note: only one that's not 'pure element type'
private bool _isContainer;
+ private bool _isElement;
private PropertyGroupCollection _propertyGroups;
private PropertyTypeCollection _noGroupPropertyTypes;
private IEnumerable _allowedContentTypes;
@@ -90,6 +91,7 @@ namespace Umbraco.Core.Models
public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo(x => x.Icon);
public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo(x => x.Thumbnail);
public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot);
+ public readonly PropertyInfo IsElementSelector = ExpressionHelper.GetPropertyInfo(x => x.IsElement);
public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer);
public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes);
public readonly PropertyInfo PropertyGroupsSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups);
@@ -180,6 +182,14 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector);
}
+ ///
+ [DataMember]
+ public bool IsElement
+ {
+ get => _isElement;
+ set => SetPropertyValueAndDetectChanges(value, ref _isElement, Ps.Value.IsElementSelector);
+ }
+
///
/// Gets or sets a list of integer Ids for allowed ContentTypes
///
diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
index 8af48bb881..adbc3de54f 100644
--- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs
@@ -15,7 +15,8 @@ namespace Umbraco.Core.Models
{
var type = contentType.GetType();
var itemType = PublishedItemType.Unknown;
- if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content;
+ if (contentType.IsElement) itemType = PublishedItemType.Element;
+ else if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content;
else if (typeof(IMediaType).IsAssignableFrom(type)) itemType = PublishedItemType.Media;
else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member;
return itemType;
diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs
index a1d4aee02f..787e347b37 100644
--- a/src/Umbraco.Core/Models/IContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/IContentTypeBase.cs
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Models
/// the icon (eg. icon-home) along with an optional CSS class name representing the
/// color (eg. icon-blue). Put together, the value for this scenario would be
/// icon-home color-blue.
- ///
+ ///
/// If a class name for the color isn't specified, the icon color will default to black.
///
string Icon { get; set; }
@@ -48,6 +48,16 @@ namespace Umbraco.Core.Models
///
bool IsContainer { get; set; }
+ ///
+ /// Gets or sets a value indicating whether this content type is for an element.
+ ///
+ ///
+ /// By default a content type is for a true media, member or document, but
+ /// it can also be for an element, ie a subset that can for instance be used in
+ /// nested content.
+ ///
+ bool IsElement { get; set; }
+
///
/// Gets or sets the content variation of the content type.
///
diff --git a/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs b/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs
new file mode 100644
index 0000000000..0023d4dbed
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/ActionRunAt.cs
@@ -0,0 +1,9 @@
+namespace Umbraco.Core.Models.Packaging
+{
+ public enum ActionRunAt
+ {
+ Undefined = 0,
+ Install,
+ Uninstall
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs
new file mode 100644
index 0000000000..a852fcc997
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ ///
+ /// The model of the package definition within an umbraco (zip) package file
+ ///
+ public class CompiledPackage : IPackageInfo
+ {
+ public FileInfo PackageFile { get; set; }
+
+ public string Name { get; set; }
+ public string Version { get; set; }
+ public string Url { get; set; }
+ public string License { get; set; }
+ public string LicenseUrl { get; set; }
+ public Version UmbracoVersion { get; set; }
+ public RequirementsType UmbracoVersionRequirementsType { get; set; }
+ public string Author { get; set; }
+ public string AuthorUrl { get; set; }
+ public string Readme { get; set; }
+ public string Control { get; set; }
+ public string IconUrl { get; set; }
+
+ public string Actions { get; set; } //fixme: Should we make this strongly typed to IEnumerable ?
+
+ public PreInstallWarnings Warnings { get; set; } = new PreInstallWarnings();
+
+ public List Files { get; set; } = new List();
+
+ public IEnumerable Macros { get; set; } //fixme: make strongly typed
+ public IEnumerable Templates { get; set; } //fixme: make strongly typed
+ public IEnumerable Stylesheets { get; set; } //fixme: make strongly typed
+ public IEnumerable DataTypes { get; set; } //fixme: make strongly typed
+ public IEnumerable Languages { get; set; } //fixme: make strongly typed
+ public IEnumerable DictionaryItems { get; set; } //fixme: make strongly typed
+ public IEnumerable DocumentTypes { get; set; } //fixme: make strongly typed
+ public IEnumerable Documents { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs
new file mode 100644
index 0000000000..c41966dfe1
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageDocument.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Xml.Linq;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ public class CompiledPackageDocument
+ {
+ public static CompiledPackageDocument Create(XElement xml)
+ {
+ if (xml.Name.LocalName != "DocumentSet")
+ throw new ArgumentException("The xml isn't formatted correctly, a document element is defined by ", nameof(xml));
+ return new CompiledPackageDocument
+ {
+ XmlData = xml,
+ ImportMode = xml.AttributeValue("importMode")
+ };
+ }
+
+ public string ImportMode { get; set; } //this is never used
+
+ ///
+ /// The serialized version of the content
+ ///
+ public XElement XmlData { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs
new file mode 100644
index 0000000000..2cb989b42b
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageFile.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Xml.Linq;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ public class CompiledPackageFile
+ {
+ public static CompiledPackageFile Create(XElement xml)
+ {
+ if (xml.Name.LocalName != "file")
+ throw new ArgumentException("The xml isn't formatted correctly, a file element is defined by ", nameof(xml));
+ return new CompiledPackageFile
+ {
+ UniqueFileName = xml.Element("guid")?.Value,
+ OriginalName = xml.Element("orgName")?.Value,
+ OriginalPath = xml.Element("orgPath")?.Value
+ };
+ }
+
+ public string OriginalPath { get; set; }
+ public string UniqueFileName { get; set; }
+ public string OriginalName { get; set; }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs
new file mode 100644
index 0000000000..8722ee7811
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/IPackageInfo.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ public interface IPackageInfo
+ {
+ string Name { get; }
+ string Version { get; }
+ string Url { get; }
+ string License { get; }
+ string LicenseUrl { get; }
+ Version UmbracoVersion { get; }
+ string Author { get; }
+ string AuthorUrl { get; }
+ string Readme { get; }
+ string Control { get; } //fixme - this needs to be an angular view
+ string IconUrl { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs
index 3eb397d728..1cab17e220 100644
--- a/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs
+++ b/src/Umbraco.Core/Models/Packaging/InstallationSummary.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Packaging
@@ -8,36 +9,19 @@ namespace Umbraco.Core.Models.Packaging
[DataContract(IsReference = true)]
public class InstallationSummary
{
- public MetaData MetaData { get; set; }
- public IEnumerable DataTypesInstalled { get; set; }
- public IEnumerable LanguagesInstalled { get; set; }
- public IEnumerable DictionaryItemsInstalled { get; set; }
- public IEnumerable MacrosInstalled { get; set; }
- public IEnumerable FilesInstalled { get; set; }
- public IEnumerable TemplatesInstalled { get; set; }
- public IEnumerable ContentTypesInstalled { get; set; }
- public IEnumerable StylesheetsInstalled { get; set; }
- public IEnumerable ContentInstalled { get; set; }
- public IEnumerable Actions { get; set; }
- public bool PackageInstalled { get; set; }
+ public IPackageInfo MetaData { get; set; }
+ public IEnumerable DataTypesInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable LanguagesInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable DictionaryItemsInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable MacrosInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable FilesInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable TemplatesInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable DocumentTypesInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable StylesheetsInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable Actions { get; set; } = Enumerable.Empty();
+ public IEnumerable ActionErrors { get; set; } = Enumerable.Empty();
+
}
- internal static class InstallationSummaryExtentions
- {
- public static InstallationSummary InitEmpty(this InstallationSummary summary)
- {
- summary.Actions = new List();
- summary.ContentInstalled = new List();
- summary.ContentTypesInstalled = new List();
- summary.DataTypesInstalled = new List();
- summary.DictionaryItemsInstalled = new List();
- summary.FilesInstalled = new List();
- summary.LanguagesInstalled = new List();
- summary.MacrosInstalled = new List();
- summary.MetaData = new MetaData();
- summary.TemplatesInstalled = new List();
- summary.PackageInstalled = false;
- return summary;
- }
- }
}
diff --git a/src/Umbraco.Core/Models/Packaging/MetaData.cs b/src/Umbraco.Core/Models/Packaging/MetaData.cs
deleted file mode 100644
index a1cb450c5b..0000000000
--- a/src/Umbraco.Core/Models/Packaging/MetaData.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Runtime.Serialization;
-
-namespace Umbraco.Core.Models.Packaging
-{
- [Serializable]
- [DataContract(IsReference = true)]
- public class MetaData
- {
- public string Name { get; set; }
- public string Version { get; set; }
- public string Url { get; set; }
- public string License { get; set; }
- public string LicenseUrl { get; set; }
- public int ReqMajor { get; set; }
- public int ReqMinor { get; set; }
- public int ReqPatch { get; set; }
- public string AuthorName { get; set; }
- public string AuthorUrl { get; set; }
- public string Readme { get; set; }
- public string Control { get; set; }
- }
-}
diff --git a/src/Umbraco.Core/Models/Packaging/PackageAction.cs b/src/Umbraco.Core/Models/Packaging/PackageAction.cs
index e941c5729a..ab7b120eae 100644
--- a/src/Umbraco.Core/Models/Packaging/PackageAction.cs
+++ b/src/Umbraco.Core/Models/Packaging/PackageAction.cs
@@ -4,13 +4,9 @@ using System.Xml.Linq;
namespace Umbraco.Core.Models.Packaging
{
- public enum ActionRunAt
- {
- Undefined = 0,
- Install,
- Uninstall
- }
-
+ ///
+ /// Defines a package action declared within a package manifest
+ ///
[Serializable]
[DataContract(IsReference = true)]
public class PackageAction
diff --git a/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs
new file mode 100644
index 0000000000..c068c57b08
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/PackageDefinition.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ [DataContract(Name = "packageInstance")]
+ public class PackageDefinition : IPackageInfo
+ {
+ ///
+ /// Converts a model to a model
+ ///
+ ///
+ ///
+ ///
+ /// This is used only for conversions and will not 'get' a PackageDefinition from the repository with a valid ID
+ ///
+ internal static PackageDefinition FromCompiledPackage(CompiledPackage compiled)
+ {
+ return new PackageDefinition
+ {
+ Actions = compiled.Actions,
+ Author = compiled.Author,
+ AuthorUrl = compiled.AuthorUrl,
+ Control = compiled.Control,
+ IconUrl = compiled.IconUrl,
+ License = compiled.License,
+ LicenseUrl = compiled.LicenseUrl,
+ Name = compiled.Name,
+ Readme = compiled.Readme,
+ UmbracoVersion = compiled.UmbracoVersion,
+ Url = compiled.Url,
+ Version = compiled.Version,
+ Files = compiled.Files.Select(x => x.OriginalPath).ToList()
+ };
+ }
+
+ [DataMember(Name = "id")]
+ public int Id { get; set; }
+
+ [DataMember(Name = "packageGuid")]
+ public Guid PackageId { get; set; }
+
+ [DataMember(Name = "name")]
+ [Required]
+ public string Name { get; set; } = string.Empty;
+
+ [DataMember(Name = "url")]
+ [Required]
+ [Url]
+ public string Url { get; set; } = string.Empty;
+
+ ///
+ /// The full path to the package's zip file when it was installed (or is being installed)
+ ///
+ [ReadOnly(true)]
+ [DataMember(Name = "packagePath")]
+ public string PackagePath { get; set; } = string.Empty;
+
+ [DataMember(Name = "version")]
+ [Required]
+ public string Version { get; set; } = "1.0.0";
+
+ ///
+ /// The minimum umbraco version that this package requires
+ ///
+ [DataMember(Name = "umbracoVersion")]
+ public Version UmbracoVersion { get; set; } = Configuration.UmbracoVersion.Current;
+
+ [DataMember(Name = "author")]
+ [Required]
+ public string Author { get; set; } = string.Empty;
+
+ [DataMember(Name = "authorUrl")]
+ [Required]
+ [Url]
+ public string AuthorUrl { get; set; } = string.Empty;
+
+ [DataMember(Name = "license")]
+ public string License { get; set; } = "MIT License";
+
+ [DataMember(Name = "licenseUrl")]
+ public string LicenseUrl { get; set; } = "http://opensource.org/licenses/MIT";
+
+ [DataMember(Name = "readme")]
+ public string Readme { get; set; } = string.Empty;
+
+ [DataMember(Name = "contentLoadChildNodes")]
+ public bool ContentLoadChildNodes { get; set; }
+
+ [DataMember(Name = "contentNodeId")]
+ public string ContentNodeId { get; set; } = string.Empty;
+
+ [DataMember(Name = "macros")]
+ public IList Macros { get; set; } = new List();
+
+ [DataMember(Name = "languages")]
+ public IList Languages { get; set; } = new List();
+
+ [DataMember(Name = "dictionaryItems")]
+ public IList DictionaryItems { get; set; } = new List();
+
+ [DataMember(Name = "templates")]
+ public IList Templates { get; set; } = new List();
+
+ [DataMember(Name = "documentTypes")]
+ public IList DocumentTypes { get; set; } = new List();
+
+ [DataMember(Name = "stylesheets")]
+ public IList Stylesheets { get; set; } = new List();
+
+ [DataMember(Name = "files")]
+ public IList Files { get; set; } = new List();
+
+ //fixme: Change this to angular view
+ [DataMember(Name = "loadControl")]
+ public string Control { get; set; } = string.Empty;
+
+ [DataMember(Name = "actions")]
+ public string Actions { get; set; } = "";
+
+ [DataMember(Name = "dataTypes")]
+ public IList DataTypes { get; set; } = new List();
+
+ [DataMember(Name = "iconUrl")]
+ public string IconUrl { get; set; } = string.Empty;
+
+ public PackageDefinition Clone()
+ {
+ return new PackageDefinition
+ {
+ Id = Id,
+ PackagePath = PackagePath,
+ Name = Name,
+ Files = new List(Files),
+ UmbracoVersion = (Version) UmbracoVersion.Clone(),
+ Version = Version,
+ Url = Url,
+ Readme = Readme,
+ AuthorUrl = AuthorUrl,
+ Author = Author,
+ LicenseUrl = LicenseUrl,
+ Actions = Actions,
+ PackageId = PackageId,
+ Control = Control,
+ DataTypes = new List(DataTypes),
+ IconUrl = IconUrl,
+ License = License,
+ Templates = new List(Templates),
+ Languages = new List(Languages),
+ Macros = new List(Macros),
+ Stylesheets = new List(Stylesheets),
+ DocumentTypes = new List(DocumentTypes),
+ DictionaryItems = new List(DictionaryItems),
+ ContentNodeId = ContentNodeId,
+ ContentLoadChildNodes = ContentLoadChildNodes
+ };
+ }
+
+ }
+
+}
diff --git a/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs
index 5850e2321c..f0acb2a46b 100644
--- a/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs
+++ b/src/Umbraco.Core/Models/Packaging/PreInstallWarnings.cs
@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Packaging
{
- [Serializable]
- [DataContract(IsReference = true)]
- internal class PreInstallWarnings
+ public class PreInstallWarnings
{
- public KeyValuePair[] UnsecureFiles { get; set; }
- public KeyValuePair[] FilesReplaced { get; set; }
- public IEnumerable ConflictingMacroAliases { get; set; }
- public IEnumerable ConflictingTemplateAliases { get; set; }
- public IEnumerable ConflictingStylesheetNames { get; set; }
+ public IEnumerable UnsecureFiles { get; set; } = Enumerable.Empty();
+ public IEnumerable FilesReplaced { get; set; } = Enumerable.Empty();
+
+ //TODO: Shouldn't we detect other conflicting entities too ?
+ public IEnumerable ConflictingMacros { get; set; } = Enumerable.Empty();
+ public IEnumerable ConflictingTemplates { get; set; } = Enumerable.Empty();
+ public IEnumerable ConflictingStylesheets { get; set; } = Enumerable.Empty();
}
}
diff --git a/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs b/src/Umbraco.Core/Models/Packaging/RequirementsType.cs
similarity index 65%
rename from src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs
rename to src/Umbraco.Core/Models/Packaging/RequirementsType.cs
index ca91626128..2b6894ed0f 100644
--- a/src/Umbraco.Web/_Legacy/Packager/RequirementsType.cs
+++ b/src/Umbraco.Core/Models/Packaging/RequirementsType.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Web._Legacy.Packager
+namespace Umbraco.Core.Models.Packaging
{
public enum RequirementsType
{
diff --git a/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs b/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs
new file mode 100644
index 0000000000..fd39954f6b
--- /dev/null
+++ b/src/Umbraco.Core/Models/Packaging/UninstallationSummary.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+
+namespace Umbraco.Core.Models.Packaging
+{
+ [Serializable]
+ [DataContract(IsReference = true)]
+ public class UninstallationSummary
+ {
+ public IPackageInfo MetaData { get; set; }
+ public IEnumerable DataTypesUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable LanguagesUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable DictionaryItemsUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable MacrosUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable FilesUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable TemplatesUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable DocumentTypesUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable StylesheetsUninstalled { get; set; } = Enumerable.Empty();
+ public IEnumerable Actions { get; set; } = Enumerable.Empty();
+ public IEnumerable ActionErrors { get; set; } = Enumerable.Empty();
+ }
+}
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs
index e55fe66945..42e9c9538d 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs
@@ -4,13 +4,18 @@
/// The type of published element.
///
/// Can be a simple element, or a document, a media, a member.
- public enum PublishedItemType // fixme - need to rename to PublishedElementType but then conflicts?
+ public enum PublishedItemType
{
///
/// Unknown.
///
Unknown = 0,
+ ///
+ /// An element.
+ ///
+ Element,
+
///
/// A document.
///
diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs
new file mode 100644
index 0000000000..0d533cfbc2
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using Umbraco.Core.IO;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Packaging;
+using File = System.IO.File;
+
+namespace Umbraco.Core.Packaging
+{
+ ///
+ /// Parses the xml document contained in a compiled (zip) Umbraco package
+ ///
+ internal class CompiledPackageXmlParser
+ {
+ private readonly ConflictingPackageData _conflictingPackageData;
+
+ public CompiledPackageXmlParser(ConflictingPackageData conflictingPackageData)
+ {
+ _conflictingPackageData = conflictingPackageData;
+ }
+
+ public CompiledPackage ToCompiledPackage(XDocument xml, FileInfo packageFile, string applicationRootFolder)
+ {
+ if (xml == null) throw new ArgumentNullException(nameof(xml));
+ if (xml.Root == null) throw new ArgumentException(nameof(xml), "The xml document is invalid");
+ if (xml.Root.Name != "umbPackage") throw new FormatException("The xml document is invalid");
+
+ var info = xml.Root.Element("info");
+ if (info == null) throw new FormatException("The xml document is invalid");
+ var package = info.Element("package");
+ if (package == null) throw new FormatException("The xml document is invalid");
+ var author = info.Element("author");
+ if (author == null) throw new FormatException("The xml document is invalid");
+ var requirements = package.Element("requirements");
+ if (requirements == null) throw new FormatException("The xml document is invalid");
+
+ var def = new CompiledPackage
+ {
+ PackageFile = packageFile,
+ Name = package.Element("name")?.Value,
+ Author = author.Element("name")?.Value,
+ AuthorUrl = author.Element("website")?.Value,
+ Version = package.Element("version")?.Value,
+ Readme = info.Element("readme")?.Value,
+ License = package.Element("license")?.Value,
+ LicenseUrl = package.Element("license")?.AttributeValue("url"),
+ Url = package.Element("url")?.Value,
+ IconUrl = package.Element("iconUrl")?.Value,
+ UmbracoVersion = new Version((int)requirements.Element("major"), (int)requirements.Element("minor"), (int)requirements.Element("patch")),
+ UmbracoVersionRequirementsType = requirements.AttributeValue("type").IsNullOrWhiteSpace() ? RequirementsType.Legacy : Enum.Parse(requirements.AttributeValue("type"), true),
+ Control = package.Element("control")?.Value,
+ Actions = xml.Root.Element("Actions")?.ToString(SaveOptions.None) ?? "", //take the entire outer xml value
+ Files = xml.Root.Element("files")?.Elements("file")?.Select(CompiledPackageFile.Create).ToList() ?? new List(),
+ Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty(),
+ Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty(),
+ Stylesheets = xml.Root.Element("Stylesheets")?.Elements("styleSheet") ?? Enumerable.Empty(),
+ DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty(),
+ Languages = xml.Root.Element("Languages")?.Elements("Language") ?? Enumerable.Empty(),
+ DictionaryItems = xml.Root.Element("DictionaryItems")?.Elements("DictionaryItem") ?? Enumerable.Empty(),
+ DocumentTypes = xml.Root.Element("DocumentTypes")?.Elements("DocumentType") ?? Enumerable.Empty(),
+ Documents = xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageDocument.Create) ?? Enumerable.Empty(),
+ };
+
+ def.Warnings = GetPreInstallWarnings(def, applicationRootFolder);
+
+ return def;
+ }
+
+ private PreInstallWarnings GetPreInstallWarnings(CompiledPackage package, string applicationRootFolder)
+ {
+ var sourceDestination = ExtractSourceDestinationFileInformation(package.Files);
+
+ var installWarnings = new PreInstallWarnings
+ {
+ ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros),
+ ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates),
+ ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets),
+ UnsecureFiles = FindUnsecureFiles(sourceDestination),
+ FilesReplaced = FindFilesToBeReplaced(sourceDestination, applicationRootFolder)
+ };
+
+ return installWarnings;
+ }
+
+ ///
+ /// Returns a tuple of the zip file's unique file name and it's application relative path
+ ///
+ ///
+ ///
+ public (string packageUniqueFile, string appRelativePath)[] ExtractSourceDestinationFileInformation(IEnumerable packageFiles)
+ {
+ return packageFiles
+ .Select(e =>
+ {
+ var fileName = PrepareAsFilePathElement(e.OriginalName);
+ var relativeDir = UpdatePathPlaceholders(PrepareAsFilePathElement(e.OriginalPath));
+ var relativePath = Path.Combine(relativeDir, fileName);
+ return (e.UniqueFileName, relativePath);
+ }).ToArray();
+ }
+
+ private IEnumerable FindFilesToBeReplaced(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestination, string applicationRootFolder)
+ {
+ return sourceDestination.Where(sd => File.Exists(Path.Combine(applicationRootFolder, sd.appRelativePath)))
+ .Select(x => x.appRelativePath)
+ .ToArray();
+ }
+
+ private IEnumerable FindUnsecureFiles(IEnumerable<(string packageUniqueFile, string appRelativePath)> sourceDestinationPair)
+ {
+ return sourceDestinationPair.Where(sd => IsFileDestinationUnsecure(sd.appRelativePath))
+ .Select(x => x.appRelativePath)
+ .ToList();
+ }
+
+ private bool IsFileDestinationUnsecure(string destination)
+ {
+ var unsecureDirNames = new[] { "bin", "app_code" };
+ if (unsecureDirNames.Any(ud => destination.StartsWith(ud, StringComparison.InvariantCultureIgnoreCase)))
+ return true;
+
+ string extension = Path.GetExtension(destination);
+ return extension != null && extension.Equals(".dll", StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ private static string PrepareAsFilePathElement(string pathElement)
+ {
+ return pathElement.TrimStart(new[] { '\\', '/', '~' }).Replace("/", "\\");
+ }
+
+ private static string UpdatePathPlaceholders(string path)
+ {
+ if (path.Contains("[$"))
+ {
+ //this is experimental and undocumented...
+ path = path.Replace("[$UMBRACO]", SystemDirectories.Umbraco);
+ path = path.Replace("[$CONFIG]", SystemDirectories.Config);
+ path = path.Replace("[$DATA]", SystemDirectories.Data);
+ }
+ return path;
+ }
+
+ ///
+ /// Parses the package actions stored in the package definition
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable GetPackageActions(XElement actionsElement, string packageName)
+ {
+ if (actionsElement == null) return Enumerable.Empty();
+
+ //invariant check ... because people can realy enter anything :/
+ if (!string.Equals("actions", actionsElement.Name.LocalName, StringComparison.InvariantCultureIgnoreCase))
+ throw new FormatException("Must be \"\" as root");
+
+ if (!actionsElement.HasElements) return Enumerable.Empty();
+
+ var actionElementName = actionsElement.Elements().First().Name.LocalName;
+
+ //invariant check ... because people can realy enter anything :/
+ if (!string.Equals("action", actionElementName, StringComparison.InvariantCultureIgnoreCase))
+ throw new FormatException("Must be \"
+ {
+ var aliasAttr = e.Attribute("alias") ?? e.Attribute("Alias"); //allow both ... because people can really enter anything :/
+ if (aliasAttr == null)
+ throw new ArgumentException("missing \"alias\" atribute in alias element", nameof(actionsElement));
+
+ var packageAction = new PackageAction
+ {
+ XmlData = e,
+ Alias = aliasAttr.Value,
+ PackageName = packageName,
+ };
+
+ var attr = e.Attribute("runat") ?? e.Attribute("Runat"); //allow both ... because people can really enter anything :/
+
+ if (attr != null && Enum.TryParse(attr.Value, true, out ActionRunAt runAt)) { packageAction.RunAt = runAt; }
+
+ attr = e.Attribute("undo") ?? e.Attribute("Undo"); //allow both ... because people can really enter anything :/
+
+ if (attr != null && bool.TryParse(attr.Value, out var undo)) { packageAction.Undo = undo; }
+
+ return packageAction;
+ }).ToArray();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs
index b0424067bf..82693677fb 100644
--- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs
+++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs
@@ -7,82 +7,53 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.Packaging
{
- internal class ConflictingPackageData : IConflictingPackageData
+ internal class ConflictingPackageData
{
private readonly IMacroService _macroService;
private readonly IFileService _fileService;
- public ConflictingPackageData(IMacroService macroService,
- IFileService fileService)
+ public ConflictingPackageData(IMacroService macroService, IFileService fileService)
{
- if (fileService != null) _fileService = fileService;
- else throw new ArgumentNullException("fileService");
- if (macroService != null) _macroService = macroService;
- else throw new ArgumentNullException("macroService");
+ _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
+ _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService));
}
- public IEnumerable FindConflictingStylesheets(XElement stylesheetNotes)
+ public IEnumerable FindConflictingStylesheets(IEnumerable stylesheetNodes)
{
- if (string.Equals(Constants.Packaging.StylesheetsNodeName, stylesheetNotes.Name.LocalName) == false)
- {
- throw new ArgumentException("the root element must be \"" + Constants.Packaging.StylesheetsNodeName + "\"", "stylesheetNotes");
- }
-
- return stylesheetNotes.Elements(Constants.Packaging.StylesheetNodeName)
+ return stylesheetNodes
.Select(n =>
{
- XElement xElement = n.Element(Constants.Packaging.NameNodeName);
+ var xElement = n.Element("Name") ?? n.Element("name"); ;
if (xElement == null)
- {
- throw new ArgumentException("Missing \"" + Constants.Packaging.NameNodeName + "\" element",
- "stylesheetNotes");
- }
+ throw new FormatException("Missing \"Name\" element");
return _fileService.GetStylesheetByName(xElement.Value) as IFile;
})
.Where(v => v != null);
}
- public IEnumerable FindConflictingTemplates(XElement templateNotes)
+ public IEnumerable FindConflictingTemplates(IEnumerable templateNodes)
{
- if (string.Equals(Constants.Packaging.TemplatesNodeName, templateNotes.Name.LocalName) == false)
- {
- throw new ArgumentException("Node must be a \"" + Constants.Packaging.TemplatesNodeName + "\" node",
- "templateNotes");
- }
-
- return templateNotes.Elements(Constants.Packaging.TemplateNodeName)
+ return templateNodes
.Select(n =>
{
- XElement xElement = n.Element(Constants.Packaging.AliasNodeNameCapital) ?? n.Element(Constants.Packaging.AliasNodeNameSmall);
+ var xElement = n.Element("Alias") ?? n.Element("alias");
if (xElement == null)
- {
- throw new ArgumentException("missing a \"" + Constants.Packaging.AliasNodeNameCapital + "\" element",
- "templateNotes");
- }
+ throw new FormatException("missing a \"Alias\" element");
return _fileService.GetTemplate(xElement.Value);
})
.Where(v => v != null);
}
- public IEnumerable FindConflictingMacros(XElement macroNodes)
+ public IEnumerable FindConflictingMacros(IEnumerable macroNodes)
{
- if (string.Equals(Constants.Packaging.MacrosNodeName, macroNodes.Name.LocalName) == false)
- {
- throw new ArgumentException("Node must be a \"" + Constants.Packaging.MacrosNodeName + "\" node",
- "macroNodes");
- }
-
- return macroNodes.Elements(Constants.Packaging.MacroNodeName)
+ return macroNodes
.Select(n =>
{
- XElement xElement = n.Element(Constants.Packaging.AliasNodeNameSmall) ?? n.Element(Constants.Packaging.AliasNodeNameCapital);
+ var xElement = n.Element("alias") ?? n.Element("Alias");
if (xElement == null)
- {
- throw new ArgumentException(string.Format("missing a \"{0}\" element in {0} element", Constants.Packaging.AliasNodeNameSmall),
- "macroNodes");
- }
+ throw new FormatException("missing a \"alias\" element in alias element");
return _macroService.GetByAlias(xElement.Value);
})
diff --git a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs b/src/Umbraco.Core/Packaging/IConflictingPackageData.cs
deleted file mode 100644
index 12f3e8f8a4..0000000000
--- a/src/Umbraco.Core/Packaging/IConflictingPackageData.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Collections.Generic;
-using System.Xml.Linq;
-using Umbraco.Core.Models;
-
-namespace Umbraco.Core.Packaging
-{
- internal interface IConflictingPackageData
- {
- IEnumerable FindConflictingStylesheets(XElement stylesheetNotes);
- IEnumerable FindConflictingTemplates(XElement templateNotes);
- IEnumerable FindConflictingMacros(XElement macroNodes);
- }
-}
diff --git a/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs
new file mode 100644
index 0000000000..42606da6ef
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs
@@ -0,0 +1,16 @@
+using Umbraco.Core.Models.Packaging;
+
+namespace Umbraco.Core.Packaging
+{
+ ///
+ /// Manages the storage of created package definitions
+ ///
+ public interface ICreatedPackagesRepository : IPackageDefinitionRepository
+ {
+ ///
+ /// Creates the package file and returns it's physical path
+ ///
+ ///
+ string ExportPackage(PackageDefinition definition);
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs b/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs
new file mode 100644
index 0000000000..61f611edc5
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/IInstalledPackagesRepository.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Umbraco.Core.Packaging
+{
+ ///
+ /// Manages the storage of installed package definitions
+ ///
+ public interface IInstalledPackagesRepository : IPackageDefinitionRepository
+ {
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/IPackageActionRunner.cs b/src/Umbraco.Core/Packaging/IPackageActionRunner.cs
new file mode 100644
index 0000000000..1f8c134364
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/IPackageActionRunner.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Xml.Linq;
+
+namespace Umbraco.Core.Packaging
+{
+ public interface IPackageActionRunner
+ {
+ ///
+ /// Runs the package action with the specified action alias.
+ ///
+ /// Name of the package.
+ /// The action alias.
+ /// The action XML.
+ ///
+ bool RunPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors);
+
+ ///
+ /// Undos the package action with the specified action alias.
+ ///
+ /// Name of the package.
+ /// The action alias.
+ /// The action XML.
+ ///
+ bool UndoPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors);
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs
new file mode 100644
index 0000000000..343a0a05ea
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models.Packaging;
+
+namespace Umbraco.Core.Packaging
+{
+ ///
+ /// Defines methods for persisting package definitions to storage
+ ///
+ public interface IPackageDefinitionRepository
+ {
+ IEnumerable GetAll();
+ PackageDefinition GetById(int id);
+ void Delete(int id);
+
+ ///
+ /// Persists a package definition to storage
+ ///
+ ///
+ /// true if creating/updating the package was successful, otherwise false
+ ///
+ bool SavePackage(PackageDefinition definition);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Packaging/IPackageExtraction.cs b/src/Umbraco.Core/Packaging/IPackageExtraction.cs
deleted file mode 100644
index 21a5198f55..0000000000
--- a/src/Umbraco.Core/Packaging/IPackageExtraction.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.Collections.Generic;
-
-namespace Umbraco.Core.Packaging
-{
- ///
- /// Used to access an umbraco package file
- /// Remeber that filenames must be unique
- /// use "FindDubletFileNames" for sanitycheck
- ///
- internal interface IPackageExtraction
- {
- ///
- /// Returns the content of the file with the given filename
- ///
- /// Full path to the umbraco package file
- /// filename of the file for wich to get the text content
- /// this is the relative directory for the location of the file in the package
- /// I dont know why umbraco packages contains directories in the first place??
- /// text content of the file
- string ReadTextFileFromArchive(string packageFilePath, string fileToRead, out string directoryInPackage);
-
- ///
- /// Copies a file from package to given destination
- ///
- /// Full path to the ubraco package file
- /// filename of the file to copy
- /// destination path (including destination filename)
- /// True a file was overwritten
- void CopyFileFromArchive(string packageFilePath, string fileInPackageName, string destinationfilePath);
-
- ///
- /// Copies a file from package to given destination
- ///
- /// Full path to the ubraco package file
- /// Key: Source file in package. Value: Destination path inclusive file name
- void CopyFilesFromArchive(string packageFilePath, IEnumerable> sourceDestination);
-
- ///
- /// Check if given list of files can be found in the package
- ///
- /// Full path to the umbraco package file
- /// a list of files you would like to find in the package
- /// a subset if any of the files in "expectedFiles" that could not be found in the package
- IEnumerable FindMissingFiles(string packageFilePath, IEnumerable expectedFiles);
-
-
- ///
- /// Sanitycheck - should return en empty collection if package is valid
- ///
- /// Full path to the umbraco package file
- /// list of files that are found more than ones (accross directories) in the package
- IEnumerable FindDubletFileNames(string packageFilePath);
-
- ///
- /// Reads the given files from archive and returns them as a collection of byte arrays
- ///
- ///
- ///
- ///
- IEnumerable ReadFilesFromArchive(string packageFilePath, IEnumerable filesToGet);
- }
-}
diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs
index 1d0d46355c..c85a4ccd5f 100644
--- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs
+++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs
@@ -1,13 +1,43 @@
-using System.Xml.Linq;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml.Linq;
using Umbraco.Core.Models.Packaging;
namespace Umbraco.Core.Packaging
{
- internal interface IPackageInstallation
+ public interface IPackageInstallation
{
- InstallationSummary InstallPackage(string packageFilePath, int userId);
- MetaData GetMetaData(string packageFilePath);
- PreInstallWarnings GetPreInstallWarnings(string packageFilePath);
- XElement GetConfigXmlElement(string packageFilePath);
+ ///
+ /// This will run the uninstallation sequence for this
+ ///
+ ///
+ ///
+ ///
+ UninstallationSummary UninstallPackage(PackageDefinition packageDefinition, int userId);
+
+ ///
+ /// Installs a packages data and entities
+ ///
+ ///
+ ///
+ ///
+ ///
+ InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId);
+
+ ///
+ /// Installs a packages files
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable InstallPackageFiles(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId);
+
+ ///
+ /// Reads the package (zip) file and returns the model
+ ///
+ ///
+ ///
+ CompiledPackage ReadPackage(FileInfo packageFile);
}
}
diff --git a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs b/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs
deleted file mode 100644
index 1c31283ee8..0000000000
--- a/src/Umbraco.Core/Packaging/Models/UninstallationSummary.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-using Umbraco.Core.Models;
-using Umbraco.Core.Models.Packaging;
-
-namespace Umbraco.Core.Packaging.Models
-{
- [Serializable]
- [DataContract(IsReference = true)]
- public class UninstallationSummary
- {
- public MetaData MetaData { get; set; }
- public IEnumerable DataTypesUninstalled { get; set; }
- public IEnumerable LanguagesUninstalled { get; set; }
- public IEnumerable DictionaryItemsUninstalled { get; set; }
- public IEnumerable MacrosUninstalled { get; set; }
- public IEnumerable FilesUninstalled { get; set; }
- public IEnumerable TemplatesUninstalled { get; set; }
- public IEnumerable ContentTypesUninstalled { get; set; }
- public IEnumerable StylesheetsUninstalled { get; set; }
- public IEnumerable ContentUninstalled { get; set; }
- public bool PackageUninstalled { get; set; }
- }
-
- internal static class UninstallationSummaryExtentions
- {
- public static UninstallationSummary InitEmpty(this UninstallationSummary summary)
- {
- summary.ContentUninstalled = new List();
- summary.ContentTypesUninstalled = new List();
- summary.DataTypesUninstalled = new List();
- summary.DictionaryItemsUninstalled = new List();
- summary.FilesUninstalled = new List();
- summary.LanguagesUninstalled = new List();
- summary.MacrosUninstalled = new List();
- summary.MetaData = new MetaData();
- summary.TemplatesUninstalled = new List();
- summary.PackageUninstalled = false;
- return summary;
- }
- }
-}
diff --git a/src/Umbraco.Core/Packaging/PackageActionRunner.cs b/src/Umbraco.Core/Packaging/PackageActionRunner.cs
new file mode 100644
index 0000000000..38275d5f0a
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/PackageActionRunner.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Xml.Linq;
+using Umbraco.Core.Logging;
+using Umbraco.Core._Legacy.PackageActions;
+
+namespace Umbraco.Core.Packaging
+{
+ ///
+ /// Package actions are executed on packge install / uninstall.
+ ///
+ internal class PackageActionRunner : IPackageActionRunner
+ {
+ private readonly ILogger _logger;
+ private readonly PackageActionCollection _packageActions;
+
+ public PackageActionRunner(ILogger logger, PackageActionCollection packageActions)
+ {
+ _logger = logger;
+ _packageActions = packageActions;
+ }
+
+ ///
+ public bool RunPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors)
+ {
+ var e = new List();
+ foreach (var ipa in _packageActions)
+ {
+ try
+ {
+ if (ipa.Alias() == actionAlias)
+ ipa.Execute(packageName, actionXml);
+ }
+ catch (Exception ex)
+ {
+ e.Add($"{ipa.Alias()} - {ex.Message}");
+ _logger.Error(ex, "Error loading package action '{PackageActionAlias}' for package {PackageName}", ipa.Alias(), packageName);
+ }
+ }
+
+ errors = e;
+ return e.Count == 0;
+ }
+
+ ///
+ public bool UndoPackageAction(string packageName, string actionAlias, XElement actionXml, out IEnumerable errors)
+ {
+ var e = new List();
+ foreach (var ipa in _packageActions)
+ {
+ try
+ {
+ if (ipa.Alias() == actionAlias)
+ ipa.Undo(packageName, actionXml);
+ }
+ catch (Exception ex)
+ {
+ e.Add($"{ipa.Alias()} - {ex.Message}");
+ _logger.Error(ex, "Error undoing package action '{PackageActionAlias}' for package {PackageName}", ipa.Alias(), packageName);
+ }
+ }
+ errors = e;
+ return e.Count == 0;
+ }
+
+ }
+}
diff --git a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs b/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs
deleted file mode 100644
index 7cacb30bd3..0000000000
--- a/src/Umbraco.Core/Packaging/PackageBinaryInspector.cs
+++ /dev/null
@@ -1,295 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Security;
-using System.Security.Permissions;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Logging;
-
-namespace Umbraco.Core.Packaging
-{
- // Note
- // That class uses ReflectionOnlyLoad which does NOT handle policies (bindingRedirect) and
- // therefore raised warnings when installing a package, if an exact dependency could not be
- // found, though it would be found via policies. So we have to explicitely apply policies
- // where appropriate.
-
- internal class PackageBinaryInspector : MarshalByRefObject
- {
- ///
- /// Entry point to call from your code
- ///
- ///
- ///
- ///
- ///
- ///
- /// Will perform the assembly scan in a separate app domain
- ///
- public static IEnumerable ScanAssembliesForTypeReference(IEnumerable assemblys, out string[] errorReport)
- {
- // need to wrap in a safe call context in order to prevent whatever Umbraco stores
- // in the logical call context from flowing to the created app domain (eg, the
- // UmbracoDatabase is *not* serializable and cannot and should not flow).
- using (new SafeCallContext())
- {
- var appDomain = GetTempAppDomain();
- var type = typeof(PackageBinaryInspector);
- try
- {
- var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap(
- type.Assembly.FullName,
- type.FullName);
- // do NOT turn PerformScan into static (even if ReSharper says so)!
- var result = value.PerformScan(assemblys.ToArray(), out errorReport);
- return result;
- }
- finally
- {
- AppDomain.Unload(appDomain);
- }
- }
- }
-
- ///
- /// Entry point to call from your code
- ///
- ///
- ///
- ///
- ///
- ///
- /// Will perform the assembly scan in a separate app domain
- ///
- public static IEnumerable ScanAssembliesForTypeReference(string dllPath, out string[] errorReport)
- {
- var appDomain = GetTempAppDomain();
- var type = typeof(PackageBinaryInspector);
- try
- {
- var value = (PackageBinaryInspector)appDomain.CreateInstanceAndUnwrap(
- type.Assembly.FullName,
- type.FullName);
- // do NOT turn PerformScan into static (even if ReSharper says so)!
- var result = value.PerformScan(dllPath, out errorReport);
- return result;
- }
- finally
- {
- AppDomain.Unload(appDomain);
- }
- }
-
- ///
- /// Performs the assembly scanning
- ///
- ///
- ///
- ///
- ///
- ///
- /// This method is executed in a separate app domain
- ///
- private IEnumerable PerformScan(IEnumerable assemblies, out string[] errorReport)
- {
- //we need this handler to resolve assembly dependencies when loading below
- AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) =>
- {
- var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name);
- var a = Assembly.ReflectionOnlyLoad(name);
- if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name);
- return a;
- };
-
- //First load each dll file into the context
- // do NOT apply policy here: we want to scan the dlls that are in the binaries
- var loaded = assemblies.Select(Assembly.ReflectionOnlyLoad).ToList();
-
- //scan
- return PerformScan(loaded, out errorReport);
- }
-
- ///
- /// Performs the assembly scanning
- ///
- ///
- ///
- ///
- ///
- ///
- /// This method is executed in a separate app domain
- ///
- private IEnumerable PerformScan(string dllPath, out string[] errorReport)
- {
- if (Directory.Exists(dllPath) == false)
- {
- throw new DirectoryNotFoundException("Could not find directory " + dllPath);
- }
-
- //we need this handler to resolve assembly dependencies when loading below
- AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (s, e) =>
- {
- var name = AppDomain.CurrentDomain.ApplyPolicy(e.Name);
- var a = Assembly.ReflectionOnlyLoad(name);
- if (a == null) throw new TypeLoadException("Could not load assembly " + e.Name);
- return a;
- };
-
- //First load each dll file into the context
- // do NOT apply policy here: we want to scan the dlls that are in the path
- var files = Directory.GetFiles(dllPath, "*.dll");
- var loaded = files.Select(Assembly.ReflectionOnlyLoadFrom).ToList();
-
- //scan
- return PerformScan(loaded, out errorReport);
- }
-
- private static IEnumerable PerformScan(IList loaded, out string[] errorReport)
- {
- var dllsWithReference = new List();
- var errors = new List();
- var assembliesWithErrors = new List();
-
- //load each of the LoadFrom assemblies into the Load context too
- foreach (var a in loaded)
- {
- var name = AppDomain.CurrentDomain.ApplyPolicy(a.FullName);
- Assembly.ReflectionOnlyLoad(name);
- }
-
- //get the list of assembly names to compare below
- var loadedNames = loaded.Select(x => x.GetName().Name).ToArray();
-
- //Then load each referenced assembly into the context
- foreach (var a in loaded)
- {
- //don't load any referenced assemblies that are already found in the loaded array - this is based on name
- // regardless of version. We'll assume that if the assembly found in the folder matches the assembly name
- // being looked for, that is the version the user has shipped their package with and therefore it 'must' be correct
- foreach (var assemblyName in a.GetReferencedAssemblies().Where(ass => loadedNames.Contains(ass.Name) == false))
- {
- try
- {
- var name = AppDomain.CurrentDomain.ApplyPolicy(assemblyName.FullName);
- Assembly.ReflectionOnlyLoad(name);
- }
- catch (FileNotFoundException)
- {
- //if an exception occurs it means that a referenced assembly could not be found
- errors.Add(
- string.Concat("This package references the assembly '",
- assemblyName.Name,
- "' which was not found"));
- assembliesWithErrors.Add(a);
- }
- catch (Exception ex)
- {
- //if an exception occurs it means that a referenced assembly could not be found
- errors.Add(
- string.Concat("This package could not be verified for compatibility. An error occurred while loading a referenced assembly '",
- assemblyName.Name,
- "' see error log for full details."));
- assembliesWithErrors.Add(a);
- Current.Logger.Error(ex, "An error occurred scanning package assembly '{AssemblyName}'", assemblyName.FullName);
- }
- }
- }
-
- var contractType = GetLoadFromContractType();
-
- //now that we have all referenced types into the context we can look up stuff
- foreach (var a in loaded.Except(assembliesWithErrors))
- {
- //now we need to see if they contain any type 'T'
- var reflectedAssembly = a;
-
- try
- {
- var found = reflectedAssembly.GetExportedTypes()
- .Where(contractType.IsAssignableFrom);
-
- if (found.Any())
- {
- dllsWithReference.Add(reflectedAssembly.FullName);
- }
- }
- catch (Exception ex)
- {
- //This is a hack that nobody can seem to get around, I've read everything and it seems that
- // this is quite a common thing when loading types into reflection only load context, so
- // we're just going to ignore this specific one for now
- var typeLoadEx = ex as TypeLoadException;
- if (typeLoadEx != null)
- {
- if (typeLoadEx.Message.InvariantContains("does not have an implementation"))
- {
- //ignore
- continue;
- }
- }
- else
- {
- errors.Add(
- string.Concat("This package could not be verified for compatibility. An error occurred while scanning a packaged assembly '",
- a.GetName().Name,
- "' see error log for full details."));
- assembliesWithErrors.Add(a);
- Current.Logger.Error(ex, "An error occurred scanning package assembly '{AssemblyName}'", a.GetName().FullName);
- }
- }
-
- }
-
- errorReport = errors.ToArray();
- return dllsWithReference;
- }
-
- ///
- /// In order to compare types, the types must be in the same context, this method will return the type that
- /// we are checking against but from the Load context.
- ///
- ///
- ///
- private static Type GetLoadFromContractType()
- {
- var name = AppDomain.CurrentDomain.ApplyPolicy(typeof(T).Assembly.FullName);
- var contractAssemblyLoadFrom = Assembly.ReflectionOnlyLoad(name);
-
- var contractType = contractAssemblyLoadFrom.GetExportedTypes()
- .FirstOrDefault(x => x.FullName == typeof(T).FullName && x.Assembly.FullName == typeof(T).Assembly.FullName);
-
- if (contractType == null)
- {
- throw new InvalidOperationException("Could not find type " + typeof(T) + " in the LoadFrom assemblies");
- }
- return contractType;
- }
-
- ///
- /// Create an app domain
- ///
- ///
- private static AppDomain GetTempAppDomain()
- {
- //copy the current app domain setup but don't shadow copy files
- var appName = "TempDomain" + Guid.NewGuid();
- var domainSetup = new AppDomainSetup
- {
- ApplicationName = appName,
- ShadowCopyFiles = "false",
- ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
- ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
- DynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase,
- LicenseFile = AppDomain.CurrentDomain.SetupInformation.LicenseFile,
- LoaderOptimization = AppDomain.CurrentDomain.SetupInformation.LoaderOptimization,
- PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath,
- PrivateBinPathProbe = AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe
- };
-
- // create new domain with full trust
- return AppDomain.CreateDomain(appName, AppDomain.CurrentDomain.Evidence, domainSetup, new PermissionSet(PermissionState.Unrestricted));
- }
- }
-}
diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
new file mode 100644
index 0000000000..2e9fee9595
--- /dev/null
+++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
@@ -0,0 +1,1279 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Web;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using Umbraco.Core.Collections;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
+using Umbraco.Core.Models.Packaging;
+using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Services;
+using Umbraco.Core.Services.Implement;
+
+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;
+ 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,
+ IContentService contentService, PropertyEditorCollection propertyEditors)
+ {
+ _logger = logger;
+ _fileService = fileService;
+ _macroService = macroService;
+ _localizationService = localizationService;
+ _dataTypeService = dataTypeService;
+ _propertyEditors = propertyEditors;
+ _entityService = entityService;
+ _contentTypeService = contentTypeService;
+ _contentService = contentService;
+ }
+
+ #region Uninstall
+
+ public UninstallationSummary UninstallPackageData(PackageDefinition package, int userId)
+ {
+ if (package == null) throw new ArgumentNullException(nameof(package));
+
+ var removedTemplates = new List();
+ var removedMacros = new List();
+ var removedContentTypes = new List();
+ var removedDictionaryItems = new List();
+ var removedDataTypes = new List();
+ var removedLanguages = new List();
+
+
+ //Uninstall templates
+ foreach (var item in package.Templates.ToArray())
+ {
+ 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());
+ }
+
+ //Uninstall macros
+ foreach (var item in package.Macros.ToArray())
+ {
+ 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());
+ }
+
+ //Remove Document Types
+ var contentTypes = new List();
+ 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));
+ }
+
+ //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);
+ }
+
+ //Remove Dictionary items
+ foreach (var item in package.DictionaryItems.ToArray())
+ {
+ 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());
+ }
+
+ //Remove Data types
+ foreach (var item in package.DataTypes.ToArray())
+ {
+ 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());
+ }
+
+ //Remove Langs
+ foreach (var item in package.Languages.ToArray())
+ {
+ 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());
+ }
+
+ // 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,
+
+ };
+
+ return summary;
+
+
+ }
+
+ #endregion
+
+ #region Content
+
+
+ public IEnumerable ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId)
+ {
+ return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
+ }
+
+ ///
+ /// Imports and saves package xml as
+ ///
+ /// Xml to import
+ /// Optional parent Id for the content being imported
+ /// A dictionary of already imported document types (basically used as a cache)
+ /// Optional Id of the user performing the import
+ /// An enumrable list of generated content
+ public IEnumerable ImportContent(CompiledPackageDocument packageDocument, int parentId, IDictionary importedDocumentTypes, int userId)
+ {
+ var element = packageDocument.XmlData;
+
+ var roots = from doc in element.Elements()
+ where (string)doc.Attribute("isDoc") == ""
+ select doc;
+
+ 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 { element };
+ // var contents = ParseDocumentRootXml(elements, parentId, importedDocumentTypes).ToList();
+ // if (contents.Any())
+ // _contentService.Save(contents, userId);
+
+ // return contents;
+ //}
+
+ //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).");
+ }
+
+ private IEnumerable ParseDocumentRootXml(IEnumerable roots, int parentId, IDictionary importedContentTypes)
+ {
+ var contents = new List();
+ 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);
+ if (content == null) continue;
+
+ contents.Add(content);
+
+ var children = (from child in root.Elements()
+ where (string)child.Attribute("isDoc") == ""
+ select child)
+ .ToList();
+
+ if (children.Count > 0)
+ contents.AddRange(CreateContentFromXml(children, content, importedContentTypes).WhereNotNull());
+ }
+ return contents;
+ }
+
+ private IEnumerable CreateContentFromXml(IEnumerable children, IContent parent, IDictionary importedContentTypes)
+ {
+ var list = new List();
+ 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
+ var child1 = child;
+ 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)
+ {
+ 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;
+ }
+
+ 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;
+ var templateId = element.AttributeValue("template");
+
+ var properties = from property in element.Elements()
+ where property.Attribute("isDoc") == null
+ select property;
+
+ var template = templateId.HasValue ? _fileService.GetTemplate(templateId.Value) : null;
+
+ IContent content = parent == null
+ ? new Content(nodeName, parentId, contentType)
+ {
+ Level = int.Parse(level),
+ SortOrder = int.Parse(sortOrder),
+ TemplateId = template?.Id,
+ Key = key
+ }
+ : new Content(nodeName, parent, contentType)
+ {
+ Level = int.Parse(level),
+ SortOrder = int.Parse(sortOrder),
+ TemplateId = template?.Id,
+ Key = key
+ };
+
+ foreach (var property in properties)
+ {
+ string propertyTypeAlias = property.Name.LocalName;
+ if (content.HasProperty(propertyTypeAlias))
+ {
+ var propertyValue = property.Value;
+
+ var propertyType = contentType.PropertyTypes.FirstOrDefault(pt => pt.Alias == propertyTypeAlias);
+
+ //set property value
+ content.SetValue(propertyTypeAlias, propertyValue);
+ }
+ }
+
+ return content;
+ }
+
+ #endregion
+
+ #region DocumentTypes
+
+ public IEnumerable ImportDocumentType(XElement docTypeElement, int userId)
+ {
+ return ImportDocumentTypes(new []{ docTypeElement }, userId);
+ }
+
+ ///
+ /// Imports and saves package xml as
+ ///
+ /// Xml to import
+ /// Optional id of the User performing the operation. Default is zero (admin).
+ /// An enumrable list of generated ContentTypes
+ public IEnumerable ImportDocumentTypes(IEnumerable docTypeElements, int userId)
+ {
+ return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
+ }
+
+ ///
+ /// Imports and saves package xml as
+ ///
+ /// Xml to import
+ /// Boolean indicating whether or not to import the
+ /// Optional id of the User performing the operation. Default is zero (admin).
+ /// An enumrable list of generated ContentTypes
+ public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
+ {
+ var importedContentTypes = new Dictionary();
+
+ //When you are importing a single doc type we have to assume that the depedencies are already there.
+ //Otherwise something like uSync won't work.
+ var graph = new TopoGraph>(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();
+
+ //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();
+ //Update the structure here - we can't do it untill all DocTypes have been created
+ 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);
+ }
+
+ return list;
+ }
+
+ private Dictionary CreateContentTypeFolderStructure(IEnumerable unsortedDocumentTypes)
+ {
+ var importedFolders = new Dictionary();
+ 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('/');
+ var rootFolder = HttpUtility.UrlDecode(folders[0]);
+ //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(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++)
+ {
+ var folderName = HttpUtility.UrlDecode(folders[i]);
+ 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(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName);
+ throw tryCreateFolder.Exception;
+ }
+ return _contentTypeService.GetContainer(tryCreateFolder.Result.Entity.Id);
+ }
+
+ private IContentType CreateContentTypeFromXml(XElement documentType, IReadOnlyDictionary 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
+ ? new ContentType(-1) { Alias = alias }
+ : new ContentType(parent, alias);
+
+ if (parent != null)
+ contentType.AddContentType(parent);
+
+ return UpdateContentTypeFromXml(documentType, contentType, importedContentTypes);
+ }
+
+ private IContentType UpdateContentTypeFromXml(XElement documentType, IContentType contentType, IReadOnlyDictionary 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;
+
+ //NOTE AllowAtRoot is a new property in the package xml so we need to verify it exists before using it.
+ var allowAtRoot = infoElement.Element("AllowAtRoot");
+ if (allowAtRoot != null)
+ contentType.AllowedAsRoot = allowAtRoot.Value.InvariantEquals("true");
+
+ //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it.
+ var isListView = infoElement.Element("IsListView");
+ if (isListView != null)
+ contentType.IsContainer = isListView.Value.InvariantEquals("true");
+
+ var isElement = infoElement.Element("IsElement");
+ if (isElement != null)
+ contentType.IsElement = isElement.Value.InvariantEquals("true");
+
+ //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);
+ var added = contentType.AddContentType(compositionContentType);
+ }
+ }
+ }
+
+ 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("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("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)
+ {
+ //TODO: We should expose this to the UI during install!
+ _logger.Warn("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!
+ dataTypeDefinition = _dataTypeService.GetByEditorAlias(Constants.PropertyEditors.Aliases.NoEdit).FirstOrDefault();
+ //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);
+ var propertyType = new PropertyType(dataTypeDefinition, property.Element("Alias").Value)
+ {
+ Name = property.Element("Name").Value,
+ Description = (string)property.Element("Description"),
+ Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false,
+ ValidationRegExp = (string)property.Element("Validation"),
+ SortOrder = sortOrder
+ };
+
+ 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 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(
+ "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(() => allowedChild.Id), sortOrder, allowedChild.Alias));
+ sortOrder++;
+ }
+
+ contentType.AllowedContentTypes = allowedChildren;
+ return contentType;
+ }
+
+ ///
+ /// Used during Content import to ensure that the ContentType of a content item exists
+ ///
+ ///
+ ///
+ 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
+
+ ///
+ /// Imports and saves package xml as
+ ///
+ /// Xml to import
+ /// Optional id of the user
+ /// An enumrable list of generated DataTypeDefinitions
+ public IEnumerable ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId)
+ {
+ var dataTypes = new List();
+
+ var importedFolders = CreateDataTypeFolderStructure(dataTypeElements);
+
+ foreach (var dataTypeElement in dataTypeElements)
+ {
+ var dataTypeDefinitionName = dataTypeElement.AttributeValue("Name");
+
+ var dataTypeDefinitionId = dataTypeElement.AttributeValue("Definition");
+ var databaseTypeAttribute = dataTypeElement.Attribute("DatabaseType");
+
+ var parentId = -1;
+ if (importedFolders.ContainsKey(dataTypeDefinitionName))
+ parentId = importedFolders[dataTypeDefinitionName];
+
+ var definition = _dataTypeService.GetDataType(dataTypeDefinitionId);
+ //If the datatypedefinition doesn't already exist we create a new new according to the one in the package xml
+ if (definition == null)
+ {
+ var databaseType = databaseTypeAttribute?.Value.EnumParse(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))
+ editor = new VoidEditor(_logger) { Alias = editorAlias };
+
+ 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);
+ }
+
+ return dataTypes;
+ }
+
+ private Dictionary CreateDataTypeFolderStructure(IEnumerable datatypeElements)
+ {
+ var importedFolders = new Dictionary();
+ foreach (var datatypeElement in datatypeElements)
+ {
+ var foldersAttribute = datatypeElement.Attribute("Folders");
+ if (foldersAttribute != null)
+ {
+ var name = datatypeElement.Attribute("Name").Value;
+ var folders = foldersAttribute.Value.Split('/');
+ var rootFolder = HttpUtility.UrlDecode(folders[0]);
+ //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(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++)
+ {
+ var folderName = HttpUtility.UrlDecode(folders[i]);
+ 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(tryCreateFolder.Exception, "Could not create folder: {FolderName}", folderName);
+ throw tryCreateFolder.Exception;
+ }
+ return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id);
+ }
+
+ #endregion
+
+ #region Dictionary Items
+
+ ///
+ /// Imports and saves the 'DictionaryItems' part of the package xml as a list of
+ ///
+ /// Xml to import
+ ///
+ /// An enumerable list of dictionary items
+ public IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId)
+ {
+ var languages = _localizationService.GetAllLanguages().ToList();
+ return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
+ }
+
+ private IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId)
+ {
+ var items = new List();
+ foreach (var dictionaryItemElement in dictionaryItemElementList)
+ items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId, userId));
+
+ return items;
+ }
+
+ private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages, Guid? parentId, int userId)
+ {
+ var items = new List();
+
+ 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 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 languages, Guid? parentId)
+ {
+ var dictionaryItem = parentId.HasValue ? new DictionaryItem(parentId.Value, key) : new DictionaryItem(key);
+ var translations = new List();
+
+ foreach (var valueElement in dictionaryItemElement.Elements("Value"))
+ AddDictionaryTranslation(translations, valueElement, languages);
+
+ dictionaryItem.Translations = translations;
+ return dictionaryItem;
+ }
+
+ private static bool DictionaryValueIsNew(IEnumerable translations, XElement valueElement)
+ {
+ return translations.All(t =>
+ String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value,
+ StringComparison.InvariantCultureIgnoreCase) != 0
+ );
+ }
+
+ private static void AddDictionaryTranslation(ICollection translations, XElement valueElement, IEnumerable 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
+
+
+ ///
+ /// Imports and saves the 'Languages' part of a package xml as a list of
+ ///
+ /// Xml to import
+ /// Optional id of the User performing the operation
+ /// An enumerable list of generated languages
+ public IEnumerable ImportLanguages(IEnumerable languageElements, int userId)
+ {
+ var list = new List();
+ foreach (var languageElement in languageElements)
+ {
+ var isoCode = languageElement.AttributeValue("CultureAlias");
+ var existingLanguage = _localizationService.GetLanguageByIsoCode(isoCode);
+ if (existingLanguage != null) continue;
+ var langauge = new Language(isoCode)
+ {
+ CultureName = languageElement.AttributeValue("FriendlyName")
+ };
+ _localizationService.Save(langauge, userId);
+ list.Add(langauge);
+ }
+
+ return list;
+ }
+
+ #endregion
+
+ #region Macros
+
+ ///
+ /// Imports and saves the 'Macros' part of a package xml as a list of
+ ///
+ /// Xml to import
+ /// Optional id of the User performing the operation
+ ///
+ public IEnumerable ImportMacros(IEnumerable macroElements, int userId)
+ {
+ 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.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;
+ var macro = existingMacro ?? new Macro(macroAlias, macroName, macroSource, macroType,
+ 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
+
+ public IEnumerable ImportStylesheets(IEnumerable stylesheetElements, int userId)
+ {
+ var result = new List();
+
+ 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 ImportTemplate(XElement templateElement, int userId)
+ {
+ return ImportTemplates(new[] {templateElement}, userId);
+ }
+
+ ///
+ /// Imports and saves package xml as
+ ///
+ /// Xml to import
+ /// Optional user id
+ /// An enumrable list of generated Templates
+ public IEnumerable ImportTemplates(IReadOnlyCollection templateElements, int userId)
+ {
+ var templates = new List();
+
+ var graph = new TopoGraph>(x => x.Key, x => x.Dependencies);
+
+ foreach (var tempElement in templateElements)
+ {
+ var dependencies = new List();
+ 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(
+ "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");
+
+ var isMasterPage = IsMasterPageSyntax(design);
+ var path = isMasterPage ? MasterpagePath(alias) : ViewPath(alias);
+
+ var existingTemplate = _fileService.GetTemplate(alias) as Template;
+ var template = existingTemplate ?? new Template(templateName, alias);
+ 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(() => masterTemplate.Id);
+ }
+ templates.Add(template);
+ }
+
+ if (templates.Any())
+ _fileService.SaveTemplate(templates, userId);
+
+ return templates;
+ }
+
+
+ private bool IsMasterPageSyntax(string code)
+ {
+ return Regex.IsMatch(code, @"<%@\s*Master", RegexOptions.IgnoreCase) ||
+ code.InvariantContains("
+ /// Converts a to and from XML
+ ///
+ public class PackageDefinitionXmlParser
+ {
+ private readonly ILogger _logger;
+
+ public PackageDefinitionXmlParser(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public PackageDefinition ToPackageDefinition(XElement xml)
+ {
+ if (xml == null) return null;
+
+ var retVal = new PackageDefinition
+ {
+ Id = xml.AttributeValue("id"),
+ Name = xml.AttributeValue("name") ?? string.Empty,
+ PackagePath = xml.AttributeValue("packagePath") ?? string.Empty,
+ Version = xml.AttributeValue("version") ?? string.Empty,
+ Url = xml.AttributeValue