Compare commits

..

2 Commits

Author SHA1 Message Date
Niels Lyngsø 992cc48f56 added missing $location reference 2019-02-18 15:49:43 +01:00
Niels Lyngsø 897d6f2dde List & Grid should only open items as infinity editing if entityType is Content. 2019-02-18 15:38:39 +01:00
182 changed files with 1504 additions and 2154 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.1.0">
<metadata minClientVersion="3.4.4">
<id>UmbracoCms.Core</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.1.0">
<metadata minClientVersion="3.4.4">
<id>UmbracoCms.Web</id>
<version>8.0.0</version>
<title>Umbraco Cms Core Binaries</title>
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="4.1.0">
<metadata minClientVersion="3.4.4">
<id>UmbracoCms</id>
<version>8.0.0</version>
<title>Umbraco Cms</title>
@@ -25,7 +25,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.4.0, 2.999999)" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.4]" />
<dependency id="Umbraco.ModelsBuilder.Ui" version="[8.0.0-alpha.38]" />
<dependency id="ImageProcessor.Web" version="[4.10.0.100,4.999999)" />
<dependency id="ImageProcessor.Web.Config" version="[2.5.0.100,2.999999)" />
<dependency id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="[2.0.1,2.999999)" />
+36 -46
View File
@@ -126,55 +126,45 @@
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Microsoft.CodeAnalysis.CSharp')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='log4net')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Data.SqlServerCe')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.ValueTuple')" xdt:Transform="Remove" />
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http.Formatting')" xdt:Transform="Remove" />
</assemblyBinding>
</runtime>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.3.0" newVersion="1.2.3.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.web.webPages.razor xdt:Transform="Remove" />
+93
View File
@@ -0,0 +1,93 @@
param($installPath, $toolsPath, $package, $project)
Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host " "
if ($project) {
$dateTime = Get-Date -Format yyyyMMdd-HHmmss
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
Write-Host "copyLogsPath:" "${copyLogsPath}"
$umbracoBinFolder = Join-Path $projectPath "bin"
Write-Host "umbracoBinFolder:" "${umbracoBinFolder}"
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project
# See: http://issues.umbraco.org/issue/U4-4930
if(Test-Path $umbracoBinFolder) {
$umbracoBinBackupPath = Join-Path $backupPath "bin"
New-Item -ItemType Directory -Force -Path $umbracoBinBackupPath
robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log
# Delete files Umbraco ships with
if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Examine.dll) { Remove-Item $umbracoBinFolder\Umbraco.Examine.dll -Force -Confirm:$false }
# Delete files Umbraco depends upon
$amd64Folder = Join-Path $umbracoBinFolder "amd64"
if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false }
$x86Folder = Join-Path $umbracoBinFolder "x86"
if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ClientDependency.Core.Mvc.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.Mvc.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\CookComputing.XmlRpcV2.dll) { Remove-Item $umbracoBinFolder\CookComputing.XmlRpcV2.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Examine.dll) { Remove-Item $umbracoBinFolder\Examine.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\HtmlAgilityPack.dll) { Remove-Item $umbracoBinFolder\HtmlAgilityPack.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ICSharpCode.SharpZipLib.dll) { Remove-Item $umbracoBinFolder\ICSharpCode.SharpZipLib.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.Cookies.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.OAuth.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Infrastructure.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Infrastructure.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll) { Remove-Item $umbracoBinFolder\Microsoft.Web.Mvc.FixedDisplayModes.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\MiniProfiler.dll) { Remove-Item $umbracoBinFolder\MiniProfiler.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Helpers.dll) { Remove-Item $umbracoBinFolder\System.Web.Helpers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Http.WebHost.dll) { Remove-Item $umbracoBinFolder\System.Web.Http.WebHost.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Mvc.dll) { Remove-Item $umbracoBinFolder\System.Web.Mvc.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false }
}
}
+27
View File
@@ -11,15 +11,37 @@ if ($project) {
# Create paths and list them
$projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
Write-Host "projectPath:" "${projectPath}"
$backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
Write-Host "copyLogsPath:" "${copyLogsPath}"
$webConfigSource = Join-Path $projectPath "Web.config"
Write-Host "webConfigSource:" "${webConfigSource}"
$configFolder = Join-Path $projectPath "Config"
Write-Host "configFolder:" "${configFolder}"
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# Create a backup of original web.config
Copy-Item $webConfigSource $backupPath -Force
# Backup config files folder
if(Test-Path $configFolder) {
$umbracoBackupPath = Join-Path $backupPath "Config"
New-Item -ItemType Directory -Force -Path $umbracoBackupPath
robocopy $configFolder $umbracoBackupPath /e /LOG:$copyLogsPath\ConfigBackup.log
}
# Copy umbraco and umbraco_files from package to project folder
$umbracoFolder = Join-Path $projectPath "Umbraco"
New-Item -ItemType Directory -Force -Path $umbracoFolder
$umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco"
$umbracoBackupPath = Join-Path $backupPath "Umbraco"
New-Item -ItemType Directory -Force -Path $umbracoBackupPath
robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log
robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
$copyWebconfig = $true
@@ -78,6 +100,11 @@ if ($project) {
}
}
$installFolder = Join-Path $projectPath "Install"
if(Test-Path $installFolder) {
Remove-Item $installFolder -Force -Recurse -Confirm:$false
}
# Open appropriate readme
if($copyWebconfig -eq $true)
{
+2 -2
View File
@@ -18,5 +18,5 @@ using System.Resources;
[assembly: AssemblyVersion("8.0.0")]
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.1")]
[assembly: AssemblyInformationalVersion("8.0.1")]
[assembly: AssemblyFileVersion("8.0.0")]
[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")]
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Composing
protected override IComponent CreateItem(IFactory factory, Type itemType)
{
using (_logger.DebugDuration<ComponentCollectionBuilder>($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
using (_logger.DebugDuration<Composers>($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
{
return base.CreateItem(factory, itemType);
}
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
composition.RegisterUnique<IFileSystems>(factory => factory.GetInstance<IO.FileSystems>());
// register the scheme for media paths
composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
composition.RegisterUnique<IMediaPathScheme, TwoGuidsMediaPathScheme>();
// register the IMediaFileSystem implementation
composition.RegisterFileSystem<IMediaFileSystem, MediaFileSystem>();
+2 -1
View File
@@ -8,6 +8,7 @@
/// <summary>
/// Compose.
/// </summary>
/// <param name="composition"></param>
void Compose(Composition composition);
}
}
}
+4 -2
View File
@@ -4,8 +4,10 @@
/// Represents a core <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>Core composers compose after the initial composer, and before user composers.</para>
/// <para>All core composers are required by (compose before) all user composers,
/// and require (compose after) all runtime composers.</para>
/// </remarks>
[ComposeAfter(typeof(IRuntimeComposer))]
public interface ICoreComposer : IComposer
{ }
}
}
@@ -0,0 +1,11 @@
namespace Umbraco.Core.Composing
{
/// <summary>
/// Represents a runtime <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>All runtime composers are required by (compose before) all core composers</para>
/// </remarks>
public interface IRuntimeComposer : IComposer
{ }
}
+2 -2
View File
@@ -4,9 +4,9 @@
/// Represents a user <see cref="IComposer"/>.
/// </summary>
/// <remarks>
/// <para>User composers compose after core composers, and before the final composer.</para>
/// <para>All user composers require (compose after) all core composers.</para>
/// </remarks>
[ComposeAfter(typeof(ICoreComposer))]
public interface IUserComposer : IComposer
{ }
}
}
@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a base class for collections of types.
/// </summary>
public abstract class TypeCollectionBuilderBase<TCollection, TConstraint> : ICollectionBuilder<TCollection, Type>
where TCollection : class, IBuilderCollection<Type>
{
private readonly HashSet<Type> _types = new HashSet<Type>();
private Type Validate(Type type, string action)
{
if (!typeof(TConstraint).IsAssignableFrom(type))
throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}.");
return type;
}
public void Add(Type type) => _types.Add(Validate(type, "add"));
public void Add<T>() => Add(typeof(T));
public void Add(IEnumerable<Type> types)
{
foreach (var type in types) Add(type);
}
public void Remove(Type type) => _types.Remove(Validate(type, "remove"));
public void Remove<T>() => Remove(typeof(T));
public TCollection CreateCollection(IFactory factory)
{
return factory.CreateInstance<TCollection>(_types);
}
public void RegisterWith(IRegister register)
{
register.Register(CreateCollection, Lifetime.Singleton);
}
}
}
@@ -18,6 +18,46 @@ namespace Umbraco.Core
/// </summary>
public static partial class CompositionExtensions
{
#region FileSystems
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
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>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
#endregion
#region Collection Builders
/// <summary>
@@ -10,6 +10,8 @@ namespace Umbraco.Core
/// </summary>
public static partial class CompositionExtensions
{
#region Essentials
/// <summary>
/// Registers essential services.
/// </summary>
@@ -31,5 +33,36 @@ namespace Umbraco.Core
composition.RegisterUnique(typeLoader);
composition.RegisterUnique(state);
}
#endregion
#region Unique
/// <summary>
/// Registers a unique service as its own implementation.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TService));
/// <summary>
/// Registers a unique service with an implementation type.
/// </summary>
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>
public static void RegisterUnique<TService>(this Composition composition, TService instance)
=> composition.RegisterUnique(typeof(TService), instance);
#endregion
}
}
@@ -1,47 +0,0 @@
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <typeparam name="TImplementing">The implementing type.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
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>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TImplementing>(supporting.For<TFileSystem>());
});
}
/// <summary>
/// Registers a filesystem.
/// </summary>
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
/// <param name="composition">The composition.</param>
/// <returns>The register.</returns>
public static void RegisterFileSystem<TFileSystem>(this Composition composition)
where TFileSystem : FileSystemWrapper
{
composition.RegisterUnique(factory =>
{
var fileSystems = factory.GetInstance<FileSystems>();
var supporting = factory.GetInstance<SupportingFileSystems>();
return fileSystems.GetFileSystem<TFileSystem>(supporting.For<TFileSystem>());
});
}
}
}
@@ -1,35 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to the <see cref="Composition"/> class.
/// </summary>
public static partial class CompositionExtensions
{
/// <summary>
/// Registers a unique service as its own implementation.
/// </summary>
public static void RegisterUnique<TService>(this Composition composition)
=> composition.RegisterUnique(typeof(TService), typeof(TService));
/// <summary>
/// Registers a unique service with an implementation type.
/// </summary>
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>
public static void RegisterUnique<TService>(this Composition composition, TService instance)
=> composition.RegisterUnique(typeof(TService), instance);
}
}
@@ -128,7 +128,6 @@ namespace Umbraco.Core.Configuration
if (routes == null) throw new ArgumentNullException(nameof(routes));
//check if the current request matches a route, if so then it is reserved.
//TODO: This value should be cached! Else this is doing double routing in MVC every request!
var route = routes.GetRouteData(httpContext);
if (route != null)
return true;
-67
View File
@@ -40,72 +40,5 @@ namespace Umbraco.Core
public DecomposedGuid(Guid value) : this() => this.Value = value;
}
private static readonly char[] Base32Table =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5'
};
/// <summary>
/// Converts a Guid into a base-32 string.
/// </summary>
/// <param name="guid">A Guid.</param>
/// <param name="length">The string length.</param>
/// <returns>A base-32 encoded string.</returns>
/// <remarks>
/// <para>A base-32 string representation of a Guid is the shortest, efficient, representation
/// that is case insensitive (base-64 is case sensitive).</para>
/// <para>Length must be 1-26, anything else becomes 26.</para>
/// </remarks>
public static string ToBase32String(Guid guid, int length = 26)
{
if (length <= 0 || length > 26)
length = 26;
var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes
// this could be optimized by making it unsafe,
// and fixing the table + bytes + chars (see Convert.ToBase64CharArray)
// each block of 5 bytes = 5*8 = 40 bits
// becomes 40 bits = 8*5 = 8 byte-32 chars
// a Guid is 3 blocks + 8 bits
// so it turns into a 3*8+2 = 26 chars string
var chars = new char[length];
var i = 0;
var j = 0;
while (i < 15)
{
if (j == length) break;
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)];
if (j == length) break;
chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2];
if (j == length) break;
chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)];
if (j == length) break;
chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111];
i += 5;
}
if (j < length)
chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3];
if (j < length)
chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2];
return new string(chars);
}
}
}
+18 -48
View File
@@ -29,7 +29,7 @@ namespace Umbraco.Core.IO
// shadow support
private readonly List<ShadowWrapper> _shadowWrappers = new List<ShadowWrapper>();
private readonly object _shadowLocker = new object();
private static string _shadowCurrentId; // static - unique!!
private static Guid _shadowCurrentId = Guid.Empty; // static - unique!!
#region Constructor
// DI wants a public ctor
@@ -45,13 +45,13 @@ namespace Umbraco.Core.IO
_shadowWrappers.Clear();
_filesystems.Clear();
Volatile.Write(ref _wkfsInitialized, false);
_shadowCurrentId = null;
_shadowCurrentId = Guid.Empty;
}
// for tests only, totally unsafe
internal static void ResetShadowId()
{
_shadowCurrentId = null;
_shadowCurrentId = Guid.Empty;
}
// set by the scope provider when taking control of filesystems
@@ -126,11 +126,11 @@ namespace Umbraco.Core.IO
var scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "macro-partials", IsScoped);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "partials", IsScoped);
_macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", IsScoped);
_partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", IsScoped);
_stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, "css", IsScoped);
_scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, "scripts", IsScoped);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "views", IsScoped);
_mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", IsScoped);
// TODO: do we need a lock here?
_shadowWrappers.Add(_macroPartialFileSystem);
@@ -146,11 +146,6 @@ namespace Umbraco.Core.IO
#region Providers
private readonly Dictionary<Type, string> _paths = new Dictionary<Type, string>();
// internal for tests
internal IReadOnlyDictionary<Type, string> Paths => _paths;
/// <summary>
/// Gets a strongly-typed filesystem.
/// </summary>
@@ -167,33 +162,10 @@ namespace Umbraco.Core.IO
return (TFileSystem) _filesystems.GetOrAdd(typeof(TFileSystem), _ => new Lazy<IFileSystem>(() =>
{
var typeofTFileSystem = typeof(TFileSystem);
var name = typeof(TFileSystem).FullName;
if (name == null) throw new Exception("panic!");
// path must be unique and not collide with paths used in CreateWellKnownFileSystems
// for our well-known 'media' filesystem we can use the short 'media' path
// for others, put them under 'x/' and use ... something
string path;
if (typeofTFileSystem == typeof(MediaFileSystem))
{
path = "media";
}
else
{
lock (_paths)
{
if (!_paths.TryGetValue(typeofTFileSystem, out path))
{
path = Guid.NewGuid().ToString("N").Substring(0, 6);
while (_paths.ContainsValue(path)) // this can't loop forever, right?
path = Guid.NewGuid().ToString("N").Substring(0, 6);
_paths[typeofTFileSystem] = path;
}
}
path = "x/" + path;
}
var shadowWrapper = CreateShadowWrapper(supporting, path);
var shadowWrapper = CreateShadowWrapper(supporting, "typed/" + name);
return _container.CreateInstance<TFileSystem>(shadowWrapper);
})).Value;
}
@@ -207,37 +179,35 @@ namespace Umbraco.Core.IO
// global shadow for the entire application, so great care should be taken to ensure that the
// application is *not* doing anything else when using a shadow.
internal ICompletable Shadow()
internal ICompletable Shadow(Guid id)
{
if (Volatile.Read(ref _wkfsInitialized) == false) EnsureWellKnownFileSystems();
var id = ShadowWrapper.CreateShadowId();
return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow
}
internal void BeginShadow(string id)
internal void BeginShadow(Guid id)
{
lock (_shadowLocker)
{
// if we throw here, it means that something very wrong happened.
if (_shadowCurrentId != null)
if (_shadowCurrentId != Guid.Empty)
throw new InvalidOperationException("Already shadowing.");
_shadowCurrentId = id;
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", _shadowCurrentId);
_logger.Debug<ShadowFileSystems>("Shadow '{ShadowId}'", id);
foreach (var wrapper in _shadowWrappers)
wrapper.Shadow(_shadowCurrentId);
wrapper.Shadow(id);
}
}
internal void EndShadow(string id, bool completed)
internal void EndShadow(Guid id, bool completed)
{
lock (_shadowLocker)
{
// if we throw here, it means that something very wrong happened.
if (_shadowCurrentId == null)
if (_shadowCurrentId == Guid.Empty)
throw new InvalidOperationException("Not shadowing.");
if (id != _shadowCurrentId)
throw new InvalidOperationException("Not the current shadow.");
@@ -258,7 +228,7 @@ namespace Umbraco.Core.IO
}
}
_shadowCurrentId = null;
_shadowCurrentId = Guid.Empty;
if (exceptions.Count > 0)
throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
@@ -270,7 +240,7 @@ namespace Umbraco.Core.IO
lock (_shadowLocker)
{
var wrapper = new ShadowWrapper(filesystem, shadowPath, IsScoped);
if (_shadowCurrentId != null)
if (_shadowCurrentId != Guid.Empty)
wrapper.Shadow(_shadowCurrentId);
_shadowWrappers.Add(wrapper);
return wrapper;
@@ -7,21 +7,16 @@ namespace Umbraco.Core.IO.MediaPathSchemes
/// Implements a combined-guids media path scheme.
/// </summary>
/// <remarks>
/// <para>Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid.</para>
/// <para>This scheme is dangerous, as it does not prevent potential collisions.</para>
/// <para>Path is "{combinedGuid}/{filename>}" where combinedGuid is a combination of itemGuid and propertyGuid.</para>
/// </remarks>
public class CombinedGuidsMediaPathScheme : IMediaPathScheme
{
private const int DirectoryLength = 8;
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
// assumes that cuid and puid keys can be trusted - and that a single property type
// for a single content cannot store two different files with the same name
var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid);
var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
var directory = HexEncoder.Encode(GuidUtils.Combine(itemGuid, propertyGuid).ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
return Path.Combine(directory, filename).Replace('\\', '/');
}
@@ -1,38 +0,0 @@
using System;
using System.IO;
namespace Umbraco.Core.IO.MediaPathSchemes
{
/// <summary>
/// Implements a unique directory media path scheme.
/// </summary>
/// <remarks>
/// <para>This scheme provides short paths, yet handle potential collisions.</para>
/// </remarks>
public class UniqueMediaPathScheme : IMediaPathScheme
{
private const int DirectoryLength = 8;
/// <inheritdoc />
public string GetFilePath(IMediaFileSystem fileSystem, Guid itemGuid, Guid propertyGuid, string filename, string previous = null)
{
string directory;
// no point "combining" guids if all we want is some random guid - just get a new one
// and then, because we don't want collisions, ensure that the directory does not already exist
// (should be quite rare, but eh...)
do
{
var combinedGuid = Guid.NewGuid();
directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); // see also HexEncoder, we may want to fragment path eg 12/e4/f3...
} while (fileSystem.DirectoryExists(directory));
return Path.Combine(directory, filename).Replace('\\', '/');
}
/// <inheritdoc />
public string GetDeleteDirectory(IMediaFileSystem fileSystem, string filepath) => Path.GetDirectoryName(filepath);
}
}
@@ -304,13 +304,7 @@ namespace Umbraco.Core.IO
// permissions to reach that path, but it may nevertheless be outside of
// our root path, due to relative segments, so better check
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
{
// this says that 4.7.2 supports long paths - but Windows does not
// https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2
if (path.Length > 260)
throw new PathTooLongException($"Path {path} is too long.");
return path;
}
// nothing prevents us to reach the file, security-wise, yet it is outside
// this filesystem's root - throw
+2 -2
View File
@@ -10,7 +10,7 @@ namespace Umbraco.Core.IO
private bool _completed;
// invoked by the filesystems when shadowing
public ShadowFileSystems(FileSystems fileSystems, string id)
public ShadowFileSystems(FileSystems fileSystems, Guid id)
{
_fileSystems = fileSystems;
Id = id;
@@ -19,7 +19,7 @@ namespace Umbraco.Core.IO
}
// for tests
public string Id { get; }
public Guid Id { get; }
// invoked by the scope when exiting, if completed
public void Complete()
+1 -27
View File
@@ -22,33 +22,7 @@ namespace Umbraco.Core.IO
_isScoped = isScoped;
}
public static string CreateShadowId()
{
const int retries = 50; // avoid infinite loop
const int idLength = 8; // 6 chars
// shorten a Guid to idLength chars, and see whether it collides
// with an existing directory or not - if it does, try again, and
// we should end up with a unique identifier eventually - but just
// detect infinite loops (just in case)
for (var i = 0; i < retries; i++)
{
var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength);
var virt = ShadowFsPath + "/" + id;
var shadowDir = IOHelper.MapPath(virt);
if (Directory.Exists(shadowDir))
continue;
Directory.CreateDirectory(shadowDir);
return id;
}
throw new Exception($"Could not get a shadow identifier (tried {retries} times)");
}
internal void Shadow(string id)
internal void Shadow(Guid id)
{
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
// on ShadowFileSystemsScope.None - and if None is false then we should be running
@@ -281,7 +281,7 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" });
InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.Date", DbType = "Date" });
InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}");
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" });
_database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext",
@@ -5,7 +5,6 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Migrations.Upgrade.V_7_12_0;
using Umbraco.Core.Migrations.Upgrade.V_7_14_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
namespace Umbraco.Core.Migrations.Upgrade
{
@@ -138,8 +137,6 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameLabelAndRichTextPropertyEditorAliases>("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}");
To<MergeDateAndDateTimePropertyEditor>("{78BAF571-90D0-4D28-8175-EF96316DA789}");
To<ChangeNuCacheJsonFormat>("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}");
//FINAL
@@ -170,7 +167,7 @@ namespace Umbraco.Core.Migrations.Upgrade
From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0
// 7.14.0 has migrations, handle it...
// clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then
// clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations),
@@ -1,16 +0,0 @@
using Umbraco.Core.Migrations.PostMigrations;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_1
{
public class ChangeNuCacheJsonFormat : MigrationBase
{
public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context)
{ }
public override void Migrate()
{
// nothing - just adding the post-migration
Context.AddPostMigration<RebuildPublishedSnapshot>();
}
}
}
@@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
{
@@ -105,6 +101,35 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Validates the content item's properties pass variant rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
public static Property[] ValidateProperties(this IContentBase content, string culture = "*")
{
// select invalid properties
return content.Properties.Where(x =>
{
// if culture is null, we validate invariant properties only
// if culture is '*' we validate both variant and invariant properties, automatically
// if culture is specific eg 'en-US' we both too, but explicitly
var varies = x.PropertyType.VariesByCulture();
if (culture == null)
return !(varies || x.IsValid(null)); // validate invariant property, invariant culture
if (culture == "*")
return !x.IsValid(culture); // validate property, all cultures
return varies
? !x.IsValid(culture) // validate variant property, explicit culture
: !x.IsValid(null); // validate invariant property, explicit culture
})
.ToArray();
}
public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date)
{
if (string.IsNullOrWhiteSpace(name))
@@ -166,13 +191,6 @@ namespace Umbraco.Core.Models
content.CultureInfos.AddOrUpdate(culture, name, date);
}
/// <summary>
/// Sets the publishing values for names and properties.
/// </summary>
/// <param name="content"></param>
/// <param name="culture"></param>
/// <returns>A value indicating whether it was possible to publish the names and values for the specified
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data</returns>
public static bool PublishCulture(this IContent content, string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -183,6 +201,10 @@ namespace Umbraco.Core.Models
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
// the values we want to publish should be valid
if (content.ValidateProperties(culture).Any())
return false;
var alsoInvariant = false;
if (culture == "*") // all cultures
{
+2 -2
View File
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class ContentType : ContentTypeCompositionBase, IContentType
{
public const bool SupportsPublishingConst = true;
public const bool IsPublishingConst = true;
private int _defaultTemplate;
private IEnumerable<ITemplate> _allowedTemplates;
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool SupportsPublishing => SupportsPublishingConst;
public override bool IsPublishing => IsPublishingConst;
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<ITemplate>> TemplateComparer = new DelegateEqualityComparer<IEnumerable<ITemplate>>(
+5 -5
View File
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Models
// actually OK as IsPublishing is constant
// ReSharper disable once VirtualMemberCallInConstructor
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.Nothing;
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Models
// actually OK as IsPublishing is constant
// ReSharper disable once VirtualMemberCallInConstructor
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
_variations = ContentVariation.Nothing;
@@ -70,7 +70,7 @@ namespace Umbraco.Core.Models
public abstract ISimpleContentType ToSimple();
/// <summary>
/// Gets a value indicating whether the content type supports publishing.
/// Gets a value indicating whether the content type is publishing.
/// </summary>
/// <remarks>
/// <para>A publishing content type supports draft and published values for properties.
@@ -80,7 +80,7 @@ namespace Umbraco.Core.Models
/// the draft or published value of a property returns the same thing, and publishing
/// a value property has no effect.</para>
/// </remarks>
public abstract bool SupportsPublishing { get; }
public abstract bool IsPublishing { get; }
//Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<ContentTypeSort>> ContentTypeSortComparer =
@@ -257,7 +257,7 @@ namespace Umbraco.Core.Models
{
if (_noGroupPropertyTypes != null)
_noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged;
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value);
_noGroupPropertyTypes = new PropertyTypeCollection(IsPublishing, value);
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
@@ -226,7 +226,7 @@ namespace Umbraco.Core.Models
return null;
// create the new group
var group = new PropertyGroup(SupportsPublishing) { Name = name, SortOrder = 0 };
var group = new PropertyGroup(IsPublishing) { Name = name, SortOrder = 0 };
// check if it is inherited - there might be more than 1 but we want the 1st, to
// reuse its sort order - if there are more than 1 and they have different sort
+2 -2
View File
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class MediaType : ContentTypeCompositionBase, IMediaType
{
public const bool SupportsPublishingConst = false;
public const bool IsPublishingConst = false;
/// <summary>
/// Constuctor for creating a MediaType with the parent's id.
@@ -45,7 +45,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool SupportsPublishing => SupportsPublishingConst;
public override bool IsPublishing => IsPublishingConst;
/// <inheritdoc />
IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias);
+2 -2
View File
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class MemberType : ContentTypeCompositionBase, IMemberType
{
public const bool SupportsPublishingConst = false;
public const bool IsPublishingConst = false;
//Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId
private string _alias;
@@ -35,7 +35,7 @@ namespace Umbraco.Core.Models
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
/// <inheritdoc />
public override bool SupportsPublishing => SupportsPublishingConst;
public override bool IsPublishing => IsPublishingConst;
public override ContentVariation Variations
{
+54 -4
View File
@@ -184,7 +184,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return null;
return PropertyType.SupportsPublishing
return PropertyType.IsPublishing
? (published ? pvalue.PublishedValue : pvalue.EditedValue)
: pvalue.EditedValue;
}
@@ -244,7 +244,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return;
if (!PropertyType.SupportsPublishing)
if (!PropertyType.IsPublishing)
throw new NotSupportedException("Property type does not support publishing.");
var origValue = pvalue.PublishedValue;
pvalue.PublishedValue = PropertyType.ConvertAssignedValue(pvalue.EditedValue);
@@ -255,7 +255,7 @@ namespace Umbraco.Core.Models
{
if (pvalue == null) return;
if (!PropertyType.SupportsPublishing)
if (!PropertyType.IsPublishing)
throw new NotSupportedException("Property type does not support publishing.");
var origValue = pvalue.PublishedValue;
pvalue.PublishedValue = PropertyType.ConvertAssignedValue(null);
@@ -288,7 +288,7 @@ namespace Umbraco.Core.Models
{
var (pvalue, _) = GetPValue(culture, segment, true);
if (published && PropertyType.SupportsPublishing)
if (published && PropertyType.IsPublishing)
pvalue.PublishedValue = value;
else
pvalue.EditedValue = value;
@@ -332,6 +332,56 @@ namespace Umbraco.Core.Models
return (pvalue, change);
}
/// <summary>
/// Gets a value indicating whether the property has valid values.
/// </summary>
internal bool IsValid(string culture = "*", string segment = "*")
{
// TODO: validating values shouldn't be done here, this calls in to IsValidValue
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
// if validating invariant/neutral, and it is supported, validate
// (including ensuring that the value exists, if mandatory)
if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null))
if (!IsValidValue(_pvalue?.EditedValue))
return false;
// if validating only invariant/neutral, we are good
if (culture == null && segment == null)
return true;
// if nothing else to validate, we are good
if ((culture == null || culture == "*") && (segment == null || segment == "*") && !PropertyType.VariesByCulture())
return true;
// for anything else, validate the existing values (including mandatory),
// but we cannot validate mandatory globally (we don't know the possible cultures and segments)
if (_vvalues == null) return culture == "*" || IsValidValue(null);
var pvalues = _vvalues.Where(x =>
PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok
(culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches
(segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches
.Select(x => x.Value)
.ToList();
return pvalues.Count == 0 || pvalues.All(x => IsValidValue(x.EditedValue));
}
/// <summary>
/// Boolean indicating whether the passed in value is valid
/// </summary>
/// <param name="value"></param>
/// <returns>True is property value is valid, otherwise false</returns>
private bool IsValidValue(object value)
{
// TODO: this shouldn't exist here, the model itself shouldn't be responsible for it's own validation and this requires singleton access
return PropertyType.IsPropertyValueValid(value);
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
+14 -15
View File
@@ -82,22 +82,9 @@ namespace Umbraco.Core.Models
}
/// <summary>
/// Gets a value indicating whether the content type owning this property type is publishing.
/// Gets a value indicating whether the content type, owning this property type, is publishing.
/// </summary>
/// <remarks>
/// <para>A publishing content type supports draft and published values for properties.
/// It is possible to retrieve either the draft (default) or published value of a property.
/// Setting the value always sets the draft value, which then needs to be published.</para>
/// <para>A non-publishing content type only supports one value for properties. Getting
/// the draft or published value of a property returns the same thing, and publishing
/// a value property has no effect.</para>
/// <para>When true, getting the property value returns the edited value by default, but
/// it is possible to get the published value using the appropriate 'published' method
/// parameter.</para>
/// <para>When false, getting the property value always return the edited value,
/// regardless of the 'published' method parameter.</para>
/// </remarks>
public bool SupportsPublishing { get; internal set; }
public bool IsPublishing { get; internal set; }
/// <summary>
/// Gets of sets the name of the property type.
@@ -368,6 +355,18 @@ namespace Umbraco.Core.Models
}
// TODO: this and other value validation methods should be a service level (not a model) thing. Changing this to internal for now
/// <summary>
/// Determines whether a value is valid for this property type.
/// </summary>
internal bool IsPropertyValueValid(object value)
{
var editor = Current.PropertyEditors[_propertyEditorAlias]; // TODO: inject
var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // TODO: inject
var valueEditor = editor.GetValueEditor(configuration);
return !valueEditor.Validate(value, Mandatory, ValidationRegExp).Any();
}
/// <summary>
/// Sanitizes a property type alias.
/// </summary>
@@ -23,18 +23,18 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
internal Action OnAdd;
internal PropertyTypeCollection(bool supportsPublishing)
internal PropertyTypeCollection(bool isPublishing)
{
SupportsPublishing = supportsPublishing;
IsPublishing = isPublishing;
}
public PropertyTypeCollection(bool supportsPublishing, IEnumerable<PropertyType> properties)
: this(supportsPublishing)
public PropertyTypeCollection(bool isPublishing, IEnumerable<PropertyType> properties)
: this(isPublishing)
{
Reset(properties);
}
public bool SupportsPublishing { get; }
public bool IsPublishing { get; }
/// <summary>
/// Resets the collection to only contain the <see cref="PropertyType"/> instances referenced in the <paramref name="properties"/> parameter.
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Models
protected override void SetItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
item.IsPublishing = IsPublishing;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
@@ -65,7 +65,7 @@ namespace Umbraco.Core.Models
protected override void InsertItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
item.IsPublishing = IsPublishing;
base.InsertItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
@@ -79,7 +79,7 @@ namespace Umbraco.Core.Models
// TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members
internal new void Add(PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
item.IsPublishing = IsPublishing;
// TODO: this is not pretty and should be refactored
try
@@ -155,7 +155,7 @@ namespace Umbraco.Core.Models
public object DeepClone()
{
var clone = new PropertyTypeCollection(SupportsPublishing);
var clone = new PropertyTypeCollection(IsPublishing);
foreach (var propertyType in this)
clone.Add((PropertyType) propertyType.DeepClone());
return clone;
@@ -1,18 +0,0 @@
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Provides a live published model creation service.
/// </summary>
public interface ILivePublishedModelFactory : IPublishedModelFactory
{
/// <summary>
/// Gets an object that can be used to synchronize access to the factory.
/// </summary>
object SyncRoot { get; }
/// <summary>
/// Refreshes the factory.
/// </summary>
void Refresh();
}
}
@@ -11,14 +11,14 @@ namespace Umbraco.Core.Models.PublishedContent
/// <summary>
/// Initializes a new instance of the <see cref="PublishedCultureInfo"/> class.
/// </summary>
public PublishedCultureInfo(string culture, string name, string urlSegment, DateTime date)
public PublishedCultureInfo(string culture, string name, DateTime date)
{
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name));
Culture = culture;
Name = name;
UrlSegment = urlSegment;
UrlSegment = name.ToUrlSegment(culture);
Date = date;
}
@@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Factories
var propertyGroups = new PropertyGroupCollection();
foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue))
{
var group = new PropertyGroup(MemberType.SupportsPublishingConst);
var group = new PropertyGroup(MemberType.IsPublishingConst);
// if the group is defined on the current member type,
// assign its identifier, else it will be zero
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence.Factories
group.Key = groupDto.UniqueId;
group.Name = groupDto.Text;
group.SortOrder = groupDto.SortOrder;
group.PropertyTypes = new PropertyTypeCollection(MemberType.SupportsPublishingConst);
group.PropertyTypes = new PropertyTypeCollection(MemberType.IsPublishingConst);
//Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded
var localGroupDto = groupDto;
@@ -121,7 +121,7 @@ namespace Umbraco.Core.Persistence.Factories
foreach (var property in properties)
{
if (property.PropertyType.SupportsPublishing)
if (property.PropertyType.IsPublishing)
{
//create the resulting hashset if it's not created and the entity varies by culture
if (entityVariesByCulture && editedCultures == null)
@@ -4,7 +4,10 @@ using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NPoco;
using StackExchange.Profiling.Data;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
@@ -147,23 +147,8 @@ namespace Umbraco.Core.Persistence
private static TConnection GetTypedConnection<TConnection>(IDbConnection connection)
where TConnection : class, IDbConnection
{
var c = connection;
for (;;)
{
switch (c)
{
case TConnection ofType:
return ofType;
case RetryDbConnection retry:
c = retry.Inner;
break;
case ProfiledDbConnection profiled:
c = profiled.WrappedConnection;
break;
default:
throw new NotSupportedException(connection.GetType().FullName);
}
}
var profiled = connection as ProfiledDbConnection;
return profiled == null ? connection as TConnection : profiled.WrappedConnection as TConnection;
}
/// <summary>
@@ -175,20 +160,8 @@ namespace Umbraco.Core.Persistence
private static TTransaction GetTypedTransaction<TTransaction>(IDbTransaction transaction)
where TTransaction : class, IDbTransaction
{
var t = transaction;
for (;;)
{
switch (t)
{
case TTransaction ofType:
return ofType;
case ProfiledDbTransaction profiled:
t = profiled.WrappedTransaction;
break;
default:
throw new NotSupportedException(transaction.GetType().FullName);
}
}
var profiled = transaction as ProfiledDbTransaction;
return profiled == null ? transaction as TTransaction : profiled.WrappedTransaction as TTransaction;
}
/// <summary>
@@ -200,23 +173,11 @@ namespace Umbraco.Core.Persistence
private static TCommand GetTypedCommand<TCommand>(IDbCommand command)
where TCommand : class, IDbCommand
{
var c = command;
for (;;)
{
switch (c)
{
case TCommand ofType:
return ofType;
case FaultHandlingDbCommand faultHandling:
c = faultHandling.Inner;
break;
case ProfiledDbCommand profiled:
c = profiled.InternalCommand;
break;
default:
throw new NotSupportedException(command.GetType().FullName);
}
}
var faultHandling = command as FaultHandlingDbCommand;
if (faultHandling != null) command = faultHandling.Inner;
var profiled = command as ProfiledDbCommand;
if (profiled != null) command = profiled.InternalCommand;
return command as TCommand;
}
public static void TruncateTable(this IDatabase db, ISqlSyntaxProvider sqlSyntax, string tableName)
@@ -5,7 +5,6 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.Querying;
@@ -1120,25 +1119,6 @@ namespace Umbraco.Core.Persistence
#endregion
#region Aliasing
internal static string GetAliasedField(this Sql<ISqlContext> sql, string field)
{
// get alias, if aliased
//
// regex looks for pattern "([\w+].[\w+]) AS ([\w+])" ie "(field) AS (alias)"
// and, if found & a group's field matches the field name, returns the alias
//
// so... if query contains "[umbracoNode].[nodeId] AS [umbracoNode__nodeId]"
// then GetAliased for "[umbracoNode].[nodeId]" returns "[umbracoNode__nodeId]"
var matches = sql.SqlContext.SqlSyntax.AliasRegex.Matches(sql.SQL);
var match = matches.Cast<Match>().FirstOrDefault(m => m.Groups[1].Value.InvariantEquals(field));
return match == null ? field : match.Groups[2].Value;
}
#endregion
#region Utilities
private static string[] GetColumns<TDto>(this Sql<ISqlContext> sql, string tableAlias = null, string referenceName = null, Expression<Func<TDto, object>>[] columnExpressions = null, bool withAlias = true)
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
_templateRepository = templateRepository;
}
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
protected override bool IsPublishing => ContentType.IsPublishingConst;
protected override IRepositoryCachePolicy<IContentType, int> CreateCachePolicy()
{
@@ -60,11 +60,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Any())
{
//NOTE: This logic should never be executed according to our cache policy
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository)
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository)
.Where(x => ids.Contains(x.Id));
}
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository);
return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, IsPublishing, this, _templateRepository);
}
protected override IEnumerable<IContentType> PerformGetAll(params Guid[] ids)
@@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected abstract bool SupportsPublishing { get; }
protected abstract bool IsPublishing { get; }
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
{
@@ -1021,7 +1021,7 @@ AND umbracoNode.id <> @id",
var dtos = Database
.Fetch<PropertyTypeGroupDto>(sql);
var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, SupportsPublishing, id, createDate, updateDate,CreatePropertyType);
var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, IsPublishing, id, createDate, updateDate,CreatePropertyType);
return new PropertyGroupCollection(propertyGroups);
}
@@ -1057,7 +1057,7 @@ AND umbracoNode.id <> @id",
//Reset dirty properties
Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false));
return new PropertyTypeCollection(SupportsPublishing, list);
return new PropertyTypeCollection(IsPublishing, list);
}
protected void ValidateAlias(PropertyType pt)
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
protected override bool IsPublishing => MediaType.IsPublishingConst;
protected override IRepositoryCachePolicy<IMediaType, int> CreateCachePolicy()
{
@@ -55,11 +55,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Any())
{
//NOTE: This logic should never be executed according to our cache policy
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this)
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this)
.Where(x => ids.Contains(x.Id));
}
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, SupportsPublishing, this);
return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, IsPublishing, this);
}
protected override IEnumerable<IMediaType> PerformGetAll(params Guid[] ids)
@@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: base(scopeAccessor, cache, logger)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
protected override bool IsPublishing => MemberType.IsPublishingConst;
protected override IRepositoryCachePolicy<IMemberType, int> CreateCachePolicy()
{
@@ -702,6 +702,23 @@ ORDER BY colName";
{
if (orderBy == null) throw new ArgumentNullException(nameof(orderBy));
// get the referenced column name and find the corresp mapped column name
var expressionMember = ExpressionHelper.GetMemberInfo(orderBy);
var mapper = _mapperCollection[typeof(IUser)];
var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name);
if (mappedField.IsNullOrWhiteSpace())
throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause");
return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, mappedField, orderDirection, includeUserGroups, excludeUserGroups, userState, filter);
}
private IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection = Direction.Ascending,
string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery<IUser> filter = null)
{
if (string.IsNullOrWhiteSpace(orderBy)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(orderBy));
Sql<ISqlContext> filterSql = null;
var customFilterWheres = filter?.GetWhereClauses().ToArray();
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
@@ -717,6 +734,7 @@ ORDER BY colName";
filterSql.Append($"AND ({clause.Item1})", clause.Item2);
}
if (includeUserGroups != null && includeUserGroups.Length > 0)
{
const string subQuery = @"AND (umbracoUser.id IN (SELECT DISTINCT umbracoUser.id
@@ -790,7 +808,7 @@ ORDER BY colName";
sql = new SqlTranslator<IUser>(sql, query).Translate();
// get sorted and filtered sql
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderBy, orderDirection);
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy);
// get a page of results and total count
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
@@ -816,36 +834,14 @@ ORDER BY colName";
return sql;
}
private Sql<ISqlContext> ApplySort(Sql<ISqlContext> sql, Expression<Func<IUser, object>> orderBy, Direction orderDirection)
private static Sql<ISqlContext> ApplySort(Sql<ISqlContext> sql, Direction orderDirection, string orderBy)
{
if (orderBy == null) return sql;
var expressionMember = ExpressionHelper.GetMemberInfo(orderBy);
var mapper = _mapperCollection[typeof(IUser)];
var mappedField = mapper.Map(SqlContext.SqlSyntax, expressionMember.Name);
if (mappedField.IsNullOrWhiteSpace())
throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause");
// beware! NPoco paging code parses the query to isolate the ORDER BY fragment,
// using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything
// else in orderBy is going to break NPoco / not be detected
// beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar]
// to [bar] only, so we MUST use aliases, cannot use [table].[field]
// beware! pre-2012 SqlServer is using a convoluted syntax for paging, which
// includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...",
// so anything added here MUST also be part of the inner SELECT statement, ie
// the original statement, AND must be using the proper alias, as the inner SELECT
// will hide the original table.field names entirely
var orderByField = sql.GetAliasedField(mappedField);
if (string.IsNullOrEmpty(orderBy)) return sql;
if (orderDirection == Direction.Ascending)
sql.OrderBy(orderByField);
sql.OrderBy(orderBy);
else
sql.OrderByDescending(orderByField);
sql.OrderByDescending(orderBy);
return sql;
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using NPoco;
@@ -123,12 +122,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax
SERVERPROPERTY('ResourceLastUpdateDateTime') ResourceLastUpdateDateTime,
SERVERPROPERTY('ProductLevel') ProductLevel;";
string GetString(IDataReader reader, int ordinal, string defaultValue)
=> reader.IsDBNull(ordinal) ? defaultValue : reader.GetString(ordinal);
int GetInt32(IDataReader reader, int ordinal, int defaultValue)
=> reader.IsDBNull(ordinal) ? defaultValue : reader.GetInt32(ordinal);
connection.ConnectionString = connectionString;
ServerVersionInfo version;
using (connection)
@@ -143,12 +136,12 @@ namespace Umbraco.Core.Persistence.SqlSyntax
reader.Read();
// InstanceName can be NULL for the default instance
version = new ServerVersionInfo(
GetString(reader, 0, "Unknown"),
GetString(reader, 2, "(default)"),
GetString(reader, 3, string.Empty),
(EngineEdition) GetInt32(reader, 5, 0),
GetString(reader, 7, "DEFAULT"),
GetString(reader, 9, "Unknown"));
reader.GetString(0),
reader.IsDBNull(2) ? "(default)" : reader.GetString(2),
reader.IsDBNull(3) ? "" : reader.GetString(3),
(EngineEdition) reader.GetInt32(5),
reader.GetString(7),
reader.GetString(9));
}
connection.Close();
}
@@ -290,7 +290,7 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
public IEnumerable<XElement> ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published)
{
published &= property.PropertyType.SupportsPublishing;
published &= property.PropertyType.IsPublishing;
var nodeName = property.PropertyType.Alias.ToSafeAlias();
@@ -36,10 +36,6 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
if (source is int)
return (int)source == 1;
// this is required for correct true/false handling in nested content elements
if (source is long)
return (long)source == 1;
if (source is bool)
return (bool)source;
@@ -1,33 +0,0 @@
using System;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods for <see cref="IPublishedModelFactory"/>.
/// </summary>
public static class PublishedModelFactoryExtensions
{
/// <summary>
/// Executes an action with a safe live factory/
/// </summary>
/// <remarks>
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
/// </remarks>
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
{
if (factory is ILivePublishedModelFactory liveFactory)
{
lock (liveFactory.SyncRoot)
{
liveFactory.Refresh();
action();
}
}
else
{
action();
}
}
}
}
+2 -2
View File
@@ -168,7 +168,7 @@ namespace Umbraco.Core.Runtime
_state.BootFailedException = bfe;
}
timer?.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves
timer.Fail(exception: bfe); // be sure to log the exception - even if we repeat ourselves
// if something goes wrong above, we may end up with no factory
// meaning nothing can get the runtime state, etc - so let's try
@@ -242,7 +242,7 @@ namespace Umbraco.Core.Runtime
}
catch
{
timer?.Fail();
timer.Fail();
throw;
}
}
@@ -5,11 +5,11 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Runtime
{
public class CoreInitialComponent : IComponent
public class CoreRuntimeComponent : IComponent
{
private readonly IEnumerable<Profile> _mapperProfiles;
public CoreInitialComponent(IEnumerable<Profile> mapperProfiles)
public CoreRuntimeComponent(IEnumerable<Profile> mapperProfiles)
{
_mapperProfiles = mapperProfiles;
}
@@ -24,9 +24,7 @@ using IntegerValidator = Umbraco.Core.PropertyEditors.Validators.IntegerValidato
namespace Umbraco.Core.Runtime
{
// core's initial composer composes before all core composers
[ComposeBefore(typeof(ICoreComposer))]
public class CoreInitialComposer : ComponentComposer<CoreInitialComponent>
public class CoreRuntimeComposer : ComponentComposer<CoreRuntimeComponent>, IRuntimeComposer
{
public override void Compose(Composition composition)
{
+2 -2
View File
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Scoping
// see note below
if (scopeFileSystems == true)
_fscope = fileSystems.Shadow();
_fscope = fileSystems.Shadow(Guid.NewGuid());
return;
}
@@ -105,7 +105,7 @@ namespace Umbraco.Core.Scoping
// every scoped FS to trigger the creation of shadow FS "on demand", and that would be
// pretty pointless since if scopeFileSystems is true, we *know* we want to shadow
if (scopeFileSystems == true)
_fscope = fileSystems.Shadow();
_fscope = fileSystems.Shadow(Guid.NewGuid());
}
}
@@ -31,16 +31,16 @@ namespace Umbraco.Core.Services
/// <param name="contentService"></param>
/// <param name="name"></param>
/// <param name="parentId"></param>
/// <param name="contentTypeAlias"></param>
/// <param name="mediaTypeAlias"></param>
/// <param name="userId"></param>
/// <returns></returns>
public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId)
{
var guidUdi = parentId as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
var parent = contentService.GetById(guidUdi.Guid);
return contentService.Create(name, parent, contentTypeAlias, userId);
return contentService.Create(name, parent, mediaTypeAlias, userId);
}
/// <summary>
+24 -24
View File
@@ -391,30 +391,30 @@ namespace Umbraco.Core.Services
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId);
///// <summary>
///// Saves and publishes a document branch.
///// </summary>
///// <param name="content">The root document.</param>
///// <param name="force">A value indicating whether to force-publish documents that are not already published.</param>
///// <param name="shouldPublish">A function determining cultures to publish.</param>
///// <param name="publishCultures">A function publishing cultures.</param>
///// <param name="userId">The identifier of the user performing the operation.</param>
///// <remarks>
///// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
///// only those documents that are already published, are republished. When <c>true</c>, all documents are
///// published. The root of the branch is always published, regardless of <paramref name="force"/>.</para>
///// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
///// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
///// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
///// cultures may trigger an unwanted republish.</para>
///// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
///// whether the cultures could be published.</para>
///// </remarks>
//IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force,
// Func<IContent, HashSet<string>> shouldPublish,
// Func<IContent, HashSet<string>, bool> publishCultures,
// int userId = Constants.Security.SuperUserId);
/// <summary>
/// Saves and publishes a document branch.
/// </summary>
/// <param name="content">The root document.</param>
/// <param name="force">A value indicating whether to force-publish documents that are not already published.</param>
/// <param name="shouldPublish">A function determining cultures to publish.</param>
/// <param name="publishCultures">A function publishing cultures.</param>
/// <param name="userId">The identifier of the user performing the operation.</param>
/// <remarks>
/// <para>The <paramref name="force"/> parameter determines which documents are published. When <c>false</c>,
/// only those documents that are already published, are republished. When <c>true</c>, all documents are
/// published. The root of the branch is always published, regardless of <paramref name="force"/>.</para>
/// <para>The <paramref name="editing"/> parameter is a function which determines whether a document has
/// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
/// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
/// cultures may trigger an unwanted republish.</para>
/// <para>The <paramref name="publishCultures"/> parameter is a function to execute to publish cultures, on
/// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
/// whether the cultures could be published.</para>
/// </remarks>
IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
int userId = Constants.Security.SuperUserId);
/// <summary>
/// Unpublishes a document.
@@ -10,7 +10,6 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Changes;
@@ -28,8 +27,6 @@ namespace Umbraco.Core.Services.Implement
private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
private readonly ILanguageRepository _languageRepository;
private IQuery<IContent> _queryNotTrashed;
//TODO: The non-lazy object should be injected
private readonly Lazy<PropertyValidationService> _propertyValidationService = new Lazy<PropertyValidationService>(() => new PropertyValidationService());
#region Constructors
@@ -196,7 +193,7 @@ namespace Umbraco.Core.Services.Implement
var content = new Content(name, parentId, contentType);
using (var scope = ScopeProvider.CreateScope())
{
CreateContent(scope, content, userId, false);
CreateContent(scope, content, parent, userId, false);
scope.Complete();
}
@@ -230,7 +227,7 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback
var content = new Content(name, parent, contentType);
CreateContent(scope, content, userId, false);
CreateContent(scope, content, parent, userId, false);
scope.Complete();
return content;
@@ -264,7 +261,7 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback
var content = parentId > 0 ? new Content(name, parent, contentType) : new Content(name, parentId, contentType);
CreateContent(scope, content, userId, true);
CreateContent(scope, content, parent, userId, true);
scope.Complete();
return content;
@@ -296,14 +293,14 @@ namespace Umbraco.Core.Services.Implement
throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback
var content = new Content(name, parent, contentType);
CreateContent(scope, content, userId, true);
CreateContent(scope, content, parent, userId, true);
scope.Complete();
return content;
}
}
private void CreateContent(IScope scope, IContent content, int userId, bool withIdentity)
private void CreateContent(IScope scope, Content content, IContent parent, int userId, bool withIdentity)
{
content.CreatorId = userId;
content.WriterId = userId;
@@ -314,12 +311,12 @@ namespace Umbraco.Core.Services.Implement
// if saving is cancelled, content remains without an identity
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return;
_documentRepository.Save(content);
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshNode).ToEventArgs());
}
@@ -761,7 +758,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
@@ -786,7 +783,7 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
}
var changeType = TreeChangeTypes.RefreshNode;
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
@@ -816,7 +813,7 @@ namespace Umbraco.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
@@ -836,7 +833,7 @@ namespace Umbraco.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
}
scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content");
@@ -869,51 +866,26 @@ namespace Umbraco.Core.Services.Implement
throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types.");
}
using (var scope = ScopeProvider.CreateScope())
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
// explicitly SaveAndPublish a specific culture also publishes invariant values
if (!culture.IsNullOrWhiteSpace() && culture != "*")
{
scope.WriteLock(Constants.Locks.ContentTree);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
Property[] invalidProperties;
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
// explicitly SaveAndPublish a specific culture also publishes invariant values
if (!culture.IsNullOrWhiteSpace() && culture != "*")
{
// publish the invariant values
var publishInvariant = content.PublishCulture(null);
if (!publishInvariant)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
}
// publish the culture(s)
var publishCulture = content.PublishCulture(culture);
if (!publishCulture)
// publish the invariant values
var publishInvariant = content.PublishCulture(null);
if (!publishInvariant)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
return result;
}
// publish the culture(s)
var publishCulture = content.PublishCulture(culture);
if (!publishCulture)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
// finally, "save publishing"
// what happens next depends on whether the content can be published or not
return CommitDocumentChanges(content, userId, raiseEvents);
}
/// <inheritdoc />
@@ -922,40 +894,23 @@ namespace Umbraco.Core.Services.Implement
if (content == null) throw new ArgumentNullException(nameof(content));
if (cultures == null) throw new ArgumentNullException(nameof(cultures));
using (var scope = ScopeProvider.CreateScope())
var evtMsgs = EventMessagesFactory.Get();
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
{
scope.WriteLock(Constants.Locks.ContentTree);
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var varies = content.ContentType.VariesByCulture();
if (cultures.Length == 0)
{
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
if (cultures.Select(content.PublishCulture).Any(isValid => !isValid))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values
if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{
InvalidProperties = invalidProperties
};
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
return result;
//no cultures specified and doesn't vary, so publish it, else nothing to publish
return !varies
? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents)
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
// TODO: currently, no way to know which one failed
if (cultures.Select(content.PublishCulture).Any(isValid => !isValid))
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
return CommitDocumentChanges(content, userId, raiseEvents);
}
/// <inheritdoc />
@@ -988,87 +943,56 @@ namespace Umbraco.Core.Services.Implement
if (!content.Published)
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
using (var scope = ScopeProvider.CreateScope())
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
scope.WriteLock(Constants.Locks.ContentTree);
content.PublishedState = PublishedState.Unpublishing;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
if (persisted != null && !persisted.IsCulturePublished(culture))
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
{
content.PublishedState = PublishedState.Unpublishing;
}
else
{
// If the culture we want to unpublish was already unpublished, nothing to do.
// To check for that we need to lookup the persisted content item
var persisted = content.HasIdentity ? GetById(content.Id) : null;
if (persisted != null && !persisted.IsCulturePublished(culture))
return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
// unpublish the culture
content.UnpublishCulture(culture);
}
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId);
scope.Complete();
return result;
// unpublish the culture
content.UnpublishCulture(culture);
}
// finally, "save publishing"
return CommitDocumentChanges(content, userId);
}
/// <summary>
/// Saves a document and publishes/unpublishes any pending publishing changes made to the document.
/// </summary>
/// <remarks>
/// <para>
/// This MUST NOT be called from within this service, this used to be a public API and must only be used outside of this service.
/// Internally in this service, calls must be made to CommitDocumentChangesInternal
/// </para>
///
/// <para>This is the underlying logic for both publishing and unpublishing any document</para>
/// <para>Pending publishing/unpublishing changes on a document are made with calls to <see cref="ContentRepositoryExtensions.PublishCulture"/> and
/// <see cref="ContentRepositoryExtensions.UnpublishCulture"/>.</para>
/// <para>Pending publishing/unpublishing changes on a document are made with calls to <see cref="IContent.PublishCulture"/> and
/// <see cref="IContent.UnpublishCulture"/>.</para>
/// <para>When publishing or unpublishing a single culture, or all cultures, use <see cref="SaveAndPublish"/>
/// and <see cref="Unpublish"/>. But if the flexibility to both publish and unpublish in a single operation is required
/// then this method needs to be used in combination with <see cref="ContentRepositoryExtensions.PublishCulture"/> and <see cref="ContentRepositoryExtensions.UnpublishCulture"/>
/// then this method needs to be used in combination with <see cref="IContent.PublishCulture"/> and <see cref="IContent.UnpublishCulture"/>
/// on the content itself - this prepares the content, but does not commit anything - and then, invoke
/// <see cref="CommitDocumentChanges"/> to actually commit the changes to the database.</para>
/// <para>The document is *always* saved, even when publishing fails.</para>
/// </remarks>
internal PublishResult CommitDocumentChanges(IContent content,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true)
internal PublishResult CommitDocumentChanges(IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true)
{
using (var scope = ScopeProvider.CreateScope())
{
var evtMsgs = EventMessagesFactory.Get();
scope.WriteLock(Constants.Locks.ContentTree);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, userId, raiseEvents);
scope.Complete();
return result;
}
}
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
ContentSavingEventArgs saveEventArgs,
int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs));
var evtMsgs = saveEventArgs.Messages;
var evtMsgs = EventMessagesFactory.Get();
PublishResult publishResult = null;
PublishResult unpublishResult = null;
@@ -1094,6 +1018,11 @@ namespace Umbraco.Core.Services.Implement
var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
var previouslyPublished = content.HasIdentity && content.Published;
// always save
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
if (publishing)
{
culturesUnpublishing = content.GetCulturesUnpublishing();
@@ -1129,7 +1058,7 @@ namespace Umbraco.Core.Services.Implement
// but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
// PublishState to anything other than Publishing or Unpublishing - which is precisely
// what we want to do here - throws
content.Published = content.Published;
content.Published = content.Published;
}
}
@@ -1178,7 +1107,7 @@ namespace Umbraco.Core.Services.Implement
// raise the Saved event, always
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), "Saved");
}
if (unpublishing) // we have tried to unpublish - won't happen in a branch
@@ -1313,13 +1242,6 @@ namespace Umbraco.Core.Services.Implement
.Distinct()
.ToList();
if (pendingCultures.Count == 0)
break; //shouldn't happen but no point in continuing if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d);
var publishing = true;
foreach (var culture in pendingCultures)
{
@@ -1328,14 +1250,7 @@ namespace Umbraco.Core.Services.Implement
if (d.Trashed) continue; // won't publish
//publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
Property[] invalidProperties = null;
var tryPublish = d.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties);
if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
publishing &= tryPublish; //set the culture to be published
publishing &= d.PublishCulture(culture); //set the culture to be published
if (!publishing) break; // no point continuing
}
@@ -1344,8 +1259,7 @@ namespace Umbraco.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
result = CommitDocumentChanges(d, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1379,13 +1293,6 @@ namespace Umbraco.Core.Services.Implement
.Distinct()
.ToList();
if (pendingCultures.Count == 0)
break; //shouldn't happen but no point in continuing if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d);
foreach (var c in pendingCultures)
{
//Clear this schedule for this culture
@@ -1394,11 +1301,13 @@ namespace Umbraco.Core.Services.Implement
d.UnpublishCulture(c);
}
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
if (pendingCultures.Count > 0)
{
result = CommitDocumentChanges(d, d.WriterId);
if (result.Success == false)
Logger.Error<ContentService>(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
yield return result;
}
}
else
{
@@ -1419,20 +1328,16 @@ namespace Umbraco.Core.Services.Implement
}
}
// utility 'PublishCultures' func used by SaveAndPublishBranch
private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet<string> culturesToPublish)
private bool SaveAndPublishBranch_PublishCultures(IContent c, HashSet<string> culturesToPublish)
{
//TODO: This does not support being able to return invalid property details to bubble up to the UI
// variant content type - publish specified cultures
// invariant content type - publish only the invariant culture
return content.ContentType.VariesByCulture()
? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _))
: content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _);
return c.ContentType.VariesByCulture()
? culturesToPublish.All(c.PublishCulture)
: c.PublishCulture();
}
// utility 'ShouldPublish' func used by SaveAndPublishBranch
private HashSet<string> SaveAndPublishBranch_ShouldPublish(ref HashSet<string> cultures, string c, bool published, bool edited, bool isRoot, bool force)
private HashSet<string> SaveAndPublishBranch_ShouldPublish3(ref HashSet<string> cultures, string c, bool published, bool edited, bool isRoot, bool force)
{
// if published, republish
if (published)
@@ -1450,6 +1355,7 @@ namespace Umbraco.Core.Services.Implement
return cultures;
}
/// <inheritdoc />
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId)
{
@@ -1468,10 +1374,10 @@ namespace Umbraco.Core.Services.Implement
HashSet<string> culturesToPublish = null;
if (!c.ContentType.VariesByCulture()) // invariant content type
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
if (culture != "*") // variant content type, specific culture
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
// variant content type, all cultures
if (c.Published)
@@ -1479,7 +1385,7 @@ namespace Umbraco.Core.Services.Implement
// then some (and maybe all) cultures will be 'already published' (unless forcing),
// others will have to 'republish this culture'
foreach (var x in c.AvailableCultures)
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
return culturesToPublish;
}
@@ -1508,7 +1414,7 @@ namespace Umbraco.Core.Services.Implement
HashSet<string> culturesToPublish = null;
if (!c.ContentType.VariesByCulture()) // invariant content type
return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
return SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
// variant content type, specific cultures
if (c.Published)
@@ -1516,7 +1422,7 @@ namespace Umbraco.Core.Services.Implement
// then some (and maybe all) cultures will be 'already published' (unless forcing),
// others will have to 'republish this culture'
foreach (var x in cultures)
SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
SaveAndPublishBranch_ShouldPublish3(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force);
return culturesToPublish;
}
@@ -1529,7 +1435,8 @@ namespace Umbraco.Core.Services.Implement
return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
}
internal IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
/// <inheritdoc />
public IEnumerable<PublishResult> SaveAndPublishBranch(IContent document, bool force,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
int userId = Constants.Security.SuperUserId)
@@ -1553,7 +1460,7 @@ namespace Umbraco.Core.Services.Implement
throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
// deal with the branch root - if it fails, abort
var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
var result = SaveAndPublishBranchOne(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId);
if (result != null)
{
results.Add(result);
@@ -1584,7 +1491,7 @@ namespace Umbraco.Core.Services.Implement
}
// no need to check path here, parent has to be published here
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
result = SaveAndPublishBranchOne(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId);
if (result != null)
{
results.Add(result);
@@ -1614,7 +1521,7 @@ namespace Umbraco.Core.Services.Implement
// shouldPublish: a function determining whether the document has changes that need to be published
// note - 'force' is handled by 'editing'
// publishValues: a function publishing values (using the appropriate PublishCulture calls)
private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document,
private PublishResult SaveAndPublishBranchOne(IScope scope, IContent document,
Func<IContent, HashSet<string>> shouldPublish,
Func<IContent, HashSet<string>, bool> publishCultures,
bool isRoot,
@@ -1627,18 +1534,11 @@ namespace Umbraco.Core.Services.Implement
if (culturesToPublish.Count == 0) // empty = already published
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
// publish & check if values are valid
if (!publishCultures(document, culturesToPublish))
{
//TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot);
var result = CommitDocumentChangesInternal(scope, document, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -2613,6 +2513,7 @@ namespace Umbraco.Core.Services.Implement
return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content);
}
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id);
return new PublishResult(evtMsgs, content);
}
@@ -1175,7 +1175,7 @@ namespace Umbraco.Core.Services.Implement
var identity = int.MaxValue;
var memType = new MemberType(-1);
var propGroup = new PropertyGroup(MemberType.SupportsPublishingConst)
var propGroup = new PropertyGroup(MemberType.IsPublishingConst)
{
Name = "Membership",
Id = --identity
@@ -3,14 +3,26 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml.Linq;
using Semver;
using Umbraco.Core.Collections;
using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Strings;
using Content = Umbraco.Core.Models.Content;
namespace Umbraco.Core.Services.Implement
{
@@ -1,135 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Services
{
//TODO: We should make this an interface and inject it into the ContentService
internal class PropertyValidationService
{
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService)
{
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
}
//TODO: Remove this method in favor of the overload specifying all dependencies
public PropertyValidationService()
: this(Current.PropertyEditors, Current.Services.DataTypeService)
{
}
/// <summary>
/// Validates the content item's properties pass validation rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*")
{
// select invalid properties
invalidProperties = content.Properties.Where(x =>
{
// if culture is null, we validate invariant properties only
// if culture is '*' we validate both variant and invariant properties, automatically
// if culture is specific eg 'en-US' we both too, but explicitly
var varies = x.PropertyType.VariesByCulture();
if (culture == null)
return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture
if (culture == "*")
return !IsPropertyValid(x, culture); // validate property, all cultures
return varies
? !IsPropertyValid(x, culture) // validate variant property, explicit culture
: !IsPropertyValid(x, null); // validate invariant property, explicit culture
})
.ToArray();
return invalidProperties.Length == 0;
}
/// <summary>
/// Gets a value indicating whether the property has valid values.
/// </summary>
public bool IsPropertyValid(Property property, string culture = "*", string segment = "*")
{
//NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there.
// The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing.
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
Property.PropertyValue pvalue = null;
// if validating invariant/neutral, and it is supported, validate
// (including ensuring that the value exists, if mandatory)
if ((culture == null || culture == "*") && (segment == null || segment == "*") && property.PropertyType.SupportsVariation(null, null))
{
// validate pvalue (which is the invariant value)
pvalue = property.Values.FirstOrDefault(x => x.Culture == null && x.Segment == null);
if (!IsValidPropertyValue(property, pvalue?.EditedValue))
return false;
}
// if validating only invariant/neutral, we are good
if (culture == null && segment == null)
return true;
// if nothing else to validate, we are good
if ((culture == null || culture == "*") && (segment == null || segment == "*") && !property.PropertyType.VariesByCulture())
return true;
// for anything else, validate the existing values (including mandatory),
// but we cannot validate mandatory globally (we don't know the possible cultures and segments)
// validate vvalues (which are the variant values)
// if we don't have vvalues (property.Values is empty or only contains pvalue), validate null
if (property.Values.Count == (pvalue == null ? 0 : 1))
return culture == "*" || IsValidPropertyValue(property, null);
// else validate vvalues (but don't revalidate pvalue)
var pvalues = property.Values.Where(x =>
x != pvalue && // don't revalidate pvalue
property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok
(culture == "*" || x.Culture.InvariantEquals(culture)) && // the culture matches
(segment == "*" || x.Segment.InvariantEquals(segment))) // the segment matches
.ToList();
return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue));
}
/// <summary>
/// Boolean indicating whether the passed in value is valid
/// </summary>
/// <param name="property"></param>
/// <param name="value"></param>
/// <returns>True is property value is valid, otherwise false</returns>
private bool IsValidPropertyValue(Property property, object value)
{
return IsPropertyValueValid(property.PropertyType, value);
}
/// <summary>
/// Determines whether a value is valid for this property type.
/// </summary>
private bool IsPropertyValueValid(PropertyType propertyType, object value)
{
var editor = _propertyEditors[propertyType.PropertyEditorAlias];
var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration;
var valueEditor = editor.GetValueEditor(configuration);
return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any();
}
}
}
+62 -47
View File
@@ -593,14 +593,13 @@ namespace Umbraco.Core
///<returns></returns>
public static string ToUrlBase64(this string input)
{
if (input == null) throw new ArgumentNullException(nameof(input));
if (input == null) throw new ArgumentNullException("input");
if (string.IsNullOrEmpty(input))
return string.Empty;
if (String.IsNullOrEmpty(input)) return String.Empty;
//return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ",");
var bytes = Encoding.UTF8.GetBytes(input);
return UrlTokenEncode(bytes);
//return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ",");
}
/// <summary>
@@ -610,14 +609,14 @@ namespace Umbraco.Core
/// <returns></returns>
public static string FromUrlBase64(this string input)
{
if (input == null) throw new ArgumentNullException(nameof(input));
if (input == null) throw new ArgumentNullException("input");
//if (input.IsInvalidBase64()) return null;
try
{
//var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "="));
var decodedBytes = UrlTokenDecode(input);
byte[] decodedBytes = UrlTokenDecode(input);
return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null;
}
catch (FormatException)
@@ -796,40 +795,42 @@ namespace Umbraco.Core
internal static byte[] UrlTokenDecode(string input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.Length == 0)
return Array.Empty<byte>();
// calc array size - must be groups of 4
var arrayLength = input.Length;
var remain = arrayLength % 4;
if (remain != 0) arrayLength += 4 - remain;
var inArray = new char[arrayLength];
for (var i = 0; i < input.Length; i++)
{
var ch = input[i];
throw new ArgumentNullException("input");
}
int length = input.Length;
if (length < 1)
{
return new byte[0];
}
int num2 = input[length - 1] - '0';
if ((num2 < 0) || (num2 > 10))
{
return null;
}
char[] inArray = new char[(length - 1) + num2];
for (int i = 0; i < (length - 1); i++)
{
char ch = input[i];
switch (ch)
{
case '-': // restore '-' as '+'
case '-':
inArray[i] = '+';
break;
case '_': // restore '_' as '/'
case '_':
inArray[i] = '/';
break;
default: // keep char unchanged
default:
inArray[i] = ch;
break;
}
}
// pad with '='
for (var j = input.Length; j < inArray.Length; j++)
for (int j = length - 1; j < inArray.Length; j++)
{
inArray[j] = '=';
}
return Convert.FromBase64CharArray(inArray, 0, inArray.Length);
}
@@ -841,40 +842,54 @@ namespace Umbraco.Core
internal static string UrlTokenEncode(byte[] input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.Length == 0)
return string.Empty;
// base-64 digits are A-Z, a-z, 0-9, + and /
// the = char is used for trailing padding
var str = Convert.ToBase64String(input);
var pos = str.IndexOf('=');
if (pos < 0) pos = str.Length;
// replace chars that would cause problems in urls
var chArray = new char[pos];
for (var i = 0; i < pos; i++)
{
var ch = str[i];
throw new ArgumentNullException("input");
}
if (input.Length < 1)
{
return String.Empty;
}
string str = null;
int index = 0;
char[] chArray = null;
str = Convert.ToBase64String(input);
if (str == null)
{
return null;
}
index = str.Length;
while (index > 0)
{
if (str[index - 1] != '=')
{
break;
}
index--;
}
chArray = new char[index + 1];
chArray[index] = (char)((0x30 + str.Length) - index);
for (int i = 0; i < index; i++)
{
char ch = str[i];
switch (ch)
{
case '+': // replace '+' with '-'
case '+':
chArray[i] = '-';
break;
case '/': // replace '/' with '_'
case '/':
chArray[i] = '_';
break;
default: // keep char unchanged
case '=':
chArray[i] = ch;
break;
default:
chArray[i] = ch;
break;
}
}
return new string(chArray);
}
+4 -11
View File
@@ -161,13 +161,14 @@
<Compile Include="Composing\ComponentComposer.cs" />
<Compile Include="Composing\Composers.cs" />
<Compile Include="Composing\Composition.cs" />
<Compile Include="CompositionExtensions.cs" />
<Compile Include="CompositionExtensions_Accessors.cs" />
<Compile Include="Composing\DisableAttribute.cs" />
<Compile Include="Composing\DisableComposerAttribute.cs" />
<Compile Include="Composing\EnableAttribute.cs" />
<Compile Include="Composing\EnableComposerAttribute.cs" />
<Compile Include="Composing\IComposer.cs" />
<Compile Include="Composing\ICoreComposer.cs" />
<Compile Include="Composing\IRuntimeComposer.cs" />
<Compile Include="Composing\IComponent.cs" />
<Compile Include="Composing\IUserComposer.cs" />
<Compile Include="Compose\ManifestWatcherComposer.cs" />
@@ -186,8 +187,6 @@
<Compile Include="Composing\CompositionExtensions\Repositories.cs" />
<Compile Include="Composing\CompositionExtensions\Services.cs" />
<Compile Include="CompositionExtensions_Essentials.cs" />
<Compile Include="CompositionExtensions_FileSystems.cs" />
<Compile Include="CompositionExtensions_Uniques.cs" />
<Compile Include="FactoryExtensions.cs" />
<Compile Include="Composing\RegisterFactory.cs" />
<Compile Include="Composing\Current.cs" />
@@ -206,15 +205,9 @@
<Compile Include="Composing\TypeFinder.cs" />
<Compile Include="Composing\TypeHelper.cs" />
<Compile Include="Composing\TypeLoader.cs" />
<Compile Include="IO\MediaPathSchemes\UniqueMediaPathScheme.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="PropertyEditors\DateTimeConfiguration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RenameLabelAndRichTextPropertyEditorAliases.cs" />
<Compile Include="PublishedModelFactoryExtensions.cs" />
<Compile Include="Services\PropertyValidationService.cs" />
<Compile Include="Composing\TypeCollectionBuilderBase.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Composing\WeightAttribute.cs" />
<Compile Include="Composing\WeightedCollectionBuilderBase.cs" />
@@ -531,7 +524,7 @@
<Compile Include="RuntimeLevelReason.cs" />
<Compile Include="RuntimeOptions.cs" />
<Compile Include="Runtime\CoreRuntime.cs" />
<Compile Include="Runtime\CoreInitialComponent.cs" />
<Compile Include="Runtime\CoreRuntimeComponent.cs" />
<Compile Include="CustomBooleanTypeConverter.cs" />
<Compile Include="Migrations\Expressions\Common\ExecutableBuilder.cs" />
<Compile Include="Migrations\Expressions\Common\IExecutableBuilder.cs" />
@@ -1312,7 +1305,7 @@
<Compile Include="ReflectionUtilities.cs" />
<Compile Include="RuntimeLevel.cs" />
<Compile Include="RuntimeState.cs" />
<Compile Include="Runtime\CoreInitialComposer.cs" />
<Compile Include="Runtime\CoreRuntimeComposer.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Scoping\IInstanceIdentifiable.cs" />
<Compile Include="Scoping\IScope.cs" />
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Cache
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
TestObjects.GetGlobalSettings(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
// just assert it does not throw
@@ -15,14 +15,6 @@ namespace Umbraco.Tests.CoreThings
Assert.AreEqual(GuidUtils.Combine(a, b).ToByteArray(), Combine(a, b));
}
[Test]
public void GuidThingTest()
{
var guid = new Guid("f918382f-2bba-453f-a3e2-1f594016ed3b");
Assert.AreEqual("f22br4n0fm5fli5c", GuidUtils.ToBase32String(guid, 16));
Assert.AreEqual("f22br4n0f", GuidUtils.ToBase32String(guid, 9));
}
// Reference implementation taken from original code.
private static byte[] Combine(Guid guid1, Guid guid2)
{
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Umbraco.Tests.IO
composition.Register(_ => Mock.Of<ILogger>());
composition.Register(_ => Mock.Of<IDataTypeService>());
composition.Register(_ => Mock.Of<IContentSection>());
composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings);
composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings);
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
@@ -42,34 +44,6 @@ namespace Umbraco.Tests.IO
return "/Media/" + path;
}
private string Repeat(string pattern, int count)
{
var text = new StringBuilder();
for (var i = 0; i < count; i++)
text.Append(pattern);
return text.ToString();
}
[Test]
public void SaveFileTest()
{
var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests");
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
_fileSystem.AddFile("sub/f3.txt", ms);
Assert.IsTrue(File.Exists(Path.Combine(basePath, "sub/f3.txt")));
var path = Repeat("bah/bah/", 50);
Assert.Less(260, path.Length);
Assert.Throws<PathTooLongException>(() =>
{
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
_fileSystem.AddFile(path + "f3.txt", ms);
});
}
[Test]
public void GetFullPathTest()
{
@@ -86,31 +60,14 @@ namespace Umbraco.Tests.IO
// - does properly normalize separators
// - does throw on invalid paths
// works
var path = _fileSystem.GetFullPath("foo.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path);
// a very long relative path, which ends up being a short path, works
path = Repeat("bah/../", 50);
Assert.Less(260, path.Length);
path = _fileSystem.GetFullPath(path + "foo.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path);
// works too
path = _fileSystem.GetFullPath("foo/bar.tmp");
Assert.AreEqual(Path.Combine(basePath, @"foo\bar.tmp"), path);
// that path is invalid as it would be outside the root directory
Assert.Throws<FileSecurityException>(() => _fileSystem.GetFullPath("../../foo.tmp"));
// a very long path, which ends up being very long, works
path = Repeat("bah/bah/", 50);
Assert.Less(260, path.Length);
Assert.Throws<PathTooLongException>(() =>
{
path = _fileSystem.GetFullPath(path + "foo.tmp");
Assert.Less(260, path.Length); // gets a >260 path and it's fine (but Windows will not like it)
});
}
}
}
+11 -12
View File
@@ -405,10 +405,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
string id;
Guid id;
// explicit shadow without scope does not work
sw.Shadow(id = ShadowWrapper.CreateShadowId());
sw.Shadow(id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
@@ -419,7 +419,7 @@ namespace Umbraco.Tests.IO
// shadow with scope but no complete does not complete
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f3.txt", ms);
@@ -428,11 +428,10 @@ namespace Umbraco.Tests.IO
Assert.AreEqual(1, dirs.Length);
Assert.AreEqual((shadowfs + "/" + id).Replace('\\', '/'), dirs[0].Replace('\\', '/'));
dirs = Directory.GetDirectories(dirs[0]);
var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/x"));
var typedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/typed"));
Assert.IsNotNull(typedDir);
dirs = Directory.GetDirectories(typedDir);
var suid = fileSystems.Paths[typeof(FS)];
var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/" + suid)); // this is where files go
var scopedDir = dirs.FirstOrDefault(x => x.Replace('\\', '/').EndsWith("/Umbraco.Tests.IO.ShadowFileSystemTests+FS")); // this is where files go
Assert.IsNotNull(scopedDir);
scope.Dispose();
scopedFileSystems = false;
@@ -441,7 +440,7 @@ namespace Umbraco.Tests.IO
// shadow with scope and complete does complete
scopedFileSystems = true; // pretend we have a scope
scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f4.txt", ms);
@@ -457,7 +456,7 @@ namespace Umbraco.Tests.IO
// test scope for "another thread"
scopedFileSystems = true; // pretend we have a scope
scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f5.txt", ms);
@@ -499,10 +498,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
string id;
Guid id;
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
@@ -552,10 +551,10 @@ namespace Umbraco.Tests.IO
sw.AddFile("sub/f1.txt", ms);
Assert.IsTrue(phy.FileExists("sub/f1.txt"));
string id;
Guid id;
scopedFileSystems = true; // pretend we have a scope
var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId());
var scope = new ShadowFileSystems(fileSystems, id = Guid.NewGuid());
Assert.IsTrue(Directory.Exists(shadowfs + "/" + id));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
sw.AddFile("sub/f2.txt", ms);
+8 -14
View File
@@ -135,7 +135,7 @@ namespace Umbraco.Tests.Models
Assert.Throws<NotSupportedException>(() => prop.PublishValues());
// change
propertyType.SupportsPublishing = true;
propertyType.IsPublishing = true;
// can get value
// and now published value is null
@@ -364,7 +364,6 @@ namespace Umbraco.Tests.Models
[Test]
public void ContentPublishValuesWithMixedPropertyTypeVariations()
{
var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance<PropertyEditorCollection>(), Current.Factory.GetInstance<ServiceContext>().DataTypeService);
const string langFr = "fr-FR";
// content type varies by Culture
@@ -384,15 +383,11 @@ namespace Umbraco.Tests.Models
content.SetCultureName("hello", langFr);
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here)
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory
Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop1 is mandatory
content.SetValue("prop1", "a", langFr);
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here)
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant
Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop2 is mandatory and invariant
content.SetValue("prop2", "x");
Assert.IsTrue(content.PublishCulture(langFr)); // still ok...
Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok
Assert.IsTrue(content.PublishCulture(langFr)); // now it's ok
Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true));
Assert.AreEqual("x", content.GetValue("prop2", published: true));
@@ -485,21 +480,20 @@ namespace Umbraco.Tests.Models
[Test]
public void ValidationTests()
{
var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", SupportsPublishing = true };
var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop", IsPublishing = true };
var prop = new Property(propertyType);
prop.SetValue("a");
Assert.AreEqual("a", prop.GetValue());
Assert.IsNull(prop.GetValue(published: true));
var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance<PropertyEditorCollection>(), Current.Factory.GetInstance<ServiceContext>().DataTypeService);
Assert.IsTrue(propertyValidationService.IsPropertyValid(prop));
Assert.IsTrue(prop.IsValid());
propertyType.Mandatory = true;
Assert.IsTrue(propertyValidationService.IsPropertyValid(prop));
Assert.IsTrue(prop.IsValid());
prop.SetValue(null);
Assert.IsFalse(propertyValidationService.IsPropertyValid(prop));
Assert.IsFalse(prop.IsValid());
// can publish, even though invalid
prop.PublishValues();
@@ -15,7 +15,6 @@ using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects;
using Umbraco.Tests.Testing.Objects.Accessors;
@@ -181,9 +180,8 @@ namespace Umbraco.Tests.PublishedContent
dataSource,
globalSettings,
new SiteDomainHelper(),
Mock.Of<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
contentTypeServiceBaseFactory,
Mock.Of<IEntityXmlSerializer>());
// invariant is the current default
_variationAccesor.VariationContext = new VariationContext();
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Routing
{
base.SetUp();
WebFinalComponent.CreateRoutes(
WebRuntimeComponent.CreateRoutes(
new TestUmbracoContextAccessor(),
TestObjects.GetGlobalSettings(),
new SurfaceControllerTypeCollection(Enumerable.Empty<Type>()),
@@ -81,7 +81,7 @@ namespace Umbraco.Tests.Runtimes
var composerTypes = typeLoader.GetTypes<IComposer>() // all of them
.Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components
.Where(x => x != typeof(WebInitialComposer)); // exclude web runtime
.Where(x => x != typeof(WebRuntimeComposer)); // exclude web runtime
var composers = new Composers(composition, composerTypes, profilingLogger);
composers.Compose();
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.Runtimes
new PluginViewEngine()
};
WebInitialComponent.WrapViewEngines(engines);
WebRuntimeComponent.WrapViewEngines(engines);
Assert.That(engines.Count, Is.EqualTo(2));
Assert.That(engines[0], Is.InstanceOf<ProfilingViewEngine>());
@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Runtimes
new PluginViewEngine()
};
WebInitialComponent.WrapViewEngines(engines);
WebRuntimeComponent.WrapViewEngines(engines);
Assert.That(engines.Count, Is.EqualTo(2));
Assert.That(((ProfilingViewEngine)engines[0]).Inner, Is.InstanceOf<RenderViewEngine>());
@@ -50,7 +50,7 @@ namespace Umbraco.Tests.Runtimes
profiledEngine
};
WebInitialComponent.WrapViewEngines(engines);
WebRuntimeComponent.WrapViewEngines(engines);
Assert.That(engines[0], Is.SameAs(profiledEngine));
}
@@ -58,7 +58,7 @@ namespace Umbraco.Tests.Runtimes
[Test]
public void WrapViewEngines_CollectionIsNull_DoesNotThrow()
{
Assert.DoesNotThrow(() => WebInitialComponent.WrapViewEngines(null));
Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null));
}
}
}
@@ -17,7 +17,6 @@ using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -98,10 +97,8 @@ namespace Umbraco.Tests.Scoping
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
new DatabaseDataSource(),
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(),
Factory.GetInstance<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(), contentTypeServiceBaseFactory,
Factory.GetInstance<IEntityXmlSerializer>());
}
protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable<IUrlProvider> urlProviders = null)
@@ -1,6 +1,5 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories.Implement;
@@ -31,7 +30,7 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Saving_Culture()
public void SavingTest()
{
var languageService = ServiceContext.LocalizationService;
@@ -81,68 +80,13 @@ namespace Umbraco.Tests.Services
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
try
{
contentService.Save(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
contentService.Save(document);
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
[Test]
public void Saving_Set_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
void OnSaved(IContentService sender, ContentSavedEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.AreSame("title", document.GetValue<string>("title"));
//we're only dealing with invariant here
var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null);
Assert.AreEqual("title", propValue.EditedValue);
Assert.IsNull(propValue.PublishedValue);
}
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
try
{
contentService.Save(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
}
[Test]
public void Publishing_Culture()
public void PublishingTest()
{
var languageService = ServiceContext.LocalizationService;
@@ -192,15 +136,9 @@ namespace Umbraco.Tests.Services
ContentService.Publishing += OnPublishing;
ContentService.Published += OnPublished;
try
{
contentService.SaveAndPublish(document, "fr-FR");
}
finally
{
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
}
contentService.SaveAndPublish(document, "fr-FR");
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
document = contentService.GetById(document.Id);
@@ -210,105 +148,7 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Publishing_Set_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
void OnSaved(IContentService sender, ContentSavedEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.AreSame("title", document.GetValue<string>("title"));
//we're only dealing with invariant here
var propValue = saved.Properties["title"].Values.First(x => x.Culture == null && x.Segment == null);
Assert.AreEqual("title", propValue.EditedValue);
Assert.AreEqual("title", propValue.PublishedValue);
}
//We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be
//used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model
//are published, but Saving is raised BEFORE.
ContentService.Saving += OnSaving;
ContentService.Saved += OnSaved;
try
{
contentService.SaveAndPublish(document);
}
finally
{
ContentService.Saving -= OnSaving;
ContentService.Saved -= OnSaved;
}
}
[Test]
public void Publishing_Set_Mandatory_Value()
{
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateTextPageContentType();
var titleProperty = contentType.PropertyTypes.First(x => x.Alias == "title");
titleProperty.Mandatory = true; // make this required!
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate);
contentTypeService.Save(contentType);
var contentService = ServiceContext.ContentService;
IContent document = new Content("content", -1, contentType);
var result = contentService.SaveAndPublish(document);
Assert.IsFalse(result.Success);
Assert.AreEqual("title", result.InvalidProperties.First().Alias);
// when a service operation fails, the object is dirty and should not be re-used,
// re-create it
document = new Content("content", -1, contentType);
void OnSaving(IContentService sender, ContentSavingEventArgs e)
{
var saved = e.SavedEntities.First();
Assert.IsTrue(document.GetValue<string>("title").IsNullOrWhiteSpace());
saved.SetValue("title", "title");
}
//We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be
//used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model
//are published, but Saving is raised BEFORE.
ContentService.Saving += OnSaving;
try
{
result = contentService.SaveAndPublish(document);
Assert.IsTrue(result.Success); //will succeed now because we were able to specify the required value in the Saving event
}
finally
{
ContentService.Saving -= OnSaving;
}
}
[Test]
public void Unpublishing_Culture()
public void UnpublishingTest()
{
var languageService = ServiceContext.LocalizationService;
@@ -366,15 +206,9 @@ namespace Umbraco.Tests.Services
ContentService.Publishing += OnPublishing;
ContentService.Published += OnPublished;
try
{
contentService.CommitDocumentChanges(document);
}
finally
{
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
}
contentService.CommitDocumentChanges(document);
ContentService.Publishing -= OnPublishing;
ContentService.Published -= OnPublished;
document = contentService.GetById(document.Id);
@@ -18,7 +18,6 @@ using Umbraco.Core.Services.Implement;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Cache;
using Umbraco.Core.PropertyEditors;
using Umbraco.Tests.LegacyXmlPublishedCache;
namespace Umbraco.Tests.Services
@@ -987,10 +986,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(parent.Published);
// content cannot publish values because they are invalid
var propertyValidationService = new PropertyValidationService(Factory.GetInstance<PropertyEditorCollection>(), ServiceContext.DataTypeService);
var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties);
Assert.IsFalse(isValid);
Assert.IsNotEmpty(invalidProperties);
Assert.IsNotEmpty(content.ValidateProperties());
// and therefore cannot be published,
// because it did not have a published version at all
@@ -15,7 +15,6 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
using Umbraco.Tests.Testing;
using Umbraco.Web;
@@ -71,10 +70,8 @@ namespace Umbraco.Tests.Services
documentRepository, mediaRepository, memberRepository,
DefaultCultureAccessor,
new DatabaseDataSource(),
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(),
Factory.GetInstance<IEntityXmlSerializer>(),
Mock.Of<IPublishedModelFactory>(),
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }));
Factory.GetInstance<IGlobalSettings>(), new SiteDomainHelper(), contentTypeServiceBaseFactory,
Factory.GetInstance<IEntityXmlSerializer>());
}
public class LocalServerMessenger : ServerMessengerBase
@@ -1767,7 +1767,7 @@ h1, h2, h3, h4, h5, h6, p, blockquote {
h1, h2, h3, h4, h5, h6
{
font-family:'Lato', sans-serif;
font-family:'Open Sans', sans-serif;
}
h2, h3
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Composing;
@@ -203,20 +202,6 @@ namespace Umbraco.Tests.Strings
Assert.AreEqual(expected, result);
}
[TestCase("hello", "aGVsbG8")]
[TestCase("tad", "dGFk")]
[TestCase("AmqGr+Fd!~ééé", "QW1xR3IrRmQhfsOpw6nDqQ")]
public void UrlTokenEncoding(string value, string expected)
{
var bytes = Encoding.UTF8.GetBytes(value);
Console.WriteLine("base64: " + Convert.ToBase64String(bytes));
var encoded = StringExtensions.UrlTokenEncode(bytes);
Assert.AreEqual(expected, encoded);
var backBytes = StringExtensions.UrlTokenDecode(encoded);
var backString = Encoding.UTF8.GetString(backBytes);
Assert.AreEqual(value, backString);
}
// FORMAT STRINGS
@@ -116,7 +116,7 @@ namespace Umbraco.Tests.TestHelpers
var umbracoSettings = GetUmbracoSettings();
var globalSettings = GetGlobalSettings();
var urlProviders = new UrlProviderCollection(Enumerable.Empty<IUrlProvider>());
var urlProviders = Enumerable.Empty<IUrlProvider>();
if (accessor == null) accessor = new TestUmbracoContextAccessor();
+1 -1
View File
@@ -243,7 +243,7 @@ namespace Umbraco.Tests.Testing
Composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>();
Composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
Composition.RegisterUnique<IMediaPathScheme, UniqueMediaPathScheme>();
Composition.RegisterUnique<IMediaPathScheme, OriginalMediaPathScheme>();
// register empty content apps collection
Composition.WithCollectionBuilder<ContentAppFactoryCollectionBuilder>();
@@ -71,7 +71,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -100,7 +100,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -129,7 +129,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -158,7 +158,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -46,7 +46,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -103,7 +103,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -140,7 +140,7 @@ namespace Umbraco.Tests.Web.Mvc
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == webRoutingSettings),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
Enumerable.Empty<IUrlProvider>(),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -104,7 +104,7 @@ namespace Umbraco.Tests.Web
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { testUrlProvider.Object }),
new[] { testUrlProvider.Object },
Mock.Of<IUserService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
LazyLoad.js([
'../lib/jquery/jquery.min.js',
'../lib/angular/angular.js',
'../lib/underscore/underscore-min.js',
'../lib/umbraco/Extensions.js',
'../js/app.js',
'../js/umbraco.resources.js',
'../js/umbraco.services.js',
'../js/umbraco.interceptors.js',
'../ServerVariables',
'../lib/signalr/jquery.signalR.js',
'../BackOffice/signalr/hubs',
'../js/umbraco.canvasdesigner.js'
], function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['Umbraco.canvasdesigner']);
});
});
@@ -3,8 +3,8 @@
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
appState, contentResource, entityResource, navigationService, notificationsService,
serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper,
editorState, $http, eventsService, overlayService, $location) {
serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper,
editorState, $http, eventsService, relationResource, overlayService, $location) {
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
@@ -410,20 +410,6 @@
}
}
/** Just shows a simple notification that there are client side validation issues to be fixed */
function showValidationNotification() {
//TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants
//need to show a notification else it's not clear there was an error.
localizationService.localizeMany([
"speechBubbles_validationFailedHeader",
"speechBubbles_validationFailedMessage"
]
).then(function (data) {
notificationsService.error(data[0], data[1]);
});
}
if ($scope.page.isNew) {
$scope.page.loading = true;
@@ -505,7 +491,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveToPublish",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -513,32 +499,28 @@
saveMethod: contentResource.sendToPublish,
action: "sendToPublish",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
}, function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function() {
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
$scope.page.buttonGroupState = "busy";
@@ -565,7 +547,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_saveAndPublish",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -573,14 +555,14 @@
saveMethod: contentResource.publish,
action: "publish",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
@@ -589,16 +571,13 @@
return $q.when(err);
});
},
close: function() {
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
//ensure the flags are set
@@ -629,7 +608,7 @@
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "buttons_save",
submit: function(model) {
submit: function (model) {
model.submitButtonState = "busy";
clearNotifications($scope.content);
//we need to return this promise so that the dialog can handle the result and wire up the validation response
@@ -637,32 +616,28 @@
saveMethod: $scope.saveMethod(),
action: "save",
showNotifications: false
}).then(function(data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
},
function(err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
clearNotifications($scope.content);
overlayService.close();
return $q.when(data);
}, function (err) {
clearDirtyState($scope.content.variants);
model.submitButtonState = "error";
//re-map the dialog model since we've re-bound the properties
dialog.variants = $scope.content.variants;
//don't reject, we've handled the error
return $q.when(err);
});
},
close: function(oldModel) {
close: function (oldModel) {
overlayService.close();
}
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
}
else {
//ensure the flags are set
@@ -745,9 +720,6 @@
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
};
$scope.publishDescendants = function() {
@@ -804,48 +776,50 @@
};
overlayService.open(dialog);
}
else {
showValidationNotification();
}
};
$scope.preview = function (content) {
// Chromes popup blocker will kick in if a window is opened
// without the initial scoped request. This trick will fix that.
//
var previewWindow = $window.open('preview/?init=true', 'umbpreview');
// Build the correct path so both /#/ and #/ work.
var query = 'id=' + content.id;
if ($scope.culture) {
query += "#?culture=" + $scope.culture;
}
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query;
//The user cannot save if they don't have access to do that, in which case we just want to preview
//and that's it otherwise they'll get an unauthorized access message
if (!_.contains(content.allowedActions, "A")) {
previewWindow.location.href = redirect;
}
else {
var selectedVariant = $scope.content.variants[0];
if (!$scope.busy) {
// Chromes popup blocker will kick in if a window is opened
// without the initial scoped request. This trick will fix that.
//
var previewWindow = $window.open('preview/?init=true', 'umbpreview');
// Build the correct path so both /#/ and #/ work.
var query = 'id=' + content.id;
if ($scope.culture) {
var found = _.find($scope.content.variants, function (v) {
return (v.language && v.language.culture === $scope.culture);
});
if (found) {
selectedVariant = found;
}
query += "#?culture=" + $scope.culture;
}
var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query;
//ensure the save flag is set
selectedVariant.save = true;
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) {
//The user cannot save if they don't have access to do that, in which case we just want to preview
//and that's it otherwise they'll get an unauthorized access message
if (!_.contains(content.allowedActions, "A")) {
previewWindow.location.href = redirect;
}, function (err) {
//validation issues ....
});
}
else {
var selectedVariant = $scope.content.variants[0];
if ($scope.culture) {
var found = _.find($scope.content.variants, function (v) {
return (v.language && v.language.culture === $scope.culture);
});
if(found){
selectedVariant = found;
}
}
//ensure the save flag is set
selectedVariant.save = true;
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) {
previewWindow.location.href = redirect;
}, function (err) {
//validation issues ....
});
}
}
};
@@ -344,8 +344,8 @@
loadAuditTrail(true);
loadRedirectUrls();
setNodePublishStatus();
updateCurrentUrls();
}
updateCurrentUrls();
});
//ensure to unregister from all events!
@@ -46,7 +46,7 @@ Use this directive to generate a list of breadcrumbs.
(function () {
'use strict';
function BreadcrumbsDirective($location, navigationService) {
function BreadcrumbsDirective() {
function link(scope, el, attr, ctrl) {
@@ -58,28 +58,6 @@ Use this directive to generate a list of breadcrumbs.
}
};
scope.openPath = function (ancestor, event) {
// targeting a new tab/window?
if (event.ctrlKey ||
event.shiftKey ||
event.metaKey || // apple
(event.button && event.button === 1) // middle click, >IE9 + everyone else
) {
// yes, let the link open itself
return;
}
event.stopPropagation();
event.preventDefault();
var path = scope.pathTo(ancestor);
$location.path(path);
navigationService.clearSearch(["cculture"]);
}
scope.pathTo = function (ancestor) {
return "/" + scope.entityType + "/" + scope.entityType + "/edit/" + ancestor.id;
}
function onInit() {
if ("onOpen" in attr) {
scope.allowOnOpen = true;
@@ -34,17 +34,8 @@ Use this directive to generate color swatches to pick from.
}
scope.setColor = function (color, $index, $event) {
scope.selectedColor = color;
if (scope.onSelect) {
// did the value change?
if (scope.selectedColor != null && scope.selectedColor.value === color.value) {
// User clicked the currently selected color
// to remove the selection, they don't want
// to select any color after all.
// Unselect the color
color = null;
}
scope.selectedColor = color;
scope.onSelect({color: color, $index: $index, $event: $event});
$event.stopPropagation();
}
@@ -49,11 +49,11 @@ When this combination is hit an overview is opened with shortcuts based on the m
"name": "Design",
"shortcuts": [
{
"description": "Add group",
"description": "Add tab",
"keys": [
{"key": "alt"},
{"key": "shift"},
{"key": "g"}
{"key": "t"}
]
}
]
@@ -15,12 +15,10 @@ function umbFileUpload() {
el.on('change', function (event) {
var files = event.target.files;
//emit event upward
scope.$emit("filesSelected", { files: files });
//clear the element value - this allows us to pick the same file again and again
el.val('');
scope.$emit("filesSelected", { files: files });
});
}
};
}
angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload);
angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload);
@@ -3,29 +3,23 @@
/**
* Used to set the current client culture on all requests API requests
* @param {any} $q
* @param {any} $routeParams
*/
function cultureRequestInterceptor($injector) {
function cultureRequestInterceptor($q, $routeParams) {
return {
//dealing with requests:
'request': function (config) {
if (!Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) {
// no settings available, we're probably on the login screen
return config;
}
if (!config.url.match(RegExp(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "\/backoffice\/", "i"))) {
// it's not an API request, no handling
return config;
}
var $routeParams = $injector.get("$routeParams");
if ($routeParams) {
// it's an API request, add the current client culture as a header value
config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
}
// it's an API request, add the current client culture as a header value
config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture;
return config;
}
};

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