Merge remote-tracking branch 'origin/temp8' into temp8-3668-query-builder-snippits

This commit is contained in:
Bjarke Berg
2019-01-16 12:49:23 +01:00
149 changed files with 1306 additions and 2795 deletions

View File

@@ -3,57 +3,57 @@
[string]$Directory
)
$workingDirectory = $Directory
CD $workingDirectory
CD "$($workingDirectory)"
# Clone repo
$fullGitUrl = "https://$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
git clone $fullGitUrl 2>&1 | % { $_.ToString() }
$fullGitUrl = "https://$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
git clone $($fullGitUrl) $($env:GIT_REPOSITORYNAME) 2>&1 | % { $_.ToString() }
# Remove everything so that unzipping the release later will update everything
# Don't remove the readme file nor the git directory
Write-Host "Cleaning up git directory before adding new version"
Remove-Item -Recurse $workingDirectory\$env:GIT_REPOSITORYNAME\* -Exclude README.md,.git
Remove-Item -Recurse "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)\*" -Exclude README.md,.git
# Find release zip
$zipsDir = "$workingDirectory\$env:BUILD_DEFINITIONNAME\zips"
$zipsDir = "$($workingDirectory)\$($env:BUILD_DEFINITIONNAME)\zips"
$pattern = "UmbracoCms.([0-9]{1,2}.[0-9]{1,3}.[0-9]{1,3}).zip"
Write-Host "Searching for Umbraco release files in $workingDirectory\$zipsDir for a file with pattern $pattern"
$file = (Get-ChildItem $zipsDir | Where-Object { $_.Name -match "$pattern" })
Write-Host "Searching for Umbraco release files in $($zipsDir) for a file with pattern $($pattern)"
$file = (Get-ChildItem "$($zipsDir)" | Where-Object { $_.Name -match "$($pattern)" })
if($file)
{
# Get release name
$version = [regex]::Match($file.Name, $pattern).captures.groups[1].value
$releaseName = "Umbraco $version"
Write-Host "Found $releaseName"
$version = [regex]::Match($($file.Name), $($pattern)).captures.groups[1].value
$releaseName = "Umbraco $($version)"
Write-Host "Found $($releaseName)"
# Unzip into repository to update release
Add-Type -AssemblyName System.IO.Compression.FileSystem
Write-Host "Unzipping $($file.FullName) to $workingDirectory\$env:GIT_REPOSITORYNAME"
[System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$workingDirectory\$env:GIT_REPOSITORYNAME")
Write-Host "Unzipping $($file.FullName) to $($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
[System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)")
# Telling git who we are
git config --global user.email "coffee@umbraco.com" 2>&1 | % { $_.ToString() }
git config --global user.name "Umbraco HQ" 2>&1 | % { $_.ToString() }
# Commit
CD $env:GIT_REPOSITORYNAME
Write-Host "Committing Umbraco $version Release from Build Output"
CD "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
Write-Host "Committing Umbraco $($version) Release from Build Output"
git add . 2>&1 | % { $_.ToString() }
git commit -m " Release $releaseName from Build Output" 2>&1 | % { $_.ToString() }
git commit -m " Release $($releaseName) from Build Output" 2>&1 | % { $_.ToString() }
# Tag the release
git tag -a "v$version" -m "v$version"
git tag -a "v$($version)" -m "v$($version)"
# Push release to master
$fullGitAuthUrl = "https://$($env:GIT_USERNAME):$GitHubPersonalAccessToken@$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
git push $fullGitAuthUrl 2>&1 | % { $_.ToString() }
$fullGitAuthUrl = "https://$($env:GIT_USERNAME):$($GitHubPersonalAccessToken)@$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
git push $($fullGitAuthUrl) 2>&1 | % { $_.ToString() }
#Push tag to master
git push $fullGitAuthUrl --tags 2>&1 | % { $_.ToString() }
git push $($fullGitAuthUrl) --tags 2>&1 | % { $_.ToString() }
}
else
{
Write-Error "Umbraco release file not found, searched in $workingDirectory\$zipsDir for a file with pattern $pattern - cancelling"
Write-Error "Umbraco release file not found, searched in $($workingDirectory)\$($zipsDir) for a file with pattern $($pattern) - canceling"
}

View File

@@ -25,7 +25,7 @@
<dependency id="ClientDependency" version="[1.9.7,1.999999)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0,1.999999)" />
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
<dependency id="Examine" version="[1.0.0-beta072,1.999999)" />
<dependency id="Examine" version="[1.0.0-beta078,1.999999)" />
<dependency id="HtmlAgilityPack" version="[1.8.9,1.999999)" />
<dependency id="ImageProcessor" version="[2.6.2.25,2.999999)" />
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />

View File

@@ -22,7 +22,7 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.3, 2.999999)" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.31]" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.33]" />
<dependency id="ImageProcessor.Web" version="[4.9.3.25,4.999999)" />
<dependency id="ImageProcessor.Web.Config" version="[2.4.1.19,2.999999)" />
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Core.Components
public class Composition : IRegister
{
private readonly Dictionary<Type, ICollectionBuilder> _builders = new Dictionary<Type, ICollectionBuilder>();
private readonly Dictionary<Type, Unique> _uniques = new Dictionary<Type, Unique>();
private readonly Dictionary<string, Action<IRegister>> _uniques = new Dictionary<string, Action<IRegister>>();
private readonly IRegister _register;
/// <summary>
@@ -83,11 +83,32 @@ namespace Umbraco.Core.Components
/// <inheritdoc />
public void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.Register(factory, lifetime);
/// <inheritdoc />
public void RegisterInstance(Type serviceType, object instance)
=> _register.RegisterInstance(serviceType, instance);
public void Register(Type serviceType, object instance)
=> _register.Register(serviceType, instance);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Type implementingType, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(implementingType, lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> _register.RegisterFor<TService, TTarget>(factory, lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(TService instance)
where TService : class
=> _register.RegisterFor<TService, TTarget>(instance);
/// <inheritdoc />
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<TService>()
=> GetUniqueName(typeof(TService));
private string GetUniqueName(Type serviceType)
=> serviceType.FullName;
private string GetUniqueName<TService, TTarget>()
=> GetUniqueName(typeof(TService), typeof(TTarget));
private string GetUniqueName(Type serviceType, Type targetType)
=> serviceType.FullName + "::" + targetType.FullName;
/// <summary>
/// Registers a unique service.
/// Registers a unique service as its own implementation.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUnique(Type serviceType)
=> _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, Lifetime.Singleton);
/// <summary>
/// Registers a unique service with an implementation type.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUnique(Type serviceType, Type implementingType)
=> _uniques[serviceType] = new Unique(serviceType, implementingType);
=> _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, implementingType, Lifetime.Singleton);
/// <summary>
/// Registers a unique service.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUnique(Type serviceType, object instance)
=> _uniques[serviceType] = new Unique(serviceType, instance);
/// <summary>
/// Registers a unique service.
/// Registers a unique service with an implementation factory.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUnique<TService>(Func<IFactory, TService> factory)
=> _uniques[typeof(TService)] = new Unique<TService>(factory);
where TService : class
=> _uniques[GetUniqueName<TService>()] = register => register.Register<TService>(factory, Lifetime.Singleton);
private class Unique
{
private readonly Type _serviceType;
private readonly Type _implementingType;
private readonly object _instance;
/// <summary>
/// Registers a unique service with an implementing instance.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUnique(Type serviceType, object instance)
=> _uniques[GetUniqueName(serviceType)] = register => register.Register(serviceType, instance);
protected Unique(Type serviceType)
{
_serviceType = serviceType;
}
/// <summary>
/// Registers a unique service for a target, as its own implementation.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>()
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(Lifetime.Singleton);
public Unique(Type serviceType, Type implementingType)
: this(serviceType)
{
_implementingType = implementingType;
}
/// <summary>
/// Registers a unique service for a target, with an implementing type.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(Type implementingType)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(implementingType, Lifetime.Singleton);
public Unique(Type serviceType, object instance)
: this(serviceType)
{
_instance = instance;
}
/// <summary>
/// Registers a unique service for a target, with an implementation factory.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(Func<IFactory, TService> factory)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(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<TService> : Unique
{
private readonly Func<IFactory, TService> _factory;
public Unique(Func<IFactory, TService> factory)
: base(typeof(TService))
{
_factory = factory;
}
public override void RegisterWith(IRegister register)
{
register.Register(_factory, Lifetime.Singleton);
}
}
/// <summary>
/// Registers a unique service for a target, with an implementing instance.
/// </summary>
/// <remarks>Unique services have one single implementation, and a Singleton lifetime.</remarks>
public void RegisterUniqueFor<TService, TTarget>(TService instance)
where TService : class
=> _uniques[GetUniqueName<TService, TTarget>()] = register => register.RegisterFor<TService, TTarget>(instance);
#endregion

View File

@@ -26,15 +26,16 @@ namespace Umbraco.Core.Components
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition, Func<IFactory, IFileSystem> supportingFileSystemFactory)
public static void RegisterFileSystem<TFileSystem, TImplementing>(this Composition composition)
where TImplementing : FileSystemWrapper, TFileSystem
where TFileSystem : class
{
composition.RegisterUnique<TFileSystem>(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supportingFileSystemFactory(factory));
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
@@ -43,15 +44,15 @@ namespace Umbraco.Core.Components
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <param name="supportingFileSystemFactory">A factory method creating the supporting filesystem.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition, Func<IFactory, IFileSystem> supportingFileSystemFactory)
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supportingFileSystemFactory(factory));
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
@@ -280,6 +281,22 @@ namespace Umbraco.Core.Components
composition.RegisterUnique(_ => helper);
}
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFactory, IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(filesystemFactory);
/// <summary>
/// Sets the underlying media filesystem.
/// </summary>
/// <param name="composition">A composition.</param>
/// <param name="filesystemFactory">A filesystem factory.</param>
public static void SetMediaFileSystem(this Composition composition, Func<IFileSystem> filesystemFactory)
=> composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(_ => filesystemFactory());
#endregion
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class CollectionBuilderBase<TBuilder, TCollection, TItem> : ICollectionBuilder<TCollection, TItem>
where TBuilder: CollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : IBuilderCollection<TItem>
where TCollection : class, IBuilderCollection<TItem>
{
private readonly List<Type> _types = new List<Type>();
private readonly object _locker = new object();

View File

@@ -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<IMediaFileSystem, MediaFileSystem>(
* factory => new PhysicalFileSystem("~/somewhere"));
* composition.RegisterUniqueFor<IFileSystem, IMediaFileSystem>(...);
*
* 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<MyFileSystem>(
* factory => new PhysicalFileSystem("~/my"));
* composition.RegisterFileSystem<MyFileSystem>();
*
* Register the underlying filesystem:
*
* composition.RegisterUniqueFor<IFileSystem, MyFileSystem>(...);
*
* 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<IMyFileSystem, MyFileSystem>(
* factory => new PhysicalFileSystem("~/my"));
* composition.RegisterFileSystem<IMyFileSystem, MyFileSystem>();
* composition.RegisterUniqueFor<IFileSystem, IMyFileSystem>(...);
*
* 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<IMediaPathScheme, TwoGuidsMediaPathScheme>();
// register the IMediaFileSystem implementation with a supporting filesystem
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>(
factory => new PhysicalFileSystem("~/media"));
// register the IMediaFileSystem implementation
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>();
// 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<IMediaFileSystem>() returns the underlying filesystem
composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
return composition;
}

View File

@@ -51,6 +51,13 @@ namespace Umbraco.Core.Composing
public static void RegisterUnique<TService, TImplementing>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementation type, for a target.
/// </summary>
public static void RegisterUniqueFor<TService, TTarget, TImplementing>(this Composition composition)
where TService : class
=> composition.RegisterUniqueFor<TService, TTarget>(typeof(TImplementing));
/// <summary>
/// Registers a unique service with an implementing instance.
/// </summary>

View File

@@ -17,6 +17,7 @@ namespace Umbraco.Core.Composing
/// <returns>An instance of the specified type.</returns>
/// <remarks>Throws an exception if the factory failed to get an instance of the specified type.</remarks>
public static T GetInstance<T>(this IFactory factory)
where T : class
=> (T)factory.GetInstance(typeof(T));
/// <summary>
@@ -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.</remarks>
public static T TryGetInstance<T>(this IFactory factory)
where T : class
=> (T)factory.TryGetInstance(typeof(T));
/// <summary>
@@ -42,6 +44,7 @@ namespace Umbraco.Core.Composing
/// <para>The arguments are used as dependencies by the factory.</para>
/// </remarks>
public static T CreateInstance<T>(this IFactory factory, params object[] args)
where T : class
=> (T)factory.CreateInstance(typeof(T), args);
/// <summary>

View File

@@ -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
/// <summary>
/// Defines a service factory for Umbraco.
/// </summary>
@@ -28,6 +21,15 @@ namespace Umbraco.Core.Composing
/// <remarks>Throws an exception if the container failed to get an instance of the specified type.</remarks>
object GetInstance(Type type);
/// <summary>
/// Gets a targeted instance of a service.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <returns>The instance of the specified type for the specified target.</returns>
/// <remarks>Throws an exception if the container failed to get an instance of the specified type.</remarks>
TService GetInstanceFor<TService, TTarget>();
/// <summary>
/// Tries to get an instance of a service.
/// </summary>
@@ -48,7 +50,8 @@ namespace Umbraco.Core.Composing
/// Gets all instances of a service.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
IEnumerable<TService> GetAllInstances<TService>();
IEnumerable<TService> GetAllInstances<TService>()
where TService : class;
/// <summary>
/// Releases an instance.

View File

@@ -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
/// <summary>
/// Defines a service register for Umbraco.
/// </summary>
@@ -36,12 +25,53 @@ namespace Umbraco.Core.Composing
/// <summary>
/// Registers a service with an implementation factory.
/// </summary>
void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient);
void Register<TService>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service with an implementing instance.
/// </summary>
void RegisterInstance(Type serviceType, object instance);
void Register(Type serviceType, object instance);
/// <summary>
/// Registers a service for a target, as its own implementation.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementation type.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Type implementingType, Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementation factory.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(Func<IFactory, TService> factory, Lifetime lifetime = Lifetime.Transient)
where TService : class;
/// <summary>
/// Registers a service for a target, with an implementing instance.
/// </summary>
/// <remarks>
/// There can only be one implementation or instanced registered for a service and target;
/// what happens if many are registered is not specified.
/// </remarks>
void RegisterFor<TService, TTarget>(TService instance)
where TService : class;
/// <summary>
/// Registers a base type for auto-registration.

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class LazyCollectionBuilderBase<TBuilder, TCollection, TItem> : CollectionBuilderBase<TBuilder, TCollection, TItem>
where TBuilder : LazyCollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : IBuilderCollection<TItem>
where TCollection : class, IBuilderCollection<TItem>
{
private readonly List<Func<IEnumerable<Type>>> _producers = new List<Func<IEnumerable<Type>>>();
private readonly List<Type> _excluded = new List<Type>();

View File

@@ -102,18 +102,25 @@ namespace Umbraco.Core.Composing.LightInject
/// <inheritdoc />
public IFactory CreateFactory() => this;
private static string GetTargetedServiceName<TTarget>() => "TARGET:" + typeof(TTarget).FullName;
#region Factory
/// <inheritdoc />
public object GetInstance(Type type)
=> Container.GetInstance(type);
/// <inheritdoc />
public TService GetInstanceFor<TService, TTarget>()
=> Container.GetInstance<TService>(GetTargetedServiceName<TTarget>());
/// <inheritdoc />
public object TryGetInstance(Type type)
=> Container.TryGetInstance(type);
/// <inheritdoc />
public IEnumerable<T> GetAllInstances<T>()
where T : class
=> Container.GetAllInstances<T>();
/// <inheritdoc />
@@ -138,21 +145,7 @@ namespace Umbraco.Core.Composing.LightInject
/// <inheritdoc />
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));
/// <inheritdoc />
public void Register(Type serviceType, Type implementingType, Lifetime lifetime = Lifetime.Transient)
@@ -174,22 +167,41 @@ namespace Umbraco.Core.Composing.LightInject
/// <inheritdoc />
public void Register<TService>(Func<IFactory, TService> 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));
}
/// <inheritdoc />
public void Register(Type serviceType, object instance)
=> Container.RegisterInstance(serviceType, instance);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Lifetime lifetime = Lifetime.Transient)
where TService : class
=> RegisterFor<TService, TTarget>(typeof(TService), lifetime);
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(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<TTarget>(), GetLifetime(lifetime));
}
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(Func<IFactory, TService> 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<TTarget>(), GetLifetime(lifetime));
}
/// <inheritdoc />
public void RegisterFor<TService, TTarget>(TService instance)
where TService : class
=> Container.RegisterInstance(typeof(TService), instance, GetTargetedServiceName<TTarget>());
private ILifetime GetLifetime(Lifetime lifetime)
{
switch (lifetime)
@@ -207,10 +219,6 @@ namespace Umbraco.Core.Composing.LightInject
}
}
/// <inheritdoc />
public void RegisterInstance(Type serviceType, object instance)
=> Container.RegisterInstance(serviceType, instance);
/// <inheritdoc />
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
//
///// <inheritdoc />
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, TDependency> factory)
// => Container.RegisterConstructorDependency((f, x) => factory(this, x));
//
///// <inheritdoc />
//public void RegisterConstructorDependency<TDependency>(Func<IContainer, ParameterInfo, object[], TDependency> 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<ILifetime> lifetime, Func<Type, Type, bool> shouldRegister, Func<Type, Type, string> 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
}
}

View File

@@ -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!

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Core.Composing
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class OrderedCollectionBuilderBase<TBuilder, TCollection, TItem> : CollectionBuilderBase<TBuilder, TCollection, TItem>
where TBuilder : OrderedCollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : IBuilderCollection<TItem>
where TCollection : class, IBuilderCollection<TItem>
{
protected abstract TBuilder This { get; }

View File

@@ -11,22 +11,32 @@
public static void Register<TService, TImplementing>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
=> register.Register(typeof(TService), typeof(TImplementing), lifetime);
/// <summary>
/// Registers a service with an implementation type, for a target.
/// </summary>
public static void RegisterFor<TService, TImplementing, TTarget>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> register.RegisterFor<TService, TTarget>(typeof(TImplementing), lifetime);
/// <summary>
/// Registers a service as its own implementation.
/// </summary>
public static void Register<TService>(this IRegister register, Lifetime lifetime = Lifetime.Transient)
where TService : class
=> register.Register(typeof(TService), lifetime);
/// <summary>
/// Registers a service with an implementing instance.
/// </summary>
public static void RegisterInstance<TService>(this IRegister register, TService instance)
=> register.RegisterInstance(typeof(TService), instance);
public static void Register<TService>(this IRegister register, TService instance)
where TService : class
=> register.Register(typeof(TService), instance);
/// <summary>
/// Registers a base type for auto-registration.
/// </summary>
public static void RegisterAuto<TServiceBase>(this IRegister register)
where TServiceBase : class
=> register.RegisterAuto(typeof(TServiceBase));
}
}

View File

@@ -0,0 +1,18 @@
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a base class for targeted service factories.
/// </summary>
/// <typeparam name="TService"></typeparam>
public abstract class TargetedServiceFactory<TService>
{
private readonly IFactory _factory;
protected TargetedServiceFactory(IFactory factory)
{
_factory = factory;
}
public TService For<TTarget>() => _factory.GetInstanceFor<TService, TTarget>();
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Composing
/// <typeparam name="TItem">The type of the items.</typeparam>
public abstract class WeightedCollectionBuilderBase<TBuilder, TCollection, TItem> : CollectionBuilderBase<TBuilder, TCollection, TItem>
where TBuilder : WeightedCollectionBuilderBase<TBuilder, TCollection, TItem>
where TCollection : IBuilderCollection<TItem>
where TCollection : class, IBuilderCollection<TItem>
{
protected abstract TBuilder This { get; }

View File

@@ -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);

View File

@@ -17,19 +17,19 @@ namespace Umbraco.Core.Deploy
IEnumerable<string> PropertyEditorAliases { get; }
/// <summary>
/// Gets the deploy property corresponding to a content property.
/// Gets the deploy property value corresponding to a content property value, and gather dependencies.
/// </summary>
/// <param name="property">The content property.</param>
/// <param name="value">The content property value.</param>
/// <param name="dependencies">The content dependencies.</param>
/// <returns>The deploy property value.</returns>
string GetValue(Property property, ICollection<ArtifactDependency> dependencies);
string ToArtifact(object value, ICollection<ArtifactDependency> dependencies);
/// <summary>
/// Sets a content property value using a deploy property.
/// Gets the content property value corresponding to a deploy property value.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="alias">The property alias.</param>
/// <param name="value">The deploy property value.</param>
void SetValue(IContentBase content, string alias, string value);
/// <param name="currentValue">The current content property value.</param>
/// <returns>The content property value.</returns>
object FromArtifact(string value, object currentValue);
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.IO
{
public class SupportingFileSystems : TargetedServiceFactory<IFileSystem>
{
public SupportingFileSystems(IFactory factory)
: base(factory)
{ }
}
}

View File

@@ -121,6 +121,8 @@ namespace Umbraco.Core.Migrations.Upgrade
To<DropTaskTables>("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version
To<MakeTagsVariant>("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}");
To<MakeRedirectUrlVariant>("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}");
To<AddContentTypeIsElementColumn>("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}");
//FINAL

View File

@@ -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<ContentTypeDto>("isElement");
}
}
}

View File

@@ -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<ContentTypeSort> _allowedContentTypes;
@@ -90,6 +91,7 @@ namespace Umbraco.Core.Models
public readonly PropertyInfo IconSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Icon);
public readonly PropertyInfo ThumbnailSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, string>(x => x.Thumbnail);
public readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.AllowedAsRoot);
public readonly PropertyInfo IsElementSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.IsElement);
public readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.IsContainer);
public readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<ContentTypeSort>>(x => x.AllowedContentTypes);
public readonly PropertyInfo PropertyGroupsSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
@@ -180,6 +182,14 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _isContainer, Ps.Value.IsContainerSelector);
}
/// <inheritdoc />
[DataMember]
public bool IsElement
{
get => _isElement;
set => SetPropertyValueAndDetectChanges(value, ref _isElement, Ps.Value.IsElementSelector);
}
/// <summary>
/// Gets or sets a list of integer Ids for allowed ContentTypes
/// </summary>

View File

@@ -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;

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Core.Models
/// the icon (eg. <c>icon-home</c>) along with an optional CSS class name representing the
/// color (eg. <c>icon-blue</c>). Put together, the value for this scenario would be
/// <c>icon-home color-blue</c>.
///
///
/// If a class name for the color isn't specified, the icon color will default to black.
/// </summary>
string Icon { get; set; }
@@ -48,6 +48,16 @@ namespace Umbraco.Core.Models
/// </remarks>
bool IsContainer { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this content type is for an element.
/// </summary>
/// <remarks>
/// <para>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.</para>
/// </remarks>
bool IsElement { get; set; }
/// <summary>
/// Gets or sets the content variation of the content type.
/// </summary>

View File

@@ -4,13 +4,18 @@
/// The type of published element.
/// </summary>
/// <remarks>Can be a simple element, or a document, a media, a member.</remarks>
public enum PublishedItemType // fixme - need to rename to PublishedElementType but then conflicts?
public enum PublishedItemType
{
/// <summary>
/// Unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// An element.
/// </summary>
Element,
/// <summary>
/// A document.
/// </summary>

View File

@@ -41,6 +41,10 @@ namespace Umbraco.Core.Persistence.Dtos
[Constraint(Default = "0")]
public bool IsContainer { get; set; }
[Column("isElement")]
[Constraint(Default = "0")]
public bool IsElement { get; set; }
[Column("allowAtRoot")]
[Constraint(Default = "0")]
public bool AllowAtRoot { get; set; }

View File

@@ -107,6 +107,7 @@ namespace Umbraco.Core.Persistence.Factories
entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId;
entity.AllowedAsRoot = dto.AllowAtRoot;
entity.IsContainer = dto.IsContainer;
entity.IsElement = dto.IsElement;
entity.Trashed = dto.NodeDto.Trashed;
entity.Variations = (ContentVariation) dto.Variations;
}
@@ -132,6 +133,7 @@ namespace Umbraco.Core.Persistence.Factories
NodeId = entity.Id,
AllowAtRoot = entity.AllowedAsRoot,
IsContainer = entity.IsContainer,
IsElement = entity.IsElement,
Variations = (byte) entity.Variations,
NodeDto = BuildNodeDto(entity, nodeObjectType)
};

View File

@@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers
CacheMap<ContentType, ContentTypeDto>(src => src.Description, dto => dto.Description);
CacheMap<ContentType, ContentTypeDto>(src => src.Icon, dto => dto.Icon);
CacheMap<ContentType, ContentTypeDto>(src => src.IsContainer, dto => dto.IsContainer);
CacheMap<ContentType, ContentTypeDto>(src => src.IsElement, dto => dto.IsElement);
CacheMap<ContentType, ContentTypeDto>(src => src.Thumbnail, dto => dto.Thumbnail);
}
}

View File

@@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers
CacheMap<MediaType, ContentTypeDto>(src => src.Description, dto => dto.Description);
CacheMap<MediaType, ContentTypeDto>(src => src.Icon, dto => dto.Icon);
CacheMap<MediaType, ContentTypeDto>(src => src.IsContainer, dto => dto.IsContainer);
CacheMap<MediaType, ContentTypeDto>(src => src.IsElement, dto => dto.IsElement);
CacheMap<MediaType, ContentTypeDto>(src => src.Thumbnail, dto => dto.Thumbnail);
}
}

View File

@@ -35,6 +35,7 @@ namespace Umbraco.Core.Persistence.Mappers
CacheMap<MemberType, ContentTypeDto>(src => src.Description, dto => dto.Description);
CacheMap<MemberType, ContentTypeDto>(src => src.Icon, dto => dto.Icon);
CacheMap<MemberType, ContentTypeDto>(src => src.IsContainer, dto => dto.IsContainer);
CacheMap<MemberType, ContentTypeDto>(src => src.IsElement, dto => dto.IsElement);
CacheMap<MemberType, ContentTypeDto>(src => src.Thumbnail, dto => dto.Thumbnail);
}
}

View File

@@ -1283,7 +1283,7 @@ AND umbracoNode.id <> @id",
if (db == null) throw new ArgumentNullException(nameof(db));
var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey,
umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
@@ -1384,6 +1384,7 @@ AND umbracoNode.id <> @id",
Description = currCt.ctDesc,
Icon = currCt.ctIcon,
IsContainer = currCt.ctIsContainer,
IsElement = currCt.ctIsElement,
NodeId = currCt.ctId,
PrimaryKey = currCt.ctPk,
Thumbnail = currCt.ctThumb,
@@ -1422,7 +1423,7 @@ AND umbracoNode.id <> @id",
var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId,
cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey,
umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
@@ -1559,6 +1560,7 @@ AND umbracoNode.id <> @id",
Description = currCt.ctDesc,
Icon = currCt.ctIcon,
IsContainer = currCt.ctIsContainer,
IsElement = currCt.ctIsElement,
NodeId = currCt.ctId,
PrimaryKey = currCt.ctPk,
Thumbnail = currCt.ctThumb,

View File

@@ -319,7 +319,7 @@ namespace Umbraco.Core.Runtime
/// Gets a profiler.
/// </summary>
protected virtual IProfiler GetProfiler()
=> new LogProfiler(ProfilingLogger);
=> new LogProfiler(Logger);
/// <summary>
/// Gets the application caches.

View File

@@ -7,27 +7,32 @@ namespace Umbraco.Core.Scoping
internal class ScopeContext : IScopeContext, IInstanceIdentifiable
{
private Dictionary<string, IEnlistedObject> _enlisted;
private bool _exiting;
public void ScopeExit(bool completed)
{
if (_enlisted == null)
return;
_exiting = true;
// fixme - can we create infinite loops?
// fixme - what about nested events? will they just be plainly ignored = really bad?
List<Exception> exceptions = null;
foreach (var enlisted in _enlisted.Values.OrderBy(x => x.Priority))
List<IEnlistedObject> orderedEnlisted;
while ((orderedEnlisted = _enlisted.Values.OrderBy(x => x.Priority).ToList()).Count > 0)
{
try
_enlisted.Clear();
foreach (var enlisted in orderedEnlisted)
{
enlisted.Execute(completed);
}
catch (Exception e)
{
if (exceptions == null)
exceptions = new List<Exception>();
exceptions.Add(e);
try
{
enlisted.Execute(completed);
}
catch (Exception e)
{
if (exceptions == null)
exceptions = new List<Exception>();
exceptions.Add(e);
}
}
}
@@ -74,9 +79,6 @@ namespace Umbraco.Core.Scoping
public T Enlist<T>(string key, Func<T> creator, Action<bool, T> action = null, int priority = 100)
{
if (_exiting)
throw new InvalidOperationException("Cannot enlist now, context is exiting.");
var enlistedObjects = _enlisted ?? (_enlisted = new Dictionary<string, IEnlistedObject>());
if (enlistedObjects.TryGetValue(key, out var enlisted))

View File

@@ -337,7 +337,8 @@ namespace Umbraco.Core.Services
new XElement("Thumbnail", contentType.Thumbnail),
new XElement("Description", contentType.Description),
new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
new XElement("IsListView", contentType.IsContainer.ToString()));
new XElement("IsListView", contentType.IsContainer.ToString()),
new XElement("IsElement", contentType.IsElement.ToString()));
var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
if(masterContentType != null)

View File

@@ -578,6 +578,10 @@ namespace Umbraco.Core.Services.Implement
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)

View File

@@ -196,6 +196,7 @@
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Composing\LightInject\MixedLightInjectScopeManagerProvider.cs" />
<Compile Include="Composing\OrderedCollectionBuilderBase.cs" />
<Compile Include="Composing\TargetedServiceFactory.cs" />
<Compile Include="Composing\TypeFinder.cs" />
<Compile Include="Composing\TypeHelper.cs" />
<Compile Include="Composing\TypeLoader.cs" />
@@ -343,6 +344,7 @@
<Compile Include="IO\IMediaPathScheme.cs" />
<Compile Include="IO\MediaPathSchemes\OriginalMediaPathScheme.cs" />
<Compile Include="IO\MediaPathSchemes\TwoGuidsMediaPathScheme.cs" />
<Compile Include="IO\SupportingFileSystems.cs" />
<Compile Include="KeyValuePairExtensions.cs" />
<Compile Include="Logging\IProfilingLogger.cs" />
<Compile Include="Logging\LogHttpRequest.cs" />
@@ -375,6 +377,7 @@
<Compile Include="Migrations\Upgrade\V_7_9_0\AddUmbracoAuditTable.cs" />
<Compile Include="Migrations\Upgrade\V_7_9_0\AddUmbracoConsentTable.cs" />
<Compile Include="Migrations\Upgrade\V_7_9_0\CreateSensitiveDataUserGroup.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddContentTypeIsElementColumn.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddLogTableColumns.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddTypedLabels.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\AddVariationTables1A.cs" />

View File

@@ -42,9 +42,9 @@ namespace Umbraco.Examine
var values = new Dictionary<string, IEnumerable<object>>
{
{"icon", c.ContentType.Icon.Yield()},
{UmbracoExamineIndex.PublishedFieldName, new object[] {c.Published ? 1 : 0}}, //Always add invariant published value
{UmbracoExamineIndex.PublishedFieldName, new object[] {c.Published ? "y" : "n"}}, //Always add invariant published value
{"id", new object[] {c.Id}},
{"key", new object[] {c.Key}},
{UmbracoExamineIndex.NodeKeyFieldName, new object[] {c.Key}},
{"parentID", new object[] {c.Level > 1 ? c.ParentId : -1}},
{"level", new object[] {c.Level}},
{"creatorID", new object[] {c.CreatorId}},
@@ -61,12 +61,12 @@ namespace Umbraco.Examine
{"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() },
{"writerID", new object[] {c.WriterId}},
{"templateID", new object[] {c.TemplateId ?? 0}},
{UmbracoContentIndex.VariesByCultureFieldName, new object[] {0}},
{UmbracoContentIndex.VariesByCultureFieldName, new object[] {"n"}},
};
if (isVariant)
{
values[UmbracoContentIndex.VariesByCultureFieldName] = new object[] { 1 };
values[UmbracoContentIndex.VariesByCultureFieldName] = new object[] { "y" };
foreach (var culture in c.AvailableCultures)
{
@@ -76,7 +76,7 @@ namespace Umbraco.Examine
values[$"nodeName_{lowerCulture}"] = PublishedValuesOnly
? c.GetPublishName(culture).Yield()
: c.GetCultureName(culture).Yield();
values[$"{UmbracoExamineIndex.PublishedFieldName}_{lowerCulture}"] = (c.IsCulturePublished(culture) ? 1 : 0).Yield<object>();
values[$"{UmbracoExamineIndex.PublishedFieldName}_{lowerCulture}"] = (c.IsCulturePublished(culture) ? "y" : "n").Yield<object>();
values[$"updateDate_{lowerCulture}"] = PublishedValuesOnly
? c.GetPublishDate(culture).Yield<object>()
: c.GetUpdateDate(culture).Yield<object>();

View File

@@ -95,17 +95,17 @@ namespace Umbraco.Examine
if (!valueSet.Values.TryGetValue(UmbracoExamineIndex.PublishedFieldName, out var published))
return ValueSetValidationResult.Failed;
if (!published[0].Equals(1))
if (!published[0].Equals("y"))
return ValueSetValidationResult.Failed;
//deal with variants, if there are unpublished variants than we need to remove them from the value set
if (valueSet.Values.TryGetValue(UmbracoContentIndex.VariesByCultureFieldName, out var variesByCulture)
&& variesByCulture.Count > 0 && variesByCulture[0].Equals(1))
&& variesByCulture.Count > 0 && variesByCulture[0].Equals("y"))
{
//so this valueset is for a content that varies by culture, now check for non-published cultures and remove those values
foreach(var publishField in valueSet.Values.Where(x => x.Key.StartsWith($"{UmbracoExamineIndex.PublishedFieldName}_")).ToList())
{
if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals(1))
if (publishField.Value.Count <= 0 || !publishField.Value[0].Equals("y"))
{
//this culture is not published, so remove all of these culture values
var cultureSuffix = publishField.Key.Substring(publishField.Key.LastIndexOf('_'));

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Examine;
using Examine.LuceneEngine.Providers;
using Lucene.Net.Analysis;
@@ -7,6 +9,7 @@ using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Umbraco.Core;
using Version = Lucene.Net.Util.Version;
using Umbraco.Core.Logging;
@@ -15,9 +18,35 @@ namespace Umbraco.Examine
/// <summary>
/// Extension methods for the LuceneIndex
/// </summary>
internal static class ExamineExtensions
public static class ExamineExtensions
{
public static bool TryParseLuceneQuery(string query)
/// <summary>
/// Matches a culture iso name suffix
/// </summary>
/// <remarks>
/// myFieldName_en-us will match the "en-us"
/// </remarks>
internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled);
/// <summary>
/// Returns all index fields that are culture specific (suffixed)
/// </summary>
/// <param name="index"></param>
/// <param name="culture"></param>
/// <returns></returns>
public static IEnumerable<string> GetCultureFields(this IUmbracoIndex index, string culture)
{
var allFields = index.GetFields();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var field in allFields)
{
var match = CultureIsoCodeFieldNameMatchExpression.Match(field);
if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value))
yield return field;
}
}
internal static bool TryParseLuceneQuery(string query)
{
//TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll
// also do this rudimentary check

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Examine
{
{"icon", m.ContentType.Icon.Yield()},
{"id", new object[] {m.Id}},
{"key", new object[] {m.Key}},
{UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}},
{"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}},
{"level", new object[] {m.Level}},
{"creatorID", new object[] {m.CreatorId}},

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Examine
{
{"icon", m.ContentType.Icon.Yield()},
{"id", new object[] {m.Id}},
{"key", new object[] {m.Key}},
{UmbracoExamineIndex.NodeKeyFieldName, new object[] {m.Key}},
{"parentID", new object[] {m.Level > 1 ? m.ParentId : -1}},
{"level", new object[] {m.Level}},
{"creatorID", new object[] {m.CreatorId}},

View File

@@ -48,7 +48,7 @@
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="Examine" Version="1.0.0-beta072" />
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NPoco" Version="3.9.4" />
</ItemGroup>

View File

@@ -1,4 +1,7 @@
using Examine;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Examine;
using Umbraco.Core;
namespace Umbraco.Examine
{
@@ -30,7 +33,7 @@ namespace Umbraco.Examine
new FieldDefinition("createDate", FieldDefinitionTypes.DateTime),
new FieldDefinition("updateDate", FieldDefinitionTypes.DateTime),
new FieldDefinition("key", FieldDefinitionTypes.InvariantCultureIgnoreCase),
new FieldDefinition(UmbracoExamineIndex.NodeKeyFieldName, FieldDefinitionTypes.InvariantCultureIgnoreCase),
new FieldDefinition("version", FieldDefinitionTypes.Raw),
new FieldDefinition("nodeType", FieldDefinitionTypes.InvariantCultureIgnoreCase),
new FieldDefinition("template", FieldDefinitionTypes.Raw),
@@ -40,36 +43,54 @@ namespace Umbraco.Examine
new FieldDefinition("email", FieldDefinitionTypes.EmailAddress),
new FieldDefinition(UmbracoExamineIndex.PublishedFieldName, FieldDefinitionTypes.Raw),
new FieldDefinition(UmbracoExamineIndex.NodeKeyFieldName, FieldDefinitionTypes.Raw),
new FieldDefinition(UmbracoExamineIndex.IndexPathFieldName, FieldDefinitionTypes.Raw),
new FieldDefinition(UmbracoExamineIndex.IconFieldName, FieldDefinitionTypes.Raw)
new FieldDefinition(UmbracoExamineIndex.IconFieldName, FieldDefinitionTypes.Raw),
new FieldDefinition(UmbracoContentIndex.VariesByCultureFieldName, FieldDefinitionTypes.Raw),
};
///// <summary>
///// Overridden to dynamically add field definitions for culture variations
///// </summary>
///// <param name="fieldName"></param>
///// <param name="fieldDefinition"></param>
///// <returns></returns>
//public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition)
//{
// var result = base.TryGetValue(fieldName, out fieldDefinition);
// if (result) return true;
// //if the fieldName is not suffixed with _iso-Code
// var underscoreIndex = fieldName.LastIndexOf('_');
// if (underscoreIndex == -1) return false;
/// <summary>
/// Overridden to dynamically add field definitions for culture variations
/// </summary>
/// <param name="fieldName"></param>
/// <param name="fieldDefinition"></param>
/// <returns></returns>
/// <remarks>
/// We need to do this so that we don't have to maintain a huge static list of all field names and their definitions
/// otherwise we'd have to dynamically add/remove definitions anytime languages are added/removed, etc...
/// For example, we have things like `nodeName` and `__Published` which are also used for culture fields like `nodeName_en-us`
/// and we don't want to have a full static list of all of these definitions when we can just define the one definition and then
/// dynamically apply that to culture specific fields.
///
/// There is a caveat to this however, when a field definition is found for a non-culture field we will create and store a new field
/// definition for that culture so that the next time it needs to be looked up and used we are not allocating more objects. This does mean
/// however that if a language is deleted, the field definitions for that language will still exist in memory. This isn't going to cause any
/// problems and the mem will be cleared on next site restart but it's worth pointing out.
/// </remarks>
public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition)
{
if (base.TryGetValue(fieldName, out fieldDefinition))
return true;
//before we use regex to match do some faster simple matching since this is going to execute quite a lot
if (!fieldName.Contains("_") || !fieldName.Contains("-"))
return false;
var match = ExamineExtensions.CultureIsoCodeFieldNameMatchExpression.Match(fieldName);
if (match.Success && match.Groups.Count == 3)
{
var nonCultureFieldName = match.Groups[1].Value;
//check if there's a definition for this and if so return the field definition for the culture field based on the non-culture field
if (base.TryGetValue(nonCultureFieldName, out var existingFieldDefinition))
{
//now add a new field def
fieldDefinition = GetOrAdd(fieldName, s => new FieldDefinition(s, existingFieldDefinition.Type));
return true;
}
}
return false;
}
// var isoCode = fieldName.Substring(underscoreIndex);
// if (isoCode.Length < 6) return false; //invalid isoCode
// var hyphenIndex = isoCode.IndexOf('-');
// if (hyphenIndex != 3) return false; //invalid isoCode
// //we'll assume this is a valid isoCode
//}
}
}

View File

@@ -32,35 +32,6 @@ namespace Umbraco.Examine
base(name, luceneDirectory, fieldDefinitions, analyzer, profilingLogger, validator)
{
}
/// <summary>
/// Overridden to ensure that the umbraco system field definitions are in place
/// </summary>
/// <param name="indexValueTypesFactory"></param>
/// <returns></returns>
protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary<string, IFieldValueTypeFactory> indexValueTypesFactory = null)
{
var keyDef = new FieldDefinition("__key", FieldDefinitionTypes.Raw);
FieldDefinitionCollection.TryAdd(keyDef);
return base.CreateFieldValueTypes(indexValueTypesFactory);
}
/// <summary>
/// Ensure some custom values are added to the index
/// </summary>
/// <param name="e"></param>
protected override void OnTransformingIndexValues(IndexingItemEventArgs e)
{
base.OnTransformingIndexValues(e);
if (e.ValueSet.Values.TryGetValue("key", out var key) && e.ValueSet.Values.ContainsKey("__key") == false)
{
//double __ prefix means it will be indexed as culture invariant
e.ValueSet.Values["__key"] = key;
}
}
}
}

View File

@@ -201,7 +201,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
{"creatorName", "Shannon"}
};
var result = new SearchResult("1234", 1, 1, () => fields.ToDictionary(x => x.Key, x => new List<string> { x.Value }));
var result = new SearchResult("1234", 1, () => fields.ToDictionary(x => x.Key, x => new List<string> { x.Value }));
var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null), ServiceContext.MediaService, ServiceContext.UserService, new StaticCacheProvider(), ContentTypesCache);
var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result));

View File

@@ -194,8 +194,8 @@ namespace Umbraco.Tests.Composing
var register = GetRegister();
// define two instances
register.RegisterInstance(typeof(Thing1), new Thing1());
register.RegisterInstance(typeof(Thing1), new Thing2());
register.Register(typeof(Thing1), new Thing1());
register.Register(typeof(Thing1), new Thing2());
var factory = register.CreateFactory();
@@ -212,8 +212,8 @@ namespace Umbraco.Tests.Composing
var register = GetRegister();
// define two instances
register.RegisterInstance(typeof(IThing), new Thing1());
register.RegisterInstance(typeof(IThing), new Thing2());
register.Register(typeof(IThing), new Thing1());
register.Register(typeof(IThing), new Thing2());
var factory = register.CreateFactory();

View File

@@ -195,7 +195,7 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void PublishedContentQueryTypedContentList()
{
var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache);
var query = new PublishedContentQuery(UmbracoContext.Current.ContentCache, UmbracoContext.Current.MediaCache, UmbracoContext.Current.VariationContextAccessor);
var result = query.Content(new[] { 1, 2, 4 }).ToArray();
Assert.AreEqual(2, result.Length);
Assert.AreEqual(1, result[0].Id);

View File

@@ -69,8 +69,9 @@ namespace Umbraco.Tests.PublishedContent
factory.CreatePropertyType("testRecursive", 1),
};
var compositionAliases = new[] { "MyCompositionAlias" };
var type = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes);
ContentTypesCache.GetPublishedContentTypeByAlias = alias => type;
var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes);
var homeType = new AutoPublishedContentType(0, "home", compositionAliases, propertyTypes);
ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType;
}
protected override TypeLoader CreateTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger)
@@ -233,10 +234,10 @@ namespace Umbraco.Tests.PublishedContent
}
[Test]
[Ignore("Fails as long as PublishedContentModel is internal.")] // fixme
public void Is_Last_From_Where_Filter2()
{
var doc = GetNode(1173);
var ct = doc.ContentType;
var items = doc.Children
.Select(x => x.CreateModel()) // linq, returns IEnumerable<IPublishedContent>
@@ -455,11 +456,11 @@ namespace Umbraco.Tests.PublishedContent
{
var doc = GetNode(1046); // has child nodes
var model = doc.FirstChild<Anything>(x => true); // predicate
var model = doc.FirstChild<Home>(x => true); // predicate
Assert.IsNotNull(model);
Assert.IsTrue(model.Id == 1173);
Assert.IsInstanceOf<Anything>(model);
Assert.IsInstanceOf<Home>(model);
Assert.IsInstanceOf<IPublishedContent>(model);
doc = GetNode(1175); // does not have child nodes

View File

@@ -540,7 +540,7 @@ namespace Umbraco.Tests.Scoping
var scopeProvider = ScopeProvider;
bool? completed = null;
Exception exception = null;
bool? completed2 = null;
Assert.IsNull(scopeProvider.AmbientScope);
using (var scope = scopeProvider.CreateScope())
@@ -551,15 +551,7 @@ namespace Umbraco.Tests.Scoping
// at that point the scope is gone, but the context is still there
var ambientContext = scopeProvider.AmbientContext;
try
{
ambientContext.Enlist("another", c2 => { });
}
catch (Exception e)
{
exception = e;
}
ambientContext.Enlist("another", c2 => { completed2 = c2; });
});
if (complete)
scope.Complete();
@@ -567,8 +559,8 @@ namespace Umbraco.Tests.Scoping
Assert.IsNull(scopeProvider.AmbientScope);
Assert.IsNull(scopeProvider.AmbientContext);
Assert.IsNotNull(completed);
Assert.IsNotNull(exception);
Assert.IsInstanceOf<InvalidOperationException>(exception);
Assert.AreEqual(complete,completed.Value);
Assert.AreEqual(complete, completed2.Value);
}
[Test]

View File

@@ -22,6 +22,36 @@ namespace Umbraco.Tests.Services
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
public class ContentTypeServiceTests : TestWithSomeContentBase
{
[Test]
public void CanSaveAndGetIsElement()
{
//create content type with a property type that varies by culture
IContentType contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var contentCollection = new PropertyTypeCollection(true);
contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext)
{
Alias = "title",
Name = "Title",
Description = "",
Mandatory = false,
SortOrder = 1,
DataTypeId = -88,
Variations = ContentVariation.Nothing
});
contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 });
ServiceContext.ContentTypeService.Save(contentType);
contentType = ServiceContext.ContentTypeService.Get(contentType.Id);
Assert.IsFalse(contentType.IsElement);
contentType.IsElement = true;
ServiceContext.ContentTypeService.Save(contentType);
contentType = ServiceContext.ContentTypeService.Get(contentType.Id);
Assert.IsTrue(contentType.IsElement);
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
{

View File

@@ -77,7 +77,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.1" />
<PackageReference Include="Castle.Core" Version="4.2.1" />
<PackageReference Include="Examine" Version="1.0.0-beta072" />
<PackageReference Include="Examine" Version="1.0.0-beta078" />
<PackageReference Include="HtmlAgilityPack">
<Version>1.8.9</Version>
</PackageReference>

View File

@@ -179,7 +179,7 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoExamineIndex.PublishedFieldName] = 1
[UmbracoExamineIndex.PublishedFieldName] = "y"
}));
Assert.AreEqual(ValueSetValidationResult.Valid, result);
}
@@ -213,7 +213,7 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoExamineIndex.PublishedFieldName] = 0
[UmbracoExamineIndex.PublishedFieldName] = "n"
}));
Assert.AreEqual(ValueSetValidationResult.Failed, result);
@@ -222,7 +222,7 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoExamineIndex.PublishedFieldName] = 1
[UmbracoExamineIndex.PublishedFieldName] = "y"
}));
Assert.AreEqual(ValueSetValidationResult.Valid, result);
}
@@ -237,8 +237,8 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoContentIndex.VariesByCultureFieldName] = 1,
[UmbracoExamineIndex.PublishedFieldName] = 0
[UmbracoContentIndex.VariesByCultureFieldName] = "y",
[UmbracoExamineIndex.PublishedFieldName] = "n"
}));
Assert.AreEqual(ValueSetValidationResult.Failed, result);
@@ -247,8 +247,8 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoContentIndex.VariesByCultureFieldName] = 1,
[UmbracoExamineIndex.PublishedFieldName] = 1
[UmbracoContentIndex.VariesByCultureFieldName] = "y",
[UmbracoExamineIndex.PublishedFieldName] = "y"
}));
Assert.AreEqual(ValueSetValidationResult.Valid, result);
@@ -257,14 +257,14 @@ namespace Umbraco.Tests.UmbracoExamine
{
["hello"] = "world",
["path"] = "-1,555",
[UmbracoContentIndex.VariesByCultureFieldName] = 1,
[$"{UmbracoExamineIndex.PublishedFieldName}_en-us"] = 1,
[UmbracoContentIndex.VariesByCultureFieldName] = "y",
[$"{UmbracoExamineIndex.PublishedFieldName}_en-us"] = "y",
["hello_en-us"] = "world",
["title_en-us"] = "my title",
[$"{UmbracoExamineIndex.PublishedFieldName}_es-es"] = 0,
[$"{UmbracoExamineIndex.PublishedFieldName}_es-es"] = "n",
["hello_es-ES"] = "world",
["title_es-ES"] = "my title",
[UmbracoExamineIndex.PublishedFieldName] = 1
[UmbracoExamineIndex.PublishedFieldName] = "y"
});
Assert.AreEqual(10, valueSet.Values.Count());
Assert.IsTrue(valueSet.Values.ContainsKey($"{UmbracoExamineIndex.PublishedFieldName}_es-es"));

View File

@@ -29,5 +29,30 @@ namespace Umbraco.Tests.Web.Mvc
var output = _htmlHelper.Wrap("div", "hello world", new {style = "color:red;", onclick = "void();"});
Assert.AreEqual("<div style=\"color:red;\" onclick=\"void();\">hello world</div>", output.ToHtmlString());
}
[Test]
public void GetRelatedLinkHtml_Simple()
{
var relatedLink = new Umbraco.Web.Models.RelatedLink {
Caption = "Link Caption",
NewWindow = true,
Link = "https://www.google.com/"
};
var output = _htmlHelper.GetRelatedLinkHtml(relatedLink);
Assert.AreEqual("<a href=\"https://www.google.com/\" target=\"_blank\">Link Caption</a>", output.ToHtmlString());
}
[Test]
public void GetRelatedLinkHtml_HtmlAttributes()
{
var relatedLink = new Umbraco.Web.Models.RelatedLink
{
Caption = "Link Caption",
NewWindow = true,
Link = "https://www.google.com/"
};
var output = _htmlHelper.GetRelatedLinkHtml(relatedLink, new { @class = "test-class"});
Assert.AreEqual("<a class=\"test-class\" href=\"https://www.google.com/\" target=\"_blank\">Link Caption</a>", output.ToHtmlString());
}
}
}

View File

@@ -248,6 +248,17 @@ gulp.task('dependencies', function () {
"src": ["./node_modules/bootstrap-social/bootstrap-social.css"],
"base": "./node_modules/bootstrap-social"
},
{
"name": "angular-chart.js",
"src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"],
"base": "./node_modules/angular-chart.js/dist"
},
{
"name": "chart.js",
"src": ["./node_modules/chart.js/dist/chart.min.js"],
"base": "./node_modules/chart.js/dist"
},
{
"name": "clipboard",
"src": ["./node_modules/clipboard/dist/clipboard.min.js"],

View File

@@ -852,6 +852,26 @@
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz",
"integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ=="
},
"angular-chart.js": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz",
"integrity": "sha1-SfDhjQgXYrbUyXkeSHr/L7sw9a4=",
"requires": {
"angular": "1.x",
"chart.js": "2.3.x"
},
"dependencies": {
"chart.js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.3.0.tgz",
"integrity": "sha1-QEYOSOLEF8BfwzJc2E97AA3H19Y=",
"requires": {
"chartjs-color": "^2.0.0",
"moment": "^2.10.6"
}
}
}
},
"angular-cookies": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz",
@@ -1970,6 +1990,39 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"chart.js": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
"integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
"requires": {
"chartjs-color-string": "^0.5.0",
"color-convert": "^0.5.3"
},
"dependencies": {
"color-convert": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
}
}
},
"chartjs-color-string": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
"requires": {
"color-name": "^1.0.0"
}
},
"chokidar": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
@@ -2216,8 +2269,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.3",

View File

@@ -8,6 +8,7 @@
"ace-builds": "1.4.2",
"angular": "1.7.5",
"angular-animate": "1.7.5",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
"angular-i18n": "1.7.5",
@@ -20,6 +21,7 @@
"angular-ui-sortable": "0.19.0",
"animejs": "2.2.0",
"bootstrap-social": "5.1.1",
"chart.js": "^2.7.3",
"clipboard": "2.0.4",
"diff": "3.5.0",
"flatpickr": "4.5.2",

View File

@@ -14,7 +14,8 @@ var app = angular.module('umbraco', [
'ngMessages',
'tmh.dynamicLocale',
'ngFileUpload',
'LocalStorageModule'
'LocalStorageModule',
'chart.js'
]);
app.config(['$compileProvider', function ($compileProvider) {
@@ -76,7 +77,7 @@ angular.module("umbraco.viewcache", [])
var _op = (url.indexOf("?") > 0) ? "&" : "?";
url += _op + "umb__rnd=" + rnd;
}
return get(url, config);
};
return $delegate;

View File

@@ -54,7 +54,6 @@
</pre>
@param {boolean} checked Set to <code>true</code> or <code>false</code> to toggle the switch.
@param {boolean} disabled Set to <code>true</code> or <code>false</code> to disable the switch.
@param {callback} onClick The function which should be called when the toggle is clicked.
@param {string=} showLabels Set to <code>true</code> or <code>false</code> to show a "On" or "Off" label next to the switch.
@param {string=} labelOn Set a custom label for when the switched is turned on. It will default to "On".

View File

@@ -97,9 +97,9 @@
}
//load in the audit trail if we are currently looking at the INFO tab
if (umbVariantContentCtrl) {
if (umbVariantContentCtrl && umbVariantContentCtrl.editor) {
var activeApp = _.find(umbVariantContentCtrl.editor.content.apps, a => a.active);
if (activeApp.alias === "umbInfo") {
if (activeApp && activeApp.alias === "umbInfo") {
isInfoTab = true;
loadAuditTrail();
loadRedirectUrls();

View File

@@ -130,8 +130,7 @@
if (!changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) {
configureViewModel();
//this is required to re-validate
vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length);
reValidate()
}
}
@@ -182,6 +181,8 @@
else {
vm.onValueChanged({ value: [] });
}
reValidate();
}
/**
@@ -189,7 +190,7 @@
*/
function validateMandatory() {
return {
isValid: !vm.validation.mandatory || (vm.viewModel != null && vm.viewModel.length > 0),
isValid: !vm.validation.mandatory || (vm.viewModel != null && vm.viewModel.length > 0)|| (vm.value != null && vm.value.length > 0),
errorMsg: "Value cannot be empty",
errorKey: "required"
};
@@ -271,6 +272,10 @@
});
}
function reValidate() {
//this is required to re-validate
vm.tagEditorForm.tagCount.$setViewValue(vm.viewModel.length);
}
}

View File

@@ -86,20 +86,21 @@ angular.module("umbraco.directives")
function generateAlias(value) {
if (generateAliasTimeout) {
$timeout.cancel(generateAliasTimeout);
$timeout.cancel(generateAliasTimeout);
}
if( value !== undefined && value !== "" && value !== null) {
if (value !== undefined && value !== "" && value !== null) {
scope.alias = "";
scope.alias = "";
scope.placeholderText = scope.labels.busy;
generateAliasTimeout = $timeout(function () {
updateAlias = true;
entityResource.getSafeAlias(value, true).then(function (safeAlias) {
if (updateAlias) {
scope.alias = safeAlias.alias;
}
scope.alias = safeAlias.alias;
}
scope.placeholderText = scope.labels.idle;
});
}, 500);
@@ -108,7 +109,6 @@ angular.module("umbraco.directives")
scope.alias = "";
scope.placeholderText = scope.labels.idle;
}
}
// if alias gets unlocked - stop watching alias
@@ -119,17 +119,17 @@ angular.module("umbraco.directives")
}));
// validate custom entered alias
eventBindings.push(scope.$watch('alias', function(newValue, oldValue){
if(scope.alias === "" && bindWatcher === true || scope.alias === null && bindWatcher === true) {
// add watcher
eventBindings.push(scope.$watch('aliasFrom', function(newValue, oldValue) {
if(bindWatcher) {
generateAlias(newValue);
}
}));
}
eventBindings.push(scope.$watch('alias', function (newValue, oldValue) {
if (scope.alias === "" || scope.alias === null || scope.alias === undefined) {
if (bindWatcher === true) {
// add watcher
eventBindings.push(scope.$watch('aliasFrom', function (newValue, oldValue) {
if (bindWatcher) {
generateAlias(newValue);
}
}));
}
}
}));
// clean up

View File

@@ -3,7 +3,7 @@
* @name umbraco.resources.codefileResource
* @description Loads in data for files that contain code such as js scripts, partial views and partial view macros
**/
function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) {
function codefileResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) {
return {
@@ -106,13 +106,16 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
*/
deleteByPath: function (type, virtualpath) {
var promise = localizationService.localize("codefile_deleteItemFailed", [virtualpath]);
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"Delete",
[{ type: type }, { virtualPath: virtualpath}])),
"Failed to delete item: " + virtualpath);
promise);
},
/**
@@ -236,13 +239,19 @@ function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
*/
createContainer: function(type, parentId, name) {
createContainer: function (type, parentId, name) {
// Is the parent ID numeric?
var key = "codefile_createFolderFailedBy" + (isNaN(parseInt(parentId)) ? "Name" : "Id");
var promise = localizationService.localize(key, [parentId]);
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"PostCreateContainer",
{ type: type, parentId: parentId, name: encodeURIComponent(name) })),
'Failed to create a folder under parent id ' + parentId);
promise);
},
/**

View File

@@ -3,7 +3,7 @@
* @name umbraco.resources.templateResource
* @description Loads in data for templates
**/
function templateResource($q, $http, umbDataFormatter, umbRequestHelper) {
function templateResource($q, $http, umbDataFormatter, umbRequestHelper, localizationService) {
return {
@@ -152,13 +152,16 @@ function templateResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
*/
deleteById: function(id) {
var promise = localizationService.localize("template_deleteByIdFailed", [id]);
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"templateApiBaseUrl",
"DeleteById",
[{ id: id }])),
"Failed to delete item " + id);
promise);
},
/**

View File

@@ -162,7 +162,7 @@ When building a custom infinite editor view you can use the same components as a
(function () {
"use strict";
function editorService(eventsService, keyboardService) {
function editorService(eventsService, keyboardService, $timeout) {
let editorsKeyboardShorcuts = [];
var editors = [];
@@ -245,10 +245,14 @@ When building a custom infinite editor view you can use the same components as a
// emit event to let components know an editor has been removed
eventsService.emit("appState.editors.close", args);
// rebind keyboard shortcuts for the new editor in focus
rebindKeyboardShortcuts();
// delay required to map the properties to the correct editor due
// to another delay in the closing animation of the editor
$timeout(function() {
// rebind keyboard shortcuts for the new editor in focus
rebindKeyboardShortcuts();
}, 0);
}
/**

View File

@@ -64,7 +64,7 @@
var saveModel = _.pick(displayModel,
'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes',
'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed',
'key', 'parentId', 'alias', 'path', 'allowCultureVariant');
'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'isElement');
//TODO: Map these
saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; });
@@ -262,7 +262,7 @@
saveModel[props[m]] = startId.id;
}
saveModel.parentId = -1;
saveModel.parentId = -1;
return saveModel;
},
@@ -293,7 +293,7 @@
});
saveModel.email = propEmail.value.trim();
saveModel.username = propLogin.value.trim();
saveModel.password = this.formatChangePasswordModel(propPass.value);
var selectedGroups = [];
@@ -336,7 +336,7 @@
/** formats the display model used to display the media to the model used to save the media */
formatMediaPostData: function (displayModel, action) {
//NOTE: the display model inherits from the save model so we can in theory just post up the display model but
//NOTE: the display model inherits from the save model so we can in theory just post up the display model but
// we don't want to post all of the data as it is unecessary.
var saveModel = {
id: displayModel.id,
@@ -354,7 +354,7 @@
/** formats the display model used to display the content to the model used to save the content */
formatContentPostData: function (displayModel, action) {
//NOTE: the display model inherits from the save model so we can in theory just post up the display model but
//NOTE: the display model inherits from the save model so we can in theory just post up the display model but
// we don't want to post all of the data as it is unecessary.
var saveModel = {
id: displayModel.id,
@@ -379,7 +379,7 @@
var propExpireDate = displayModel.removeDate;
var propReleaseDate = displayModel.releaseDate;
var propTemplate = displayModel.template;
saveModel.expireDate = propExpireDate ? propExpireDate : null;
saveModel.releaseDate = propReleaseDate ? propReleaseDate : null;
saveModel.templateAlias = propTemplate ? propTemplate : null;
@@ -389,8 +389,8 @@
/**
* This formats the server GET response for a content display item
* @param {} displayModel
* @returns {}
* @param {} displayModel
* @returns {}
*/
formatContentGetData: function(displayModel) {
@@ -418,7 +418,7 @@
}
});
});
//now assign this same invariant property instance to the same index of the other variants property array
for (var j = 1; j < displayModel.variants.length; j++) {

View File

@@ -9,7 +9,7 @@
*
* @param {navigationService} navigationService A reference to the navigationService
*/
function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) {
function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource) {
//this is used to trigger the tree to start loading once everything is ready
var treeInitPromise = $q.defer();
@@ -344,9 +344,6 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
$scope.languages = languages;
if ($scope.languages.length > 1) {
var defaultLang = _.find($scope.languages, function (l) {
return l.isDefault;
});
//if there's already one set, check if it exists
var currCulture = null;
var mainCulture = $location.search().mculture;
@@ -356,7 +353,20 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
});
}
if (!currCulture) {
$location.search("mculture", defaultLang ? defaultLang.culture : null);
// no culture in the request, let's look for one in the cookie that's set when changing language
var defaultCulture = $cookies.get("UMB_MCULTURE");
if (!defaultCulture || !_.find($scope.languages, function (l) {
return l.culture.toLowerCase() === defaultCulture.toLowerCase();
})) {
// no luck either, look for the default language
var defaultLang = _.find($scope.languages, function (l) {
return l.isDefault;
});
if (defaultLang) {
defaultCulture = defaultLang.culture;
}
}
$location.search("mculture", defaultCulture ? defaultCulture : null);
}
}
@@ -391,6 +401,10 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
$scope.selectLanguage = function (language) {
$location.search("mculture", language.culture);
// add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year)
var expireDate = new Date();
expireDate.setDate(expireDate.getDate() + 365);
$cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate});
// close the language selector
$scope.page.languageSelectorIsOpen = false;

View File

@@ -161,6 +161,7 @@
@import "components/umb-file-dropzone.less";
@import "components/umb-node-preview.less";
@import "components/umb-mini-editor.less";
@import "components/umb-property-file-upload.less";
@import "components/users/umb-user-cards.less";
@import "components/users/umb-user-details.less";

View File

@@ -1,5 +1,5 @@
.umb-file-dropzone-directive{
.umb-file-dropzone {
// drop zone
// tall and small version - animate height

View File

@@ -3,6 +3,13 @@
position: relative;
}
.umb-nested-content-property-container {
position: relative;
&:not(:last-child){
margin-bottom: 12px;
}
}
.umb-nested-content--not-supported {
opacity: 0.3;
pointer-events: none;

View File

@@ -0,0 +1,28 @@
.umb-property-file-upload {
.umb-upload-button-big {
display: block;
padding: 20px;
opacity: 1;
border: 1px dashed @gray-8;
background: none;
text-align: center;
font-size: 14px;
&, &:hover {
color: @gray-8;
}
i.icon {
font-size: 55px;
line-height: 70px
}
input {
left: 0;
bottom: 0;
height: 100%;
width: 100%;
}
}
}

View File

@@ -1,3 +1,5 @@
@checkered-background: url(../img/checkered-background.png);
//
// Container styles
// --------------------------------------------------
@@ -353,7 +355,7 @@
max-height:100%;
margin:auto;
display:block;
background-image: url(../img/checkered-background.png);
background-image: @checkered-background;
}
.umb-sortable-thumbnails li .trashed {
@@ -576,12 +578,18 @@
vertical-align: top;
}
.gravity-container .viewport {
max-width: 600px;
}
.gravity-container {
border: 1px solid @gray-8;
line-height: 0;
.gravity-container .viewport:hover {
cursor: pointer;
.viewport {
max-width: 600px;
background: @checkered-background;
&:hover {
cursor: pointer;
}
}
}
.imagecropper {
@@ -594,6 +602,10 @@
float: left;
max-width: 100%;
}
.viewport img {
background: @checkered-background;
}
}
.imagecropper .umb-cropper__container {
@@ -687,7 +699,7 @@
//
// folder-browser
// --------------------------------------------------
.umb-folderbrowser .add-link{
.umb-folderbrowser .add-link {
display: inline-block;
height: 120px;
width: 120px;
@@ -696,17 +708,6 @@
line-height: 120px
}
.umb-upload-button-big:hover{color: @gray-8;}
.umb-upload-button-big {display: block}
.umb-upload-button-big input {
left: 0;
bottom: 0;
height: 100%;
width: 100%;
}
//
// File upload
// --------------------------------------------------
@@ -724,6 +725,10 @@
list-style: none;
vertical-align: middle;
margin-bottom: 0;
img {
background: @checkered-background;
}
}
.umb-fileupload label {

View File

@@ -110,6 +110,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -75,6 +75,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -129,6 +129,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>
@@ -138,4 +139,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -52,6 +52,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -68,4 +69,4 @@
</form>
</umb-editor-view>
</div>
</div>

View File

@@ -54,6 +54,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -68,6 +68,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>
@@ -75,4 +76,4 @@
</umb-editor-view>
</form>
</form>

View File

@@ -47,6 +47,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>

View File

@@ -83,6 +83,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -44,6 +44,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>

View File

@@ -120,6 +120,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -171,6 +171,7 @@
<umb-button
action="close()"
button-style="link"
shortcut="esc"
label="Close"
type="button">
</umb-button>

View File

@@ -32,6 +32,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -67,6 +67,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -37,6 +37,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>

View File

@@ -155,6 +155,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>

View File

@@ -193,6 +193,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
@@ -209,4 +210,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -86,6 +86,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -101,4 +102,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -38,6 +38,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -51,4 +52,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -87,6 +87,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button

View File

@@ -90,6 +90,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>

View File

@@ -84,6 +84,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -97,4 +98,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -74,6 +74,7 @@
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -87,4 +88,4 @@
</umb-editor-view>
</div>
</div>

View File

@@ -24,7 +24,7 @@
<!-- Icon for files -->
<span class="umb-media-grid__item-file-icon" ng-if="!item.thumbnail && item.extension != 'svg'">
<i class="umb-media-grid__item-icon {{item.icon}}"></i>
<span>.{{item.extension}}</span>
<span ng-if="item.extension">.{{item.extension}}</span>
</span>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div data-element="dropzone" class="umb-file-dropzone-directive">
<div data-element="dropzone" class="umb-file-dropzone">
<ng-form name="uploadForm" umb-isolate-form>

View File

@@ -1,4 +1,5 @@
<div>
<div class="umb-property-file-upload">
<ng-form name="vm.fileUploadForm">
<div class="fileinput-button umb-upload-button-big"

View File

@@ -1,26 +1,91 @@
angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController",
function ($scope, relationResource, contentResource, entityResource, navigationService, appState, treeService, localizationService) {
function ($scope, relationResource, contentResource, entityResource, navigationService, appState, treeService, userService) {
$scope.source = _.clone($scope.currentNode);
$scope.error = null;
$scope.success = false;
$scope.error = null;
$scope.loading = true;
$scope.moving = false;
$scope.success = false;
$scope.dialogTreeApi = {};
$scope.searchInfo = {
showSearch: false,
results: [],
selectedSearchResults: []
}
$scope.treeModel = {
hideHeader: false
}
userService.getCurrentUser().then(function (userData) {
$scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
});
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
if ($scope.target) {
//un-select if there's a current one selected
$scope.target.selected = false;
}
$scope.target = args.node;
$scope.target.selected = true;
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.results = [];
}
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, { event: evt, node: result });
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
$scope.onTreeInit = function () {
$scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
$scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
}
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, { node: node });
};
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
function openMiniListView(node) {
$scope.miniListView = node;
}
relationResource.getByChildId($scope.source.id, "relateParentDocumentOnDelete").then(function (data) {
$scope.loading = false;
if (!data.length) {
localizationService.localizeMany(["recycleBin_itemCannotBeRestored", "recycleBin_noRestoreRelation"])
.then(function(values) {
$scope.success = false;
$scope.error = {
errorMsg: values[0],
data: {
Message: values[1]
}
}
});
$scope.moving = true;
return;
}
@@ -30,40 +95,32 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController"
$scope.target = { id: -1, name: "Root" };
} else {
$scope.loading = true;
$scope.loading = true;
entityResource.getById($scope.relation.parentId, "Document").then(function (data) {
$scope.loading = false;
$scope.target = data;
// make sure the target item isn't in the recycle bin
if($scope.target.path.indexOf("-20") !== -1) {
localizationService.localizeMany(["recycleBin_itemCannotBeRestored", "recycleBin_restoreUnderRecycled"])
.then(function (values) {
$scope.success = false;
$scope.error = {
errorMsg: values[0],
data: {
Message: values[1].replace('%0%', $scope.target.name)
}
}
});
$scope.success = false;
}
$scope.target = data;
// make sure the target item isn't in the recycle bin
if ($scope.target.path.indexOf("-20") !== -1) {
$scope.moving = true;
$scope.target = null;
}
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.loading = false;
$scope.error = err;
});
}
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.loading = false;
$scope.error = err;
});
$scope.restore = function () {
$scope.loading = true;
// this code was copied from `content.move.controller.js`
// this code was copied from `content.move.controller.js`
contentResource.move({ parentId: $scope.target.id, id: $scope.source.id })
.then(function (path) {
@@ -88,9 +145,8 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController"
});
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.loading = false;
$scope.error = err;
});
};

View File

@@ -1,34 +1,93 @@
<div ng-controller="Umbraco.Editors.Content.RestoreController">
<div class="umb-dialog-body">
<umb-pane>
<div class="umb-dialog-body" ng-cloak>
<umb-pane>
<umb-load-indicator
ng-show="loading">
</umb-load-indicator>
<umb-load-indicator
ng-show="loading">
</umb-load-indicator>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.Message}}</div>
</div>
</div>
<p class="abstract" ng-hide="loading || error != null || success">
<localize key="actions_restore">Restore</localize> <strong>{{source.name}}</strong> <localize key="general_under">under</localize> <strong>{{target.name}}</strong>?
</p>
<div ng-show="success">
<div class="alert alert-success">
<strong>{{source.name}}</strong>
<span ng-hide="moving"><localize key="recycleBin_wasRestored">was restored under</localize></span>
<span ng-show="moving"><localize key="editdatatype_wasMoved">was moved underneath</localize></span>
<strong>{{target.name}}</strong>
</div>
<button class="btn btn-primary" ng-click="close()">Ok</button>
</div>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.Message}}</div>
</div>
</div>
<div ng-hide="moving || loading || success">
<div ng-show="success">
<div class="alert alert-success">
<strong>{{source.name}}</strong> <localize key="editdatatype_wasMoved">was moved underneath</localize> <strong>{{target.name}}</strong>
</div>
<button class="btn btn-primary" ng-click="close()"><localize key="general_ok">Ok</localize></button>
</div>
<p class="abstract" ng-hide="error || success">
<localize key="actions_restore">Restore</localize> <strong>{{source.name}}</strong> <localize key="general_under">under</localize> <strong>{{target.name}}</strong>?
</p>
</umb-pane>
</div>
<div ng-hide="!moving || loading || success">
<div>
<div class="alert alert-info">
<div><strong><localize key="recycleBin_itemCannotBeRestored">Cannot automatically restore this item</localize></strong></div>
<div><localize key="recycleBin_itemCannotBeRestoredHelpText">There is no location where this item can be automatically restored. You can move the item manually using the tree below.</localize></div>
</div>
</div>
<div ng-hide="miniListView">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
show-search="{{searchInfo.showSearch}}"
section="content">
</umb-tree-search-box>
<br />
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div ng-hide="searchInfo.showSearch">
<umb-tree
section="content"
hideheader="{{treeModel.hideHeader}}"
hideoptions="true"
isdialog="true"
api="dialogTreeApi"
on-init="onTreeInit()"
enablelistviewexpand="true"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="Document"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
</umb-pane>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null"><localize key="actions_restore">Restore</localize></button>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || moving || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null"><localize key="actions_restore">Restore</localize></button>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || !moving || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null" ng-disabled="!target"><localize key="actions_move">Move</localize></button>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More