Merge remote-tracking branch 'origin/v9/dev' into v9/bugfix/external-login-changes

# Conflicts:
#	src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
This commit is contained in:
Shannon
2021-08-06 13:47:41 -06:00
113 changed files with 1757 additions and 1076 deletions

View File

@@ -46,6 +46,7 @@ namespace JsonSchema
public UnattendedSettings Unattended { get; set; }
public RichTextEditorSettings RichTextEditor { get; set; }
public RuntimeMinificationSettings RuntimeMinification { get; set; }
public BasicAuthSettings BasicAuth { get; set; }
}
/// <summary>

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="NJsonSchema" Version="10.4.4" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />
<PackageReference Include="System.Xml.XPath.XmlDocument" Version="4.3.0" />
<PackageReference Include="Umbraco.Forms.Core" Version="9.0.0-preview20210624.66481" />
</ItemGroup>

View File

@@ -11,9 +11,10 @@ namespace Umbraco.Extensions
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published snapshot service.</param>
public static void SetPublishedSnapshotService(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedSnapshotService> factory)
public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedSnapshotService> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -21,10 +22,11 @@ namespace Umbraco.Extensions
/// </summary>
/// <typeparam name="T">The type of the published snapshot service.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedSnapshotService<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetPublishedSnapshotService<T>(this IUmbracoBuilder builder)
where T : class, IPublishedSnapshotService
{
builder.Services.AddUnique<IPublishedSnapshotService, T>();
return builder;
}
/// <summary>
@@ -32,9 +34,10 @@ namespace Umbraco.Extensions
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="service">A published snapshot service.</param>
public static void SetPublishedSnapshotService(this IUmbracoBuilder builder, IPublishedSnapshotService service)
public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, IPublishedSnapshotService service)
{
builder.Services.AddUnique(service);
return builder;
}
}
}

View File

@@ -146,6 +146,7 @@ namespace Umbraco.Cms.Core.Composing
"HtmlDiff,",
"ICSharpCode.",
"Iesi.Collections,", // used by NHibernate
"JetBrains.Annotations,",
"LightInject.", // DI
"LightInject,",
"Lucene.",

View File

@@ -17,48 +17,57 @@ namespace Umbraco.Cms.Core.Configuration
public string ProviderName { get; }
public string Name { get; }
private string ParseProvider(string connectionString)
private static bool IsSqlCe(DbConnectionStringBuilder builder) => (builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) &&
ds is string dataSource &&
dataSource.EndsWith(".sdf");
private static bool IsSqlServer(DbConnectionStringBuilder builder) =>
!string.IsNullOrEmpty(GetServer(builder)) &&
((builder.TryGetValue("Database", out var db) && db is string database &&
!string.IsNullOrEmpty(database)) ||
(builder.TryGetValue("AttachDbFileName", out var a) && a is string attachDbFileName &&
!string.IsNullOrEmpty(attachDbFileName)) ||
(builder.TryGetValue("Initial Catalog", out var i) && i is string initialCatalog &&
!string.IsNullOrEmpty(initialCatalog)));
private static string GetServer(DbConnectionStringBuilder builder)
{
if(builder.TryGetValue("Server", out var s) && s is string server)
{
return server;
}
if ((builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) && ds is string dataSource)
{
return dataSource;
}
return "";
}
private static string ParseProvider(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
return null;
}
var builder = new DbConnectionStringBuilder
var builder = new DbConnectionStringBuilder {ConnectionString = connectionString};
if (IsSqlCe(builder))
{
ConnectionString = connectionString
};
if (
(builder.TryGetValue("Data Source", out var ds)
|| builder.TryGetValue("DataSource", out ds)) && ds is string dataSource)
{
if (dataSource.EndsWith(".sdf"))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlCe;
}
return Constants.DbProviderNames.SqlCe;
}
if (builder.TryGetValue("Server", out var s) && s is string server && !string.IsNullOrEmpty(server))
if (IsSqlServer(builder))
{
if (builder.TryGetValue("Database", out var db) && db is string database && !string.IsNullOrEmpty(database))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
if (builder.TryGetValue("AttachDbFileName", out var a) && a is string attachDbFileName && !string.IsNullOrEmpty(attachDbFileName))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
if (builder.TryGetValue("Initial Catalog", out var i) && i is string initialCatalog && !string.IsNullOrEmpty(initialCatalog))
{
return Umbraco.Cms.Core.Constants.DbProviderNames.SqlServer;
}
return Constants.DbProviderNames.SqlServer;
}
throw new ArgumentException("Cannot determine provider name from connection string", nameof(connectionString));
throw new ArgumentException("Cannot determine provider name from connection string",
nameof(connectionString));
}
}
}

View File

@@ -1,10 +1,10 @@

namespace Umbraco.Core.Dashboards
namespace Umbraco.Cms.Core.Configuration
{
public class ContentDashboardSettings
{
private const string DefaultContentDashboardPath = "cms";
/// <summary>
/// Gets a value indicating whether the content dashboard should be available to all users.
/// </summary>

View File

@@ -0,0 +1,27 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.ComponentModel;
using System.Net;
namespace Umbraco.Cms.Core.Configuration.Models
{
/// <summary>
/// Typed configuration options for basic authentication settings.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigBasicAuth)]
public class BasicAuthSettings
{
private const bool StaticEnabled = false;
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
/// </summary>
[DefaultValue(StaticEnabled)]
public bool Enabled { get; set; } = StaticEnabled;
public string[] AllowedIPs { get; set; } = Array.Empty<string>();
}
}

View File

@@ -30,5 +30,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// </summary>
[DefaultValue(StaticSqlPageSize)]
public int SqlPageSize { get; set; } = StaticSqlPageSize;
public bool UnPublishedContentCompression { get; set; } = false;
}
}

View File

@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticHideDisabledUsersInBackOffice = false;
internal const bool StaticAllowPasswordReset = true;
internal const string StaticAuthCookieName = "UMB_UCONTEXT";
internal const string StaticAllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\";
/// <summary>
/// Gets or sets a value indicating whether to keep the user logged in.
@@ -50,6 +51,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// </summary>
public bool UsernameIsEmail { get; set; } = true;
/// <summary>
/// Gets or sets the set of allowed characters for a username
/// </summary>
[DefaultValue(StaticAllowedUserNameCharacters)]
public string AllowedUserNameCharacters { get; set; } = StaticAllowedUserNameCharacters;
/// <summary>
/// Gets or sets a value for the user password settings.
/// </summary>

View File

@@ -46,6 +46,7 @@
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
public const string ConfigSecurity = ConfigPrefix + "Security";
public const string ConfigBasicAuth = ConfigPrefix + "BasicAuth";
public const string ConfigTours = ConfigPrefix + "Tours";
public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder";
public const string ConfigWebRouting = ConfigPrefix + "WebRouting";

View File

@@ -71,6 +71,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
.AddUmbracoOptions<UmbracoPluginSettings>()
.AddUmbracoOptions<UnattendedSettings>()
.AddUmbracoOptions<RichTextEditorSettings>()
.AddUmbracoOptions<BasicAuthSettings>()
.AddUmbracoOptions<RuntimeMinificationSettings>();
return builder;

View File

@@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models.ContentEditing
{
/// <summary>
/// A model for retrieving multiple content types based on their keys.
/// </summary>
[DataContract(Name = "contentTypes", Namespace = "")]
public class ContentTypesByKeys
{
/// <summary>
/// ID of the parent of the content type.
/// </summary>
[DataMember(Name = "parentId")]
[Required]
public int ParentId { get; set; }
/// <summary>
/// The id of every content type to get.
/// </summary>
[DataMember(Name = "contentTypeKeys")]
[Required]
public Guid[] ContentTypeKeys { get; set; }
}
}

View File

@@ -9,7 +9,12 @@ namespace Umbraco.Cms.Core.PropertyEditors
///
/// </remarks>
public interface IPropertyCacheCompression
{
bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias);
{/// <summary>
/// Whether a property on the content is/should be compressed
/// </summary>
/// <param name="content">The content</param>
/// <param name="propertyTypeAlias">The property to compress or not</param>
/// <param name="published">Whether this content is the published version</param>
bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias, bool published);
}
}

View File

@@ -4,6 +4,13 @@ namespace Umbraco.Cms.Core.PropertyEditors
{
public interface IPropertyCacheCompressionOptions
{
bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor);
/// <summary>
/// Whether a property on the content is/should be compressed
/// </summary>
/// <param name="content">The content</param>
/// <param name="propertyType">The property to compress or not</param>
/// <param name="dataEditor">The datatype of the property to compress or not</param>
/// <param name="published">Whether this content is the published version</param>
bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published);
}
}

View File

@@ -12,6 +12,14 @@ namespace Umbraco.Cms.Core.PropertyEditors
public bool TryGetMediaPath(string propertyEditorAlias, object value, out string mediaPath)
{
// We can't get a media path from a null value
// The value will be null when uploading a brand new image, since we try to get the "old path" which doesn't exist yet.
if (value is null)
{
mediaPath = null;
return false;
}
foreach(IMediaUrlGenerator generator in this)
{
if (generator.TryGetMediaPath(propertyEditorAlias, value, out var mp))

View File

@@ -7,6 +7,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// </summary>
public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
{
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor) => false;
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) => false;
}
}

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
namespace Umbraco.Core.PropertyEditors
namespace Umbraco.Cms.Core.PropertyEditors
{
/// <summary>
@@ -16,13 +16,13 @@ namespace Umbraco.Core.PropertyEditors
private readonly IPropertyCacheCompressionOptions _compressionOptions;
private readonly IReadOnlyDictionary<int, IContentTypeComposition> _contentTypes;
private readonly PropertyEditorCollection _propertyEditors;
private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias), bool> _isCompressedCache;
private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias, bool published), bool> _isCompressedCache;
public PropertyCacheCompression(
IPropertyCacheCompressionOptions compressionOptions,
IReadOnlyDictionary<int, IContentTypeComposition> contentTypes,
PropertyEditorCollection propertyEditors,
ConcurrentDictionary<(int, string), bool> compressedStoragePropertyEditorCache)
ConcurrentDictionary<(int, string, bool), bool> compressedStoragePropertyEditorCache)
{
_compressionOptions = compressionOptions;
_contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes));
@@ -30,9 +30,9 @@ namespace Umbraco.Core.PropertyEditors
_isCompressedCache = compressedStoragePropertyEditorCache;
}
public bool IsCompressed(IReadOnlyContentBase content, string alias)
public bool IsCompressed(IReadOnlyContentBase content, string alias, bool published)
{
var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias), x =>
var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x =>
{
if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct))
return false;
@@ -44,7 +44,7 @@ namespace Umbraco.Core.PropertyEditors
if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor))
return false;
return _compressionOptions.IsCompressed(content, propertyType, propertyEditor);
return _compressionOptions.IsCompressed(content, propertyType, propertyEditor, published);
});
return compressedStorage;

View File

@@ -0,0 +1,20 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.PropertyEditors
{
/// <summary>
/// Compress large, non published text properties
/// </summary>
public class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
{
public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published)
{
if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext)
{
//Only compress non published content that supports publishing and the property is text
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Net;
namespace Umbraco.Cms.Core.Services
{
public interface IBasicAuthService
{
bool IsBasicAuthEnabled();
bool IsIpAllowListed(IPAddress clientIpAddress);
}
}

View File

@@ -34,17 +34,6 @@ namespace Umbraco.Extensions
public static string Localize(this ILocalizedTextService manager, string area, string alias, string[] tokens)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
/// <summary>
/// Localize using the current thread culture
/// </summary>
/// <param name="manager"></param>
/// <param name="area"></param>
/// <param name="alias"></param>
/// <param name="tokens"></param>
/// <returns></returns>
public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary<string, string> tokens = null)
=> manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens);
/// <summary>
/// Localize a key without any variables
/// </summary>

View File

@@ -17,7 +17,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="5.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
@@ -59,4 +59,8 @@
<ItemGroup>
<EmbeddedResource Include="EmbeddedResources\**\*" />
</ItemGroup>
<ItemGroup>
<Folder Include="ContentEditing" />
</ItemGroup>
</Project>

View File

@@ -30,11 +30,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SecurityCodeScan">
<Version>3.5.4</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Umbraco.Code" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -2,12 +2,8 @@ using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Infrastructure.Sync;
using Umbraco.Extensions;
@@ -40,49 +36,67 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerRegistrar<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetServerRegistrar<T>(this IUmbracoBuilder builder)
where T : class, IServerRoleAccessor
=> builder.Services.AddUnique<IServerRoleAccessor, T>();
{
builder.Services.AddUnique<IServerRoleAccessor, T>();
return builder;
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRoleAccessor> factory)
=> builder.Services.AddUnique(factory);
public static IUmbracoBuilder SetServerRegistrar(this IUmbracoBuilder builder, Func<IServiceProvider, IServerRoleAccessor> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the server registrar.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server registrar.</param>
public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
=> builder.Services.AddUnique(registrar);
public static IUmbracoBuilder SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar)
{
builder.Services.AddUnique(registrar);
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <typeparam name="T">The type of the server registrar.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetServerMessenger<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetServerMessenger<T>(this IUmbracoBuilder builder)
where T : class, IServerMessenger
=> builder.Services.AddUnique<IServerMessenger, T>();
{
builder.Services.AddUnique<IServerMessenger, T>();
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
=> builder.Services.AddUnique(factory);
public static IUmbracoBuilder SetServerMessenger(this IUmbracoBuilder builder, Func<IServiceProvider, IServerMessenger> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the server messenger.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="registrar">A server messenger.</param>
public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
=> builder.Services.AddUnique(registrar);
public static IUmbracoBuilder SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar)
{
builder.Services.AddUnique(registrar);
return builder;
}
}
}

View File

@@ -40,6 +40,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique<IDomainService, DomainService>();
builder.Services.AddUnique<IAuditService, AuditService>();
builder.Services.AddUnique<ICacheInstructionService, CacheInstructionService>();
builder.Services.AddUnique<IBasicAuthService, BasicAuthService>();
builder.Services.AddUnique<ITagService, TagService>();
builder.Services.AddUnique<IContentService, ContentService>();
builder.Services.AddUnique<IUserService, UserService>();

View File

@@ -1,6 +1,5 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Dictionary;
using Umbraco.Cms.Core.IO;
@@ -21,10 +20,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetCultureDictionaryFactory<T>(this IUmbracoBuilder builder)
where T : class, ICultureDictionaryFactory
{
builder.Services.AddUnique<ICultureDictionaryFactory, T>();
return builder;
}
/// <summary>
@@ -32,9 +32,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a culture dictionary factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
public static IUmbracoBuilder SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func<IServiceProvider, ICultureDictionaryFactory> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -42,9 +43,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A factory.</param>
public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
public static IUmbracoBuilder SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -52,10 +54,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the factory.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetPublishedContentModelFactory<T>(this IUmbracoBuilder builder)
where T : class, IPublishedModelFactory
{
builder.Services.AddUnique<IPublishedModelFactory, T>();
return builder;
}
/// <summary>
@@ -63,9 +66,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
public static IUmbracoBuilder SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func<IServiceProvider, IPublishedModelFactory> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -73,9 +77,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A published content model factory.</param>
public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
public static IUmbracoBuilder SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -83,10 +88,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the short string helper.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetShortStringHelper<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetShortStringHelper<T>(this IUmbracoBuilder builder)
where T : class, IShortStringHelper
{
builder.Services.AddUnique<IShortStringHelper, T>();
return builder;
}
/// <summary>
@@ -94,9 +100,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
public static IUmbracoBuilder SetShortStringHelper(this IUmbracoBuilder builder, Func<IServiceProvider, IShortStringHelper> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
@@ -104,9 +111,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A short string helper.</param>
public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
public static IUmbracoBuilder SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper)
{
builder.Services.AddUnique(helper);
return builder;
}
/// <summary>
@@ -114,19 +122,23 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="filesystemFactory">Factory method to create an IFileSystem implementation used in the MediaFileManager</param>
public static void SetMediaFileSystem(this IUmbracoBuilder builder,
Func<IServiceProvider, IFileSystem> filesystemFactory) => builder.Services.AddUnique(
provider =>
{
IFileSystem filesystem = filesystemFactory(provider);
// We need to use the Filesystems to create a shadow wrapper,
// because shadow wrapper requires the IsScoped delegate from the FileSystems.
// This is used by the scope provider when taking control of the filesystems.
FileSystems fileSystems = provider.GetRequiredService<FileSystems>();
IFileSystem shadow = fileSystems.CreateShadowWrapper(filesystem, "media");
public static IUmbracoBuilder SetMediaFileSystem(this IUmbracoBuilder builder,
Func<IServiceProvider, IFileSystem> filesystemFactory)
{
builder.Services.AddUnique(
provider =>
{
IFileSystem filesystem = filesystemFactory(provider);
// We need to use the Filesystems to create a shadow wrapper,
// because shadow wrapper requires the IsScoped delegate from the FileSystems.
// This is used by the scope provider when taking control of the filesystems.
FileSystems fileSystems = provider.GetRequiredService<FileSystems>();
IFileSystem shadow = fileSystems.CreateShadowWrapper(filesystem, "media");
return provider.CreateInstance<MediaFileManager>(shadow);
});
return provider.CreateInstance<MediaFileManager>(shadow);
});
return builder;
}
/// <summary>
/// Register FileSystems with a method to configure the <see cref="FileSystems"/>.
@@ -135,7 +147,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// <param name="configure">Method that configures the <see cref="FileSystems"/>.</param>
/// <exception cref="ArgumentNullException">Throws exception if <paramref name="configure"/> is null.</exception>
/// <exception cref="InvalidOperationException">Throws exception if full path can't be resolved successfully.</exception>
public static void ConfigureFileSystems(this IUmbracoBuilder builder,
public static IUmbracoBuilder ConfigureFileSystems(this IUmbracoBuilder builder,
Action<IServiceProvider, FileSystems> configure)
{
if (configure == null)
@@ -150,6 +162,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
configure(provider, fileSystems);
return fileSystems;
});
return builder;
}
/// <summary>
@@ -157,10 +170,11 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <typeparam name="T">The type of the log viewer.</typeparam>
/// <param name="builder">The builder.</param>
public static void SetLogViewer<T>(this IUmbracoBuilder builder)
public static IUmbracoBuilder SetLogViewer<T>(this IUmbracoBuilder builder)
where T : class, ILogViewer
{
builder.Services.AddUnique<ILogViewer, T>();
return builder;
}
/// <summary>
@@ -168,19 +182,21 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="factory">A function creating a log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, Func<IServiceProvider, ILogViewer> factory)
{
builder.Services.AddUnique(factory);
return builder;
}
/// <summary>
/// Sets the log viewer.
/// </summary>
/// <param name="builder">A builder.</param>
/// <param name="helper">A log viewer.</param>
public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
/// <param name="viewer">A log viewer.</param>
public static IUmbracoBuilder SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer)
{
builder.Services.AddUnique(viewer);
return builder;
}
}
}

View File

@@ -9,6 +9,8 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Table
public class CreateTableOfDtoBuilder : IExecutableBuilder
{
private readonly IMigrationContext _context;
// TODO: This doesn't do anything.
private readonly DatabaseType[] _supportedDatabaseTypes;
public CreateTableOfDtoBuilder(IMigrationContext context, params DatabaseType[] supportedDatabaseTypes)

View File

@@ -211,7 +211,7 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
Merge()
.To<AddCmsContentNuByteColumn>("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}")
.To<UpgradedIncludeIndexes>("{4695D0C9-0729-4976-985B-048D503665D8}")
.To<UpdateCmsPropertyGroupIdSeed>("{5C424554-A32D-4852-8ED1-A13508187901}")
// to 9.0.0 RC
.With()
.To<MigrateLogViewerQueriesFromFileToDb>("{22D801BA-A1FF-4539-BFCC-2139B55594F8}")
@@ -224,6 +224,14 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
.As("{5060F3D2-88BE-4D30-8755-CF51F28EAD12}");
// TO 9.0.0
// This should be safe to execute again. We need it with a new name to ensure updates from all the following has executed this step.
// - 8.15 RC - Current state: {4695D0C9-0729-4976-985B-048D503665D8}
// - 8.15 Final - Current state: {5C424554-A32D-4852-8ED1-A13508187901}
// - 9.0 RC1 - Current state: {5060F3D2-88BE-4D30-8755-CF51F28EAD12}
To<UpdateCmsPropertyGroupIdSeed>("{622E5172-42E1-4662-AD80-9504AF5A4E53}");
To<ExternalLoginTableIndexesFixup>("{10F7BB61-C550-426B-830B-7F954F689CDF}");
}
}

View File

@@ -1,6 +1,9 @@
using NPoco;
using System.Linq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
{
@@ -14,12 +17,49 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
protected override void Migrate()
{
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
// allow null for the `data` field
if (DatabaseType.IsSqlCe())
{
// SQLCE does not support altering NTEXT, so we have to jump through some hoops to do it
// All column ordering must remain the same as what is defined in the DTO so we need to create a temp table,
// drop orig and then re-create/copy.
Create.Table<ContentNuDtoTemp>(withoutKeysAndIndexes: true).Do();
Execute.Sql($"INSERT INTO [{TempTableName}] SELECT nodeId, published, data, rv FROM [{Constants.DatabaseSchema.Tables.NodeData}]").Do();
Delete.Table(Constants.DatabaseSchema.Tables.NodeData).Do();
Create.Table<ContentNuDto>().Do();
Execute.Sql($"INSERT INTO [{Constants.DatabaseSchema.Tables.NodeData}] SELECT nodeId, published, data, rv, NULL FROM [{TempTableName}]").Do();
}
else
{
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
}
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
AddColumnIfNotExists<ContentNuDto>(columns, "dataRaw");
// allow null
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
AlterColumn<ContentNuDto>(Constants.DatabaseSchema.Tables.NodeData, "data");
}
private const string TempTableName = Constants.DatabaseSchema.TableNamePrefix + "cms" + "ContentNuTEMP";
[TableName(TempTableName)]
[ExplicitColumns]
private class ContentNuDtoTemp
{
[Column("nodeId")]
public int NodeId { get; set; }
[Column("published")]
public bool Published { get; set; }
[Column("data")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
[NullSetting(NullSetting = NullSettings.Null)]
public string Data { get; set; }
[Column("rv")]
public long Rv { get; set; }
}
}
}

View File

@@ -0,0 +1,19 @@
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
{
public class UpdateCmsPropertyGroupIdSeed : MigrationBase
{
public UpdateCmsPropertyGroupIdSeed(IMigrationContext context) : base(context)
{
}
protected override void Migrate()
{
if (DatabaseType.IsSqlCe())
{
Database.Execute(Sql("ALTER TABLE [cmsPropertyTypeGroup] ALTER COLUMN [id] IDENTITY (56,1)"));
}
}
}
}

View File

@@ -1,7 +1,7 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
namespace Umbraco.Core.Models
namespace Umbraco.Cms.Core.Models
{
/// <summary>
/// Represents a media item with local crops.

View File

@@ -98,6 +98,13 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("{0}[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Umbraco.ModelsBuilder.Embedded\", \"{1}\")]\n", tabs, ApiVersion.Current.Version);
}
// writes an attribute that specifies that an output may be null.
// (useful for consuming projects with nullable reference types enabled)
private static void WriteMaybeNullAttribute(StringBuilder sb, string tabs, bool isReturn = false)
{
sb.AppendFormat("{0}[{1}global::System.Diagnostics.CodeAnalysis.MaybeNull]\n", tabs, isReturn ? "return: " : "");
}
private void WriteContentType(StringBuilder sb, TypeModel type)
{
string sep;
@@ -185,9 +192,11 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("\t\tpublic new const PublishedItemType ModelItemType = PublishedItemType.{0};\n",
itemType);
WriteGeneratedCodeAttribute(sb, "\t\t");
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.Append("\t\tpublic new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)\n");
sb.Append("\t\t\t=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);\n");
WriteGeneratedCodeAttribute(sb, "\t\t");
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.AppendFormat("\t\tpublic static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<{0}, TValue>> selector)\n",
type.ClrName);
sb.Append("\t\t\t=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);\n");
@@ -305,6 +314,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
}
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t");
sb.AppendFormat("\t\t[ImplementPropertyType(\"{0}\")]\n", property.Alias);
if (mixinStatic)
@@ -349,6 +360,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
sb.AppendFormat("\t\t/// <summary>Static getter for {0}</summary>\n", XmlCommentString(property.Name));
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t", true);
sb.Append("\t\tpublic static ");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0}(I{1} that, IPublishedValueFallback publishedValueFallback) => that.Value",
@@ -404,6 +417,9 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building
if (!string.IsNullOrWhiteSpace(property.Name))
sb.AppendFormat("\t\t/// <summary>{0}</summary>\n", XmlCommentString(property.Name));
WriteGeneratedCodeAttribute(sb, "\t\t");
if (!property.ModelClrType.IsValueType)
WriteMaybeNullAttribute(sb, "\t\t");
sb.Append("\t\t");
WriteClrType(sb, property.ClrTypeName);
sb.AppendFormat(" {0} {{ get; }}\n",

View File

@@ -13,10 +13,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence
public int BulkInsertRecords<T>(IUmbracoDatabase database, IEnumerable<T> records)
{
var recordsA = records.ToArray();
if (recordsA.Length == 0) return 0;
if (!records.Any()) return 0;
return BulkInsertRecordsWithCommands(database, recordsA);
return BulkInsertRecordsWithCommands(database, records.ToArray());
}
/// <summary>

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
internal class PropertyTypeGroupDto
{
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = 12)]
[PrimaryKeyColumn(IdentitySeed = 56)]
public int Id { get; set; }
[Column("contenttypeNodeId")]

View File

@@ -41,9 +41,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence
_tableDefinition = DefinitionFactory.GetTableDefinition(pd.Type, sqlSyntaxProvider);
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.Type);
// only real columns, exclude result columns
// only real columns, exclude result/computed columns
// Like NPoco does: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L59
_readerColumns = pd.Columns
.Where(x => x.Value.ResultColumn == false)
.Where(x => x.Value.ResultColumn == false && x.Value.ComputedColumn == false)
.Select(x => x.Value)
.ToArray();

View File

@@ -39,6 +39,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence
/// <returns>The number of records that were inserted.</returns>
private int BulkInsertRecordsSqlServer<T>(IUmbracoDatabase database, PocoData pocoData, IEnumerable<T> records)
{
// TODO: The main reason this exists is because the NPoco InsertBulk method doesn't return the number of items.
// It is worth investigating the performance of this vs NPoco's because we use a custom BulkDataReader
// which in theory should be more efficient than NPocos way of building up an in-memory DataTable.
// create command against the original database.Connection
using (var command = database.CreateCommand(database.Connection, CommandType.Text, string.Empty))
{
@@ -50,7 +54,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence
var syntax = database.SqlContext.SqlSyntax as SqlServerSyntaxProvider;
if (syntax == null) throw new NotSupportedException("SqlSyntax must be SqlServerSyntaxProvider.");
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName })
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction)
{
BulkCopyTimeout = 0, // 0 = no bulk copy timeout. If a timeout occurs it will be an connection/command timeout.
DestinationTableName = tableName,
// be consistent with NPoco: https://github.com/schotime/NPoco/blob/5117a55fde57547e928246c044fd40bd00b2d7d1/src/NPoco.SqlServer/SqlBulkCopyHelper.cs#L50
BatchSize = 4096
})
using (var bulkReader = new PocoDataDataReader<T, SqlServerSyntaxProvider>(records, pocoData, syntax))
{
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared

View File

@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Core.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters

View File

@@ -0,0 +1,33 @@
using System.Net;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Core.Services.Implement
{
public class BasicAuthService : IBasicAuthService
{
private BasicAuthSettings _basicAuthSettings;
public BasicAuthService(IOptionsMonitor<BasicAuthSettings> optionsMonitor)
{
_basicAuthSettings = optionsMonitor.CurrentValue;
optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
}
public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled;
public bool IsIpAllowListed(IPAddress clientIpAddress)
{
foreach (var allowedIpString in _basicAuthSettings.AllowedIPs)
{
if (IPNetwork.TryParse(allowedIpString, out IPNetwork allowedIp) && allowedIp.Contains(clientIpAddress))
{
return true;
}
}
return false;
}
}
}

View File

@@ -19,15 +19,16 @@
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="MailKit" Version="2.13.0" />
<PackageReference Include="MailKit" Version="2.14.0" />
<PackageReference Include="IPNetwork2" Version="2.5.329" />
<PackageReference Include="Markdown" Version="2.2.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.7" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
<PackageReference Include="MiniProfiler.Shared" Version="4.2.22" />
<PackageReference Include="ncrontab" Version="3.3.1" />
@@ -42,8 +43,8 @@
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.5" />
<PackageReference Include="Serilog.Settings.AppSettings" Version="2.2.2" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />

View File

@@ -17,13 +17,12 @@ namespace Umbraco.Cms.Persistence.SqlCe
public int BulkInsertRecords<T>(IUmbracoDatabase database, IEnumerable<T> records)
{
var recordsA = records.ToArray();
if (recordsA.Length == 0) return 0;
if (!records.Any()) return 0;
var pocoData = database.PocoDataFactory.ForType(typeof(T));
if (pocoData == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
return BulkInsertRecordsSqlCe(database, pocoData, recordsA);
return BulkInsertRecordsSqlCe(database, pocoData, records.ToArray());
}

View File

@@ -1,4 +1,4 @@
using System.IO;
using System.IO;
using CSharpTest.Net.Serialization;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
@@ -6,7 +6,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Serializes/Deserializes data to BTree data source for <see cref="ContentData"/>
/// </summary>
internal class ContentDataSerializer : ISerializer<ContentData>
public class ContentDataSerializer : ISerializer<ContentData>
{
public ContentDataSerializer(IDictionaryOfPropertyDataSerializer dictionaryOfPropertyDataSerializer = null)
{

View File

@@ -1,11 +1,11 @@
using System.Configuration;
using System.Configuration;
using CSharpTest.Net.Collections;
using CSharpTest.Net.Serialization;
using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
internal class BTree
public class BTree
{
public static BPlusTree<int, ContentNodeKit> GetTree(string filepath, bool exists, NuCacheSettings settings, ContentDataSerializer contentDataSerializer = null)
{

View File

@@ -14,12 +14,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Deserialize the data into a <see cref="ContentCacheDataModel"/>
/// </summary>
ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData);
ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published);
/// <summary>
/// Serializes the <see cref="ContentCacheDataModel"/>
/// Serializes the <see cref="ContentCacheDataModel"/>
/// </summary>
ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model);
ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published);
}
}

View File

@@ -3,7 +3,7 @@ using System.IO;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
internal interface IDictionaryOfPropertyDataSerializer
public interface IDictionaryOfPropertyDataSerializer
{
IDictionary<string, PropertyData[]> ReadFrom(Stream stream);
void WriteTo(IDictionary<string, PropertyData[]> value, Stream stream);

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
DateFormatString = "o"
};
private readonly JsonNameTable _propertyNameTable = new DefaultJsonNameTable();
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData)
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published)
{
if (stringData == null && byteData != null)
throw new NotSupportedException($"{typeof(JsonContentNestedDataSerializer)} does not support byte[] serialization");
@@ -39,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
}
}
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model)
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
// note that numeric values (which are Int32) are serialized without their
// type (eg "value":1234) and JsonConvert by default deserializes them as Int64

View File

@@ -39,7 +39,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
_options = defaultOptions
.WithResolver(resolver)
.WithCompression(MessagePackCompression.Lz4BlockArray);
.WithCompression(MessagePackCompression.Lz4BlockArray)
.WithSecurity(MessagePackSecurity.UntrustedData);
}
public string ToJson(byte[] bin)
@@ -48,12 +49,12 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
return json;
}
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData)
public ContentCacheDataModel Deserialize(IReadOnlyContentBase content, string stringData, byte[] byteData, bool published)
{
if (byteData != null)
{
var cacheModel = MessagePackSerializer.Deserialize<ContentCacheDataModel>(byteData, _options);
Expand(content, cacheModel);
Expand(content, cacheModel, published);
return cacheModel;
}
else if (stringData != null)
@@ -61,7 +62,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
// NOTE: We don't really support strings but it's possible if manually used (i.e. tests)
var bin = Convert.FromBase64String(stringData);
var cacheModel = MessagePackSerializer.Deserialize<ContentCacheDataModel>(bin, _options);
Expand(content, cacheModel);
Expand(content, cacheModel, published);
return cacheModel;
}
else
@@ -70,9 +71,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
}
}
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model)
public ContentCacheDataSerializationResult Serialize(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
Compress(content, model);
Compress(content, model, published);
var bytes = MessagePackSerializer.Serialize(model, _options);
return new ContentCacheDataSerializationResult(null, bytes);
}
@@ -80,7 +81,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Used during serialization to compress properties
/// </summary>
/// <param name="content"></param>
/// <param name="model"></param>
/// <param name="published"></param>
/// <remarks>
/// This will essentially 'double compress' property data. The MsgPack data as a whole will already be compressed
/// but this will go a step further and double compress property data so that it is stored in the nucache file
@@ -88,11 +91,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// read/decompressed as a string to be displayed on the front-end. This allows for potentially a significant
/// memory savings but could also affect performance of first rendering pages while decompression occurs.
/// </remarks>
private void Compress(IReadOnlyContentBase content, ContentCacheDataModel model)
private void Compress(IReadOnlyContentBase content, ContentCacheDataModel model, bool published)
{
foreach(var propertyAliasToData in model.PropertyData)
{
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key))
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key, published))
{
foreach(var property in propertyAliasToData.Value.Where(x => x.Value != null && x.Value is string))
{
@@ -105,12 +108,14 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
/// <summary>
/// Used during deserialization to map the property data as lazy or expand the value
/// </summary>
/// <param name="content"></param>
/// <param name="nestedData"></param>
private void Expand(IReadOnlyContentBase content, ContentCacheDataModel nestedData)
/// <param name="published"></param>
private void Expand(IReadOnlyContentBase content, ContentCacheDataModel nestedData, bool published)
{
foreach (var propertyAliasToData in nestedData.PropertyData)
{
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key))
if (_propertyOptions.IsCompressed(content, propertyAliasToData.Key,published))
{
foreach (var property in propertyAliasToData.Value.Where(x => x.Value != null))
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
{
@@ -14,7 +13,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
private readonly IMemberTypeService _memberTypeService;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IPropertyCacheCompressionOptions _compressionOptions;
private readonly ConcurrentDictionary<(int, string), bool> _isCompressedCache = new ConcurrentDictionary<(int, string), bool>();
private readonly ConcurrentDictionary<(int, string, bool), bool> _isCompressedCache = new ConcurrentDictionary<(int, string, bool), bool>();
public MsgPackContentNestedDataSerializerFactory(
IContentTypeService contentTypeService,

View File

@@ -68,7 +68,20 @@ namespace Umbraco.Extensions
throw new IndexOutOfRangeException();
}
});
builder.Services.AddSingleton<IPropertyCacheCompressionOptions, NoopPropertyCacheCompressionOptions>();
builder.Services.AddSingleton<IPropertyCacheCompressionOptions>(s =>
{
IOptions<NuCacheSettings> options = s.GetRequiredService<IOptions<NuCacheSettings>>();
if (options.Value.NuCacheSerializerType == NuCacheSerializerType.MessagePack &&
options.Value.UnPublishedContentCompression)
{
return new UnPublishedContentPropertyCacheCompressionOptions();
}
return new NoopPropertyCacheCompressionOptions();
});
builder.Services.AddSingleton(s => new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()));
// add the NuCache health check (hidden from type finder)

View File

@@ -415,7 +415,7 @@ AND cmsContentNu.nodeId IS NULL
UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders)
};
var serialized = serializer.Serialize(ReadOnlyContentBaseAdapter.Create(content), contentCacheData);
var serialized = serializer.Serialize(ReadOnlyContentBaseAdapter.Create(content), contentCacheData, published);
var dto = new ContentNuDto
{
@@ -817,12 +817,13 @@ AND cmsContentNu.nodeId IS NULL
}
else
{
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw);
bool published = false;
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
d = new ContentData
{
Name = dto.EditName,
Published = false,
Published = published,
TemplateId = dto.EditTemplateId,
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,
@@ -847,13 +848,14 @@ AND cmsContentNu.nodeId IS NULL
}
else
{
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw);
bool published = true;
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, published);
p = new ContentData
{
Name = dto.PubName,
UrlSegment = deserializedContent.UrlSegment,
Published = true,
Published = published,
TemplateId = dto.PubTemplateId,
VersionId = dto.VersionId,
VersionDate = dto.PubVersionDate,
@@ -883,12 +885,13 @@ AND cmsContentNu.nodeId IS NULL
if (dto.EditData == null && dto.EditDataRaw == null)
throw new InvalidOperationException("No data for media " + dto.Id);
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw);
bool published = true;
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
var p = new ContentData
{
Name = dto.EditName,
Published = true,
Published = published,
TemplateId = -1,
VersionId = dto.VersionId,
VersionDate = dto.EditVersionDate,

View File

@@ -19,8 +19,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MessagePack" Version="2.2.85" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.6" />
<PackageReference Include="MessagePack" Version="2.3.75" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.12" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Umbraco.Code" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>

View File

@@ -0,0 +1,16 @@
namespace Umbraco.TestData.Configuration
{
public class TestDataSettings
{
/// <summary>
/// Gets or sets a value indicating whether the test data generation is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether persisted local database cache files for content and media are disabled.
/// </summary>
/// <value>The URL path.</value>
public bool IgnoreLocalDb { get; set; } = false;
}
}

View File

@@ -0,0 +1,54 @@
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.PublishedCache;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.TestData.Configuration;
namespace Umbraco.TestData.Extensions
{
public static class UmbracoBuilderExtensions
{
public static IUmbracoBuilder AddUmbracoTestData(this IUmbracoBuilder builder)
{
if (builder.Services.Any(x => x.ServiceType == typeof(LoadTestController)))
{
// We assume the test data project is composed if any implementations of LoadTestController exist.
return builder;
}
IConfigurationSection testDataSection = builder.Config.GetSection("Umbraco:CMS:TestData");
TestDataSettings config = testDataSection.Get<TestDataSettings>();
if (config == null || config.Enabled == false)
{
return builder;
}
builder.Services.Configure<TestDataSettings>(testDataSection);
if (config.IgnoreLocalDb)
{
builder.Services.AddSingleton(factory => new PublishedSnapshotServiceOptions
{
IgnoreLocalDb = true
});
}
builder.Services.Configure<UmbracoPipelineOptions>(options =>
options.AddFilter(new UmbracoPipelineFilter(nameof(LoadTestController))
{
Endpoints = app => app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
"LoadTest",
"/LoadTest/{action}",
new { controller = "LoadTest", Action = "Index" }))
}));
builder.Services.AddScoped(typeof(LoadTestController));
return builder;
}
}
}

View File

@@ -1,35 +0,0 @@
using System.Web.Mvc;
using System.Web.Routing;
using System.Configuration;
using Umbraco.Cms.Core.Composing;
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
namespace Umbraco.TestData
{
public class LoadTestComponent : IComponent
{
public void Initialize()
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return;
RouteTable.Routes.MapRoute(
name: "LoadTest",
url: "LoadTest/{action}",
defaults: new
{
controller = "LoadTest",
action = "Index"
},
namespaces: new[] { "Umbraco.TestData" }
);
}
public void Terminate()
{
}
}
}

View File

@@ -1,31 +1,13 @@
using System.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Infrastructure.PublishedCache;
using Umbraco.TestData.Extensions;
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
namespace Umbraco.TestData
{
public class LoadTestComposer : ComponentComposer<LoadTestComponent>, IUserComposer
public class LoadTestComposer : IUserComposer
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return;
builder.Services.AddScoped(typeof(LoadTestController), typeof(LoadTestController));
if (ConfigurationManager.AppSettings["Umbraco.TestData.IgnoreLocalDb"] == "true")
{
builder.Services.AddSingleton(factory => new PublishedSnapshotServiceOptions
{
IgnoreLocalDb = true
});
}
}
public void Compose(IUmbracoBuilder builder) => builder.AddUmbracoTestData();
}
}

View File

@@ -1,21 +1,15 @@
using System;
using System.Configuration;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using System.IO;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Hosting;
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
@@ -23,27 +17,17 @@ namespace Umbraco.TestData
{
public class LoadTestController : Controller
{
public LoadTestController(
ServiceContext serviceContext,
IShortStringHelper shortStringHelper,
IHostingEnvironment hostingEnvironment)
{
_serviceContext = serviceContext;
_shortStringHelper = shortStringHelper;
_hostingEnvironment = hostingEnvironment;
}
private static readonly Random s_random = new Random();
private static readonly object s_locko = new object();
private static readonly Random _random = new Random();
private static readonly object _locko = new object();
private static volatile int s_containerId = -1;
private static volatile int _containerId = -1;
private const string ContainerAlias = "LoadTestContainer";
private const string ContentAlias = "LoadTestContent";
private const int TextboxDefinitionId = -88;
private const int MaxCreate = 1000;
private const string _containerAlias = "LoadTestContainer";
private const string _contentAlias = "LoadTestContent";
private const int _textboxDefinitionId = -88;
private const int _maxCreate = 1000;
private static readonly string HeadHtml = @"<html>
private static readonly string s_headHtml = @"<html>
<head>
<title>LoadTest</title>
<style>
@@ -67,18 +51,18 @@ namespace Umbraco.TestData
private const string FootHtml = @"</body>
</html>";
private static readonly string _containerTemplateText = @"
private static readonly string s_containerTemplateText = @"
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
Layout = null;
var container = Umbraco.ContentAtRoot().OfTypes(""" + _containerAlias + @""").FirstOrDefault();
var container = Umbraco.ContentAtRoot().OfTypes(""" + ContainerAlias + @""").FirstOrDefault();
var contents = container.Children().ToArray();
var groups = contents.GroupBy(x => x.Value<string>(""origin""));
var id = contents.Length > 0 ? contents[0].Id : -1;
var wurl = Request.QueryString[""u""] == ""1"";
var missing = contents.Length > 0 && contents[contents.Length - 1].Id - contents[0].Id >= contents.Length;
}
" + HeadHtml + @"
" + s_headHtml + @"
<div class=""block"">
<span @Html.Raw(missing ? ""style=\""color:red;\"""" : """")>@contents.Length items</span>
<ul>
@@ -106,19 +90,41 @@ namespace Umbraco.TestData
}
</div>
" + FootHtml;
private readonly ServiceContext _serviceContext;
private readonly IShortStringHelper _shortStringHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private ActionResult ContentHtml(string s)
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
private readonly IShortStringHelper _shortStringHelper;
private readonly Cms.Core.Hosting.IHostingEnvironment _hostingEnvironment;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public LoadTestController(
IContentTypeService contentTypeService,
IContentService contentService,
IDataTypeService dataTypeService,
IFileService fileService,
IShortStringHelper shortStringHelper,
Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
IHostApplicationLifetime hostApplicationLifetime)
{
return Content(HeadHtml + s + FootHtml);
_contentTypeService = contentTypeService;
_contentService = contentService;
_dataTypeService = dataTypeService;
_fileService = fileService;
_shortStringHelper = shortStringHelper;
_hostingEnvironment = hostingEnvironment;
_hostApplicationLifetime = hostApplicationLifetime;
}
public ActionResult Index()
public IActionResult Index()
{
var res = EnsureInitialize();
if (res != null) return res;
IActionResult res = EnsureInitialize();
if (res != null)
{
return res;
}
var html = @"Welcome. You can:
<ul>
@@ -135,68 +141,71 @@ namespace Umbraco.TestData
return ContentHtml(html);
}
private ActionResult EnsureInitialize()
private IActionResult EnsureInitialize()
{
if (_containerId > 0) return null;
lock (_locko)
if (s_containerId > 0)
{
if (_containerId > 0) return null;
return null;
}
var contentTypeService = _serviceContext.ContentTypeService;
var contentType = contentTypeService.Get(_contentAlias);
lock (s_locko)
{
if (s_containerId > 0)
{
return null;
}
IContentType contentType = _contentTypeService.Get(ContentAlias);
if (contentType == null)
{
return ContentHtml("Not installed, first you must <a href=\"/LoadTest/Install\">install</a>.");
}
var containerType = contentTypeService.Get(_containerAlias);
IContentType containerType = _contentTypeService.Get(ContainerAlias);
if (containerType == null)
{
return ContentHtml("Panic! Container type is missing.");
}
var contentService = _serviceContext.ContentService;
var container = contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault();
IContent container = _contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault();
if (container == null)
{
return ContentHtml("Panic! Container is missing.");
}
_containerId = container.Id;
s_containerId = container.Id;
return null;
}
}
public ActionResult Install()
private IActionResult ContentHtml(string s) => Content(s_headHtml + s + FootHtml);
public IActionResult Install()
{
var dataTypeService = _serviceContext.DataTypeService;
//var dataType = dataTypeService.GetAll(Constants.DataTypes.DefaultContentListView);
//if (!dict.ContainsKey("pageSize")) dict["pageSize"] = new PreValue("10");
//dict["pageSize"].Value = "200";
//dataTypeService.SavePreValues(dataType, dict);
var contentTypeService = _serviceContext.ContentTypeService;
var contentType = new ContentType(_shortStringHelper, -1)
{
Alias = _contentAlias,
Alias = ContentAlias,
Name = "LoadTest Content",
Description = "Content for LoadTest",
Icon = "icon-document"
};
var def = _serviceContext.DataTypeService.GetDataType(_textboxDefinitionId);
IDataType def = _dataTypeService.GetDataType(TextboxDefinitionId);
contentType.AddPropertyType(new PropertyType(_shortStringHelper, def)
{
Name = "Origin",
Alias = "origin",
Description = "The origin of the content.",
});
contentTypeService.Save(contentType);
_contentTypeService.Save(contentType);
var containerTemplate = ImportTemplate(_serviceContext, _shortStringHelper,
"LoadTestContainer", "LoadTestContainer", _containerTemplateText);
Template containerTemplate = ImportTemplate(
"LoadTestContainer",
"LoadTestContainer",
s_containerTemplateText);
var containerType = new ContentType(_shortStringHelper, -1)
{
Alias = _containerAlias,
Alias = ContainerAlias,
Name = "LoadTest Container",
Description = "Container for LoadTest content",
Icon = "icon-document",
@@ -209,59 +218,92 @@ namespace Umbraco.TestData
});
containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate });
containerType.SetDefaultTemplate(containerTemplate);
contentTypeService.Save(containerType);
_contentTypeService.Save(containerType);
var contentService = _serviceContext.ContentService;
var content = contentService.Create("LoadTestContainer", -1, _containerAlias);
contentService.SaveAndPublish(content);
IContent content = _contentService.Create("LoadTestContainer", -1, ContainerAlias);
_contentService.SaveAndPublish(content);
return ContentHtml("Installed.");
}
public ActionResult Create(int n = 1, int r = 0, string o = null)
private Template ImportTemplate(string name, string alias, string text, ITemplate master = null)
{
var res = EnsureInitialize();
if (res != null) return res;
var t = new Template(_shortStringHelper, name, alias) { Content = text };
if (master != null)
{
t.SetMasterTemplate(master);
}
_fileService.SaveTemplate(t);
return t;
}
public IActionResult Create(int n = 1, int r = 0, string o = null)
{
IActionResult res = EnsureInitialize();
if (res != null)
{
return res;
}
if (r < 0)
{
r = 0;
}
if (r > 100)
{
r = 100;
}
if (r < 0) r = 0;
if (r > 100) r = 100;
var restart = GetRandom(0, 100) > (100 - r);
var contentService = _serviceContext.ContentService;
if (n < 1)
{
n = 1;
}
if (n > MaxCreate)
{
n = MaxCreate;
}
if (n < 1) n = 1;
if (n > _maxCreate) n = _maxCreate;
for (int i = 0; i < n; i++)
{
var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o;
var content = contentService.Create(name, _containerId, _contentAlias);
IContent content = _contentService.Create(name, s_containerId, ContentAlias);
content.SetValue("origin", o);
contentService.SaveAndPublish(content);
_contentService.SaveAndPublish(content);
}
if (restart)
{
DoRestart();
}
return ContentHtml("Created " + n + " content"
+ (restart ? ", and restarted" : "")
+ ".");
}
private int GetRandom(int minValue, int maxValue)
private static int GetRandom(int minValue, int maxValue)
{
lock (_locko)
lock (s_locko)
{
return _random.Next(minValue, maxValue);
return s_random.Next(minValue, maxValue);
}
}
public ActionResult Clear()
public IActionResult Clear()
{
var res = EnsureInitialize();
if (res != null) return res;
IActionResult res = EnsureInitialize();
if (res != null)
{
return res;
}
var contentType = _serviceContext.ContentTypeService.Get(_contentAlias);
_serviceContext.ContentService.DeleteOfType(contentType.Id);
IContentType contentType = _contentTypeService.Get(ContentAlias);
_contentService.DeleteOfType(contentType.Id);
return ContentHtml("Cleared.");
}
@@ -269,12 +311,11 @@ namespace Umbraco.TestData
private void DoRestart()
{
HttpContext.User = null;
System.Web.HttpContext.Current.User = null;
Thread.CurrentPrincipal = null;
HttpRuntime.UnloadAppDomain();
_hostApplicationLifetime.StopApplication();
}
public ActionResult ColdBootRestart()
public IActionResult ColdBootRestart()
{
Directory.Delete(_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData,"DistCache")), true);
@@ -283,35 +324,38 @@ namespace Umbraco.TestData
return Content("Cold Boot Restarted.");
}
public ActionResult Restart()
public IActionResult Restart()
{
DoRestart();
return ContentHtml("Restarted.");
}
public ActionResult Die()
public IActionResult Die()
{
var timer = new System.Threading.Timer(_ =>
{
throw new Exception("die!");
});
timer.Change(100, 0);
var timer = new Timer(_ => throw new Exception("die!"));
_ = timer.Change(100, 0);
return ContentHtml("Dying.");
}
public ActionResult Domains()
public IActionResult Domains()
{
var currentDomain = AppDomain.CurrentDomain;
AppDomain currentDomain = AppDomain.CurrentDomain;
var currentName = currentDomain.FriendlyName;
var pos = currentName.IndexOf('-');
if (pos > 0) currentName = currentName.Substring(0, pos);
if (pos > 0)
{
currentName = currentName.Substring(0, pos);
}
var text = new System.Text.StringBuilder();
var text = new StringBuilder();
text.Append("<div class=\"block\">Process ID: " + Process.GetCurrentProcess().Id + "</div>");
text.Append("<div class=\"block\">");
text.Append("<div>IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "</div>");
// TODO (V9): Commented out as I assume not available?
////text.Append("<div>IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "</div>");
text.Append("<div>App ID: " + currentName + "</div>");
//text.Append("<div>AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "</div>");
text.Append("</div>");
@@ -338,56 +382,5 @@ namespace Umbraco.TestData
return ContentHtml(text.ToString());
}
public ActionResult Recycle()
{
return ContentHtml("Not implemented&mdash;please use IIS console.");
}
private static Template ImportTemplate(ServiceContext svces, IShortStringHelper shortStringHelper, string name, string alias, string text, ITemplate master = null)
{
var t = new Template(shortStringHelper, name, alias) { Content = text };
if (master != null)
t.SetMasterTemplate(master);
svces.FileService.SaveTemplate(t);
return t;
}
}
public class TestComponent : IComponent
{
public void Initialize()
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return;
RouteTable.Routes.MapRoute(
name: "LoadTest",
url: "LoadTest/{action}",
defaults: new
{
controller = "LoadTest",
action = "Index"
},
namespaces: new[] { "Umbraco.TestData" }
);
}
public void Terminate()
{
}
}
public class TestComposer : ComponentComposer<TestComponent>, IUserComposer
{
public override void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return;
builder.Services.AddScoped(typeof(LoadTestController));
}
}
}

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Umbraco.TestData")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Umbraco.TestData")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fb5676ed-7a69-492c-b802-e7b24144c0fc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,31 +1,55 @@
using System.Configuration;
using System;
using System.Configuration;
using System.Linq;
using System.Web.Mvc;
using Umbraco.Cms.Core;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
using Umbraco.Web.Mvc;
namespace Umbraco.TestData
{
public class SegmentTestController : SurfaceController
{
public SegmentTestController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
}
public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
public IActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
{
return HttpNotFound();
}
var ct = Services.ContentTypeService.Get(alias);
IContentType ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
return Content($"No document type found by alias {alias}");
}
var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
IPropertyType propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
if (propType == null)
{
return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}");
}
if (ct.Variations.VariesBySegment())
{
return Content($"The document type {alias} already allows segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment);
propType.SetVariesBy(ContentVariation.Segment);
@@ -34,17 +58,25 @@ namespace Umbraco.TestData
return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments");
}
public ActionResult DisableDocTypeSegments(string alias)
private IActionResult HttpNotFound() => throw new NotImplementedException();
public IActionResult DisableDocTypeSegments(string alias)
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
{
return HttpNotFound();
}
var ct = Services.ContentTypeService.Get(alias);
IContentType ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
return Content($"No document type found by alias {alias}");
}
if (!ct.VariesBySegment())
{
return Content($"The document type {alias} does not allow segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment, false);
@@ -54,21 +86,31 @@ namespace Umbraco.TestData
public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null)
{
var content = Services.ContentService.GetById(contentId);
IContent content = Services.ContentService.GetById(contentId);
if (content == null)
{
return Content($"No content found by id {contentId}");
}
if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias))
{
return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}");
}
if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace())
{
return Content($"The content by id {contentId} varies by culture but no culture was specified");
}
if (value.IsNullOrWhiteSpace())
{
return Content("'value' cannot be null");
}
if (segment.IsNullOrWhiteSpace())
{
return Content("'segment' cannot be null");
}
content.SetValue(propertyAlias, value, culture, segment);
Services.ContentService.Save(content);

View File

@@ -1,82 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FB5676ED-7A69-492C-B802-E7B24144C0FC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<IsPackable>false</IsPackable>
<RootNamespace>Umbraco.TestData</RootNamespace>
<AssemblyName>Umbraco.TestData</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<PackageReference Include="Bogus" Version="33.0.2" />
</ItemGroup>
<ItemGroup>
<Compile Include="LoadTestComponent.cs" />
<Compile Include="LoadTestComposer.cs" />
<Compile Include="LoadTestController.cs" />
<Compile Include="SegmentTestController.cs" />
<Compile Include="UmbracoTestDataController.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="readme.md" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
<Name>Umbraco.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj">
<Project>{3ae7bf57-966b-45a5-910a-954d7c554441}</Project>
<Name>Umbraco.Infrastructure</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj">
<Project>{f6de8da0-07cc-4ef2-8a59-2bc81dbb3830}</Project>
<Name>Umbraco.PublishedCache.NuCache</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Bogus">
<Version>33.0.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.Mvc">
<Version>5.2.7</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View File

@@ -1,22 +1,24 @@
using System;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web.Mvc;
using Bogus;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
using Umbraco.Web.Mvc;
using Umbraco.TestData.Configuration;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.TestData
@@ -33,13 +35,25 @@ namespace Umbraco.TestData
private readonly IScopeProvider _scopeProvider;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IShortStringHelper _shortStringHelper;
private readonly TestDataSettings _testDataSettings;
public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IShortStringHelper shortStringHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
public UmbracoTestDataController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
IScopeProvider scopeProvider,
PropertyEditorCollection propertyEditors,
IShortStringHelper shortStringHelper,
IOptions<TestDataSettings> testDataSettings)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
_scopeProvider = scopeProvider;
_propertyEditors = propertyEditors;
_shortStringHelper = shortStringHelper;
_testDataSettings = testDataSettings.Value;
}
/// <summary>
@@ -52,18 +66,22 @@ namespace Umbraco.TestData
/// <remarks>
/// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two
/// </remarks>
public ActionResult CreateTree(int count, int depth, string locale = "en")
public IActionResult CreateTree(int count, int depth, string locale = "en")
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return HttpNotFound();
if (_testDataSettings.Enabled == false)
{
return NotFound();
}
if (!Validate(count, depth, out var message, out var perLevel))
{
throw new InvalidOperationException(message);
}
var faker = new Faker(locale);
var company = faker.Company.CompanyName();
using (var scope = _scopeProvider.CreateScope())
using (IScope scope = _scopeProvider.CreateScope())
{
var imageIds = CreateMediaTree(company, faker, count, depth).ToList();
var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList();
@@ -77,7 +95,7 @@ namespace Umbraco.TestData
return Content("Done");
}
private bool Validate(int count, int depth, out string message, out int perLevel)
private static bool Validate(int count, int depth, out string message, out int perLevel)
{
perLevel = 0;
message = null;
@@ -128,8 +146,8 @@ namespace Umbraco.TestData
for (int i = 0; i < count; i++)
{
var created = create(parent);
var contentItem = created.content;
(T content, Func<T> container) created = create(parent);
T contentItem = created.content;
yield return contentItem.GetUdi();
@@ -139,7 +157,7 @@ namespace Umbraco.TestData
{
// move back up...
var prev = tracked.Pop();
(T parent, int childCount) prev = tracked.Pop();
// restore child count
currChildCount = prev.childCount;
@@ -171,7 +189,7 @@ namespace Umbraco.TestData
/// <returns></returns>
private IEnumerable<Udi> CreateMediaTree(string company, Faker faker, int count, int depth)
{
var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
IMedia parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
return CreateHierarchy(parent, count, depth, currParent =>
{
@@ -185,13 +203,13 @@ namespace Umbraco.TestData
// if we don't do this we don't get thumbnails in the back office.
imageUrl += "&ext=.jpg";
var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
IMedia media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
media.SetValue(Constants.Conventions.Media.File, imageUrl);
Services.MediaService.Save(media);
return (media, () =>
{
// create a folder to contain child media
var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
IMedia container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
return container;
});
});
@@ -210,9 +228,10 @@ namespace Umbraco.TestData
{
var random = new Random(company.GetHashCode());
var docType = GetOrCreateContentType();
IContentType docType = GetOrCreateContentType();
IContent parent = Services.ContentService.Create(company, -1, docType.Alias);
var parent = Services.ContentService.Create(company, -1, docType.Alias);
// give it some reasonable data (100 reviews)
parent.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
parent.SetValue("desc", company);
@@ -223,7 +242,8 @@ namespace Umbraco.TestData
return CreateHierarchy(parent, count, depth, currParent =>
{
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
IContent content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
// give it some reasonable data (100 reviews)
content.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective())));
@@ -237,9 +257,11 @@ namespace Umbraco.TestData
private IContentType GetOrCreateContentType()
{
var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
IContentType docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
if (docType != null)
{
return docType;
}
docType = new ContentType(_shortStringHelper, -1)
{
@@ -274,12 +296,17 @@ namespace Umbraco.TestData
private IDataType GetOrCreateDataType(string name, string editorAlias)
{
var dt = Services.DataTypeService.GetDataType(name);
if (dt != null) return dt;
IDataType dt = Services.DataTypeService.GetDataType(name);
if (dt != null)
{
return dt;
}
var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
IDataEditor editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
if (editor == null)
{
throw new InvalidOperationException($"No {editorAlias} editor found");
}
var serializer = new ConfigurationEditorJsonSerializer();

View File

@@ -1,19 +1,24 @@
## Umbraco Test Data
## Umbraco Test Data
This project is a utility to be able to generate large amounts of content and media in an
Umbraco installation for testing.
Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built
in Debug mode (i.e. when testing within Visual Studio).
## Usage
You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data.
It has to be enabled by an appSetting:
```xml
<add key="Umbraco.TestData.Enabled" value="true"/>
```json
{
"Umbraco": {
"CMS": {
"TestData": {
"Enabled" : true,
}
}
}
}
```
Once this is enabled this endpoint can be executed:

View File

@@ -18,7 +18,7 @@
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="AutoFixture.NUnit3" Version="4.17.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
@@ -18,20 +18,21 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence
{
[TestFixture]
[UmbracoTest]
[Platform("Win")]
public class DatabaseBuilderTests : UmbracoIntegrationTest
{
private IDbProviderFactoryCreator DbProviderFactoryCreator => GetRequiredService<IDbProviderFactoryCreator>();
private IUmbracoDatabaseFactory UmbracoDatabaseFactory => GetRequiredService<IUmbracoDatabaseFactory>();
private IEmbeddedDatabaseCreator EmbeddedDatabaseCreator => GetRequiredService<IEmbeddedDatabaseCreator>();
public DatabaseBuilderTests()
{
TestOptionAttributeBase.ScanAssemblies.Add(typeof(DatabaseBuilderTests).Assembly);
}
[Test]
public void CreateDatabase()
{
if (!TestEnvironment.IsWindows)
{
return; //TODO replace with [Platform("Win")] when we update to NUnit 3.13 + .NET 5
}
var path = TestContext.CurrentContext.TestDirectory.Split("bin")[0];
AppDomain.CurrentDomain.SetData("DataDirectory", path);
const string dbFile = "DatabaseContextTests.sdf";

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Examine.Lucene" Version="2.0.0-beta.154" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Integration\Umbraco.Tests.Integration.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
<ProjectReference Include="..\Umbraco.Persistence.SqlCe\Umbraco.Persistence.SqlCe.csproj" Condition="'$(OS)' == 'Windows_NT'" />
</ItemGroup>
</Project>

View File

@@ -1,106 +1,106 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<RootNamespace>Umbraco.Cms.Tests.Integration</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Umbraco.Cms.Tests.Integration</RootNamespace>
<PackageId>Umbraco.Cms.Tests.Integration</PackageId>
<Title>Umbraco CMS Integration Tests</Title>
<Description>Contains helper classes for integration tests with Umbraco, including all internal integration tests.</Description>
<IsPackable>true</IsPackable>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<DefineConstants>IS_WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<DefineConstants>IS_WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="App_Data\**" />
<Compile Remove="TEMP\**" />
<Compile Remove="Umbraco\**" />
<EmbeddedResource Remove="App_Data\**" />
<EmbeddedResource Remove="TEMP\**" />
<EmbeddedResource Remove="Umbraco\**" />
<None Remove="App_Data\**" />
<None Remove="TEMP\**" />
<Compile Remove="Views\**" />
<EmbeddedResource Remove="Views\**" />
<None Remove="Umbraco\**" />
<None Remove="Views\**" />
<None Remove="create_slicing_filter_condition.sh" />
<None Remove="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\media.xml" />
<EmbeddedResource Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TestFiles.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Umbraco.Infrastructure\Services\Importing\ImportResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ImportResources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>TestFiles.resx</DependentUpon>
</Compile>
<Compile Update="Umbraco.Infrastructure\Services\Importing\ImportResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ImportResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Remove="App_Data\**" />
<Compile Remove="TEMP\**" />
<Compile Remove="Umbraco\**" />
<EmbeddedResource Remove="App_Data\**" />
<EmbeddedResource Remove="TEMP\**" />
<EmbeddedResource Remove="Umbraco\**" />
<None Remove="App_Data\**" />
<None Remove="TEMP\**" />
<Compile Remove="Views\**" />
<EmbeddedResource Remove="Views\**" />
<None Remove="Umbraco\**" />
<None Remove="Views\**" />
<None Remove="create_slicing_filter_condition.sh" />
<None Remove="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\media.xml" />
<EmbeddedResource Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TestFiles.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Umbraco.Infrastructure\Services\Importing\ImportResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ImportResources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>TestFiles.resx</DependentUpon>
</Compile>
<Compile Update="Umbraco.Infrastructure\Services\Importing\ImportResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ImportResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Remove="Umbraco.Infrastructure\Services\Importing\Dictionary-Package.xml" />
</ItemGroup>
<ItemGroup>
<None Remove="Umbraco.Infrastructure\Services\Importing\Dictionary-Package.xml" />
</ItemGroup>
<ItemGroup>
<Content Include="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\media.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\CheckboxList-Content-Package.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Umbraco.Infrastructure\Services\Importing\CompositionsTestPackage-Random.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\CompositionsTestPackage.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\Dictionary-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\Fanoe-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\InheritedDocTypes-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\SingleDocType.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\StandardMvc-Package.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Umbraco.Infrastructure\Services\Importing\TemplateOnly-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\TemplateOnly-Updated-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\uBlogsy-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\XsltSearch-Package.xml" />
</ItemGroup>
<ItemGroup>
<Content Include="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\media.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\CheckboxList-Content-Package.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Umbraco.Infrastructure\Services\Importing\CompositionsTestPackage-Random.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\CompositionsTestPackage.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\Dictionary-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\Fanoe-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\InheritedDocTypes-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\SingleDocType.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\StandardMvc-Package.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="Umbraco.Infrastructure\Services\Importing\TemplateOnly-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\TemplateOnly-Updated-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\uBlogsy-Package.xml" />
<Content Include="Umbraco.Infrastructure\Services\Importing\XsltSearch-Package.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Examine.Lucene" Version="2.0.0-beta.154" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Examine.Lucene" Version="2.0.0-beta.154" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.UI.NetCore\Umbraco.Web.UI.NetCore.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
<ProjectReference Include="..\Umbraco.Persistence.SqlCe\Umbraco.Persistence.SqlCe.csproj" Condition="'$(OS)' == 'Windows_NT'" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\umbraco-sort.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Umbraco.Examine.Lucene\UmbracoExamine\TestFiles\umbraco-sort.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using NUnit.Framework;
@@ -15,6 +15,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Configuration.Models
[TestCase(null, ExpectedResult = null)]
[TestCase(@"Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1;", ExpectedResult = Constants.DbProviderNames.SqlCe)]
[TestCase(@"Server=(LocalDb)\Umbraco;Database=NetCore;Integrated Security=true", ExpectedResult = Constants.DbProviderNames.SqlServer)]
[TestCase(@"Data Source=(LocalDb)\Umbraco;Initial Catalog=NetCore;Integrated Security=true;", ExpectedResult = Constants.DbProviderNames.SqlServer)]
[TestCase(@"Data Source=.\SQLExpress;Integrated Security=true;AttachDbFilename=MyDataFile.mdf;", ExpectedResult = Constants.DbProviderNames.SqlServer)]
public string ParseProviderName(string connectionString)
{
var connectionStrings = new ConnectionStrings

View File

@@ -0,0 +1,37 @@
using System.Linq;
using System.Net;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Services.Implement;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Services
{
[TestFixture]
public class BasicAuthServiceTests
{
[TestCase(true, ExpectedResult = true)]
[TestCase(false, ExpectedResult = false)]
public bool IsBasicAuthEnabled(bool enabled)
{
var sut = new BasicAuthService(Mock.Of<IOptionsMonitor<BasicAuthSettings>>(_ => _.CurrentValue == new BasicAuthSettings() {Enabled = enabled}));
return sut.IsBasicAuthEnabled();
}
[TestCase("::1", "1.1.1.1", ExpectedResult = false)]
[TestCase("::1", "1.1.1.1, ::1", ExpectedResult = true)]
[TestCase("127.0.0.1", "127.0.0.1, ::1", ExpectedResult = true)]
[TestCase("127.0.0.1", "", ExpectedResult = false)]
[TestCase("125.125.125.1", "125.125.125.0/24", ExpectedResult = true)]
[TestCase("125.125.124.1", "125.125.125.0/24", ExpectedResult = false)]
public bool IsIpAllowListed(string clientIpAddress, string commaSeperatedAllowlist)
{
var allowedIPs = commaSeperatedAllowlist.Split(",").Select(x=>x.Trim()).ToArray();
var sut = new BasicAuthService(Mock.Of<IOptionsMonitor<BasicAuthSettings>>(_ => _.CurrentValue == new BasicAuthSettings() {AllowedIPs = allowedIPs}));
return sut.IsIpAllowListed(IPAddress.Parse(clientIpAddress));
}
}
}

View File

@@ -77,9 +77,11 @@ namespace Umbraco.Cms.Web.Common.PublishedModels
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
public new const PublishedItemType ModelItemType = PublishedItemType.Content;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[return: global::System.Diagnostics.CodeAnalysis.MaybeNull]
public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)
=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[return: global::System.Diagnostics.CodeAnalysis.MaybeNull]
public static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<Type1, TValue>> selector)
=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);
#pragma warning restore 0109
@@ -96,6 +98,7 @@ namespace Umbraco.Cms.Web.Common.PublishedModels
// properties
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[global::System.Diagnostics.CodeAnalysis.MaybeNull]
[ImplementPropertyType(""prop1"")]
public virtual string Prop1 => this.Value<string>(_publishedValueFallback, ""prop1"");
}
@@ -183,9 +186,11 @@ namespace Umbraco.Cms.Web.Common.PublishedModels
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
public new const PublishedItemType ModelItemType = PublishedItemType.Content;
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[return: global::System.Diagnostics.CodeAnalysis.MaybeNull]
public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor)
=> PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias);
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[return: global::System.Diagnostics.CodeAnalysis.MaybeNull]
public static IPublishedPropertyType GetModelPropertyType<TValue>(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression<Func<Type1, TValue>> selector)
=> PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector);
#pragma warning restore 0109
@@ -202,6 +207,7 @@ namespace Umbraco.Cms.Web.Common.PublishedModels
// properties
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""Umbraco.ModelsBuilder.Embedded"", """ + version + @""")]
[global::System.Diagnostics.CodeAnalysis.MaybeNull]
[ImplementPropertyType(""foo"")]
public virtual global::System.Collections.Generic.IEnumerable<global::" + modelsBuilderConfig.ModelsNamespace + @".Foo> Foo => this.Value<global::System.Collections.Generic.IEnumerable<global::" + modelsBuilderConfig.ModelsNamespace + @".Foo>>(_publishedValueFallback, ""foo"");
}

View File

@@ -19,7 +19,7 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.16.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="System.Data.Odbc" Version="5.0.0" />
<PackageReference Include="System.Data.OleDb" Version="5.0.0" />

View File

@@ -30,7 +30,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
var mgr = new BackOfficeCookieManager(
Mock.Of<IUmbracoContextAccessor>(),
runtime,
new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()));
new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()),
Mock.Of<IBasicAuthService>());
var result = mgr.ShouldAuthenticateRequest("/umbraco");
@@ -48,7 +49,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")));
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco")),
Mock.Of<IBasicAuthService>());
var result = mgr.ShouldAuthenticateRequest("/umbraco");
@@ -69,7 +71,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
Mock.Of<IBasicAuthService>());
var result = mgr.ShouldAuthenticateRequest(remainingTimeoutSecondsPath);
Assert.IsTrue(result);
@@ -90,7 +93,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Security
runtime,
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")));
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install")),
Mock.Of<IBasicAuthService>());
var result = mgr.ShouldAuthenticateRequest("/notbackoffice");
Assert.IsFalse(result);

View File

@@ -0,0 +1,42 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Text;
using Microsoft.AspNetCore.Http;
using NUnit.Framework;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions
{
[TestFixture]
public class HttpContextExtensionTests
{
[Test]
public void TryGetBasicAuthCredentials_WithoutHeader_ReturnsFalse()
{
var httpContext = new DefaultHttpContext();
var result = httpContext.TryGetBasicAuthCredentials(out string _, out string _);
Assert.IsFalse(result);
}
[Test]
public void TryGetBasicAuthCredentials_WithHeader_ReturnsTrueWithCredentials()
{
const string Username = "fred";
const string Password = "test";
var httpContext = new DefaultHttpContext();
var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{Username}:{Password}"));
httpContext.Request.Headers.Add("Authorization", $"Basic {credentials}");
bool result = httpContext.TryGetBasicAuthCredentials(out string username, out string password);
Assert.IsTrue(result);
Assert.AreEqual(Username, username);
Assert.AreEqual(Password, password);
}
}
}

View File

@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Tests.UnitTests.AutoFixture;
@@ -36,5 +37,15 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website
Assert.AreEqual("~/Views/Template.cshtml", PathUtility.EnsurePathIsApplicationRootPrefixed("/Views/Template.cshtml"));
Assert.AreEqual("~/Views/Template.cshtml", PathUtility.EnsurePathIsApplicationRootPrefixed("~/Views/Template.cshtml"));
}
[AutoMoqData]
[Test]
public void EnsureApplicationMainUrl(AspNetCoreHostingEnvironment sut)
{
var url = new Uri("http://localhost:5000");
sut.EnsureApplicationMainUrl(url);
Assert.AreEqual(sut.ApplicationMainUrl, url);
}
}
}

View File

@@ -56,14 +56,14 @@ namespace Umbraco.Tests.PublishedContent
var content = Mock.Of<IReadOnlyContentBase>(x => x.ContentTypeId == 1);
var json = jsonSerializer.Serialize(content, cacheModel).StringData;
var msgPack = msgPackSerializer.Serialize(content, cacheModel).ByteData;
var json = jsonSerializer.Serialize(content, cacheModel, false).StringData;
var msgPack = msgPackSerializer.Serialize(content, cacheModel, false).ByteData;
Console.WriteLine(json);
Console.WriteLine(msgPackSerializer.ToJson(msgPack));
var jsonContent = jsonSerializer.Deserialize(content, json, null);
var msgPackContent = msgPackSerializer.Deserialize(content, null, msgPack);
var jsonContent = jsonSerializer.Deserialize(content, json, null, false);
var msgPackContent = msgPackSerializer.Deserialize(content, null, msgPack, false);
CollectionAssert.AreEqual(jsonContent.CultureData.Keys, msgPackContent.CultureData.Keys);

View File

@@ -4,7 +4,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
@@ -239,17 +238,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public Dictionary<string, Dictionary<string, string>> LocalizedText(string culture = null)
public async Task<Dictionary<string, Dictionary<string, string>>> LocalizedText(string culture = null)
{
var isAuthenticated = _backofficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated();
CultureInfo cultureInfo;
if (string.IsNullOrWhiteSpace(culture))
{
// Force authentication to occur since this is not an authorized endpoint, we need this to get a user.
AuthenticateResult authenticationResult = await this.AuthenticateBackOfficeAsync();
// We have to get the culture from the Identity, we can't rely on thread culture
// It's entirely likely for a user to have a different culture in the backoffice, than their system.
var user = authenticationResult.Principal?.Identity;
var cultureInfo = string.IsNullOrWhiteSpace(culture)
//if the user is logged in, get their culture, otherwise default to 'en'
? isAuthenticated
//current culture is set at the very beginning of each request
? Thread.CurrentThread.CurrentCulture
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage)
: CultureInfo.GetCultureInfo(culture);
cultureInfo = (authenticationResult.Succeeded && user is not null)
? user.GetCulture()
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage);
}
else
{
cultureInfo = CultureInfo.GetCultureInfo(culture);
}
var allValues = _textService.GetAllStoredValues(cultureInfo);
var pathedValues = allValues.Select(kv =>

View File

@@ -28,12 +28,9 @@ using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.BackOffice.ActionResults;
using Umbraco.Cms.Web.BackOffice.Authorization;
using Umbraco.Cms.Web.BackOffice.Extensions;
using Umbraco.Cms.Web.BackOffice.Filters;
using Umbraco.Cms.Web.BackOffice.ModelBinders;
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
@@ -478,6 +475,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return result;
}
private ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeysInternal(Guid[] contentTypeKeys, int parentId)
{
using var scope = _scopeProvider.CreateScope(autoComplete: true);
var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList();
return GetEmpties(contentTypes, parentId).ToDictionary(x => x.ContentTypeKey);
}
/// <summary>
/// Gets a collection of empty content items for all document types.
/// </summary>
@@ -486,9 +490,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[OutgoingEditorModelEvent]
public ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeys([FromQuery] Guid[] contentTypeKeys, [FromQuery] int parentId)
{
using var scope = _scopeProvider.CreateScope(autoComplete: true);
var contentTypes = _contentTypeService.GetAll(contentTypeKeys).ToList();
return GetEmpties(contentTypes, parentId).ToDictionary(x => x.ContentTypeKey);
return GetEmptyByKeysInternal(contentTypeKeys, parentId);
}
/// <summary>
/// Gets a collection of empty content items for all document types.
/// </summary>
/// <remarks>
/// This is a post request in order to support a large amount of GUIDs without hitting the URL length limit.
/// </remarks>
/// <param name="contentTypeByKeys"></param>
/// <returns></returns>
[HttpPost]
[OutgoingEditorModelEvent]
public ActionResult<IDictionary<Guid, ContentItemDisplay>> GetEmptyByKeys(ContentTypesByKeys contentTypeByKeys)
{
return GetEmptyByKeysInternal(contentTypeByKeys.ContentTypeKeys, contentTypeByKeys.ParentId);
}
[OutgoingEditorModelEvent]

View File

@@ -21,7 +21,6 @@ using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Core.Dashboards;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;

View File

@@ -258,16 +258,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
/// <summary>
/// Gets the URL of an entity
/// </summary>
/// <param name="udi">UDI of the entity to fetch URL for</param>
/// <param name="id">UDI of the entity to fetch URL for</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
public IActionResult GetUrl(Udi udi, string culture = "*")
public IActionResult GetUrl(Udi id, string culture = "*")
{
var intId = _entityService.GetId(udi);
var intId = _entityService.GetId(id);
if (!intId.Success)
return NotFound();
UmbracoEntityTypes entityType;
switch (udi.EntityType)
switch (id.EntityType)
{
case Constants.UdiEntityType.Document:
entityType = UmbracoEntityTypes.Document;

View File

@@ -11,6 +11,7 @@ using Umbraco.Cms.Web.Common.Security;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Web.BackOffice.Authorization;
using Umbraco.Cms.Web.Common.Middleware;
namespace Umbraco.Extensions

View File

@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Umbraco.Cms.Core.Security;
namespace Umbraco.Extensions
@@ -10,5 +12,6 @@ namespace Umbraco.Extensions
public static BackOfficeExternalLoginProviderErrors GetExternalLoginProviderErrors(this HttpContext httpContext)
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
}
}

View File

@@ -8,6 +8,7 @@ using Umbraco.Cms.Web.BackOffice.Middleware;
using Umbraco.Cms.Web.BackOffice.Routing;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.Cms.Web.Common.Extensions;
using Umbraco.Cms.Web.Common.Middleware;
namespace Umbraco.Extensions
{

View File

@@ -2,7 +2,9 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web;
@@ -23,6 +25,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
private readonly IRuntimeState _runtime;
private readonly string[] _explicitPaths;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
private readonly IBasicAuthService _basicAuthService;
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeCookieManager"/> class.
@@ -30,8 +33,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
public BackOfficeCookieManager(
IUmbracoContextAccessor umbracoContextAccessor,
IRuntimeState runtime,
UmbracoRequestPaths umbracoRequestPaths)
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths)
UmbracoRequestPaths umbracoRequestPaths,
IBasicAuthService basicAuthService)
: this(umbracoContextAccessor, runtime, null, umbracoRequestPaths, basicAuthService)
{
}
@@ -42,12 +46,14 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IUmbracoContextAccessor umbracoContextAccessor,
IRuntimeState runtime,
IEnumerable<string> explicitPaths,
UmbracoRequestPaths umbracoRequestPaths)
UmbracoRequestPaths umbracoRequestPaths,
IBasicAuthService basicAuthService)
{
_umbracoContextAccessor = umbracoContextAccessor;
_runtime = runtime;
_explicitPaths = explicitPaths?.ToArray();
_umbracoRequestPaths = umbracoRequestPaths;
_basicAuthService = basicAuthService;
}
/// <summary>
@@ -88,6 +94,11 @@ namespace Umbraco.Cms.Web.BackOffice.Security
return true;
}
if (_basicAuthService.IsBasicAuthEnabled())
{
return true;
}
return false;
}

View File

@@ -34,6 +34,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
private readonly IIpResolver _ipResolver;
private readonly ISystemClock _systemClock;
private readonly UmbracoRequestPaths _umbracoRequestPaths;
private readonly IBasicAuthService _basicAuthService;
private readonly IOptionsMonitor<BasicAuthSettings> _optionsSnapshot;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> class.
@@ -59,7 +61,8 @@ namespace Umbraco.Cms.Web.BackOffice.Security
IUserService userService,
IIpResolver ipResolver,
ISystemClock systemClock,
UmbracoRequestPaths umbracoRequestPaths)
UmbracoRequestPaths umbracoRequestPaths,
IBasicAuthService basicAuthService)
{
_serviceProvider = serviceProvider;
_umbracoContextAccessor = umbracoContextAccessor;
@@ -72,6 +75,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security
_ipResolver = ipResolver;
_systemClock = systemClock;
_umbracoRequestPaths = umbracoRequestPaths;
_basicAuthService = basicAuthService;
}
/// <inheritdoc />
@@ -115,7 +119,9 @@ namespace Umbraco.Cms.Web.BackOffice.Security
options.CookieManager = new BackOfficeCookieManager(
_umbracoContextAccessor,
_runtimeState,
_umbracoRequestPaths);
_umbracoRequestPaths,
_basicAuthService
);
options.Events = new CookieAuthenticationEvents
{

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Collections;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Extensions;
@@ -13,7 +15,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
{
public class AspNetCoreHostingEnvironment : IHostingEnvironment
{
private readonly ISet<Uri> _applicationUrls = new HashSet<Uri>();
private readonly ConcurrentHashSet<Uri> _applicationUrls = new ConcurrentHashSet<Uri>();
private readonly IServiceProvider _serviceProvider;
private readonly IOptionsMonitor<HostingSettings> _hostingSettings;
private readonly IOptionsMonitor<WebRoutingSettings> _webRoutingSettings;
@@ -168,6 +170,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
// (this is a simplified version of what was in 7.x)
// note: should this be optional? is it expensive?
if (currentApplicationUrl is null)
{
return;
@@ -181,9 +184,10 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
var change = !_applicationUrls.Contains(currentApplicationUrl);
if (change)
{
_applicationUrls.Add(currentApplicationUrl);
ApplicationMainUrl = currentApplicationUrl;
if (_applicationUrls.TryAdd(currentApplicationUrl))
{
ApplicationMainUrl = currentApplicationUrl;
}
}
}
}

View File

@@ -101,10 +101,10 @@ namespace Umbraco.Extensions
services.AddSingleton(httpContextAccessor);
var requestCache = new HttpContextRequestAppCache(httpContextAccessor);
var appCaches = AppCaches.Create(requestCache);
var appCaches = AppCaches.Create(requestCache);
IProfiler profiler = GetWebProfiler(config);
ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
TypeLoader typeLoader = services.AddTypeLoader(

View File

@@ -37,7 +37,7 @@ namespace Umbraco.Extensions
IOptions<UmbracoPipelineOptions> startupOptions = app.ApplicationServices.GetRequiredService<IOptions<UmbracoPipelineOptions>>();
app.RunPrePipeline(startupOptions.Value);
app.UseUmbracoCore();
app.UseUmbracoRequestLogging();

View File

@@ -14,13 +14,7 @@ namespace Umbraco.Extensions
/// <returns></returns>
public static async Task<AuthenticateResult> AuthenticateBackOfficeAsync(this ControllerBase controller)
{
if (controller.HttpContext == null)
{
return AuthenticateResult.NoResult();
}
var result = await controller.HttpContext.AuthenticateAsync(Cms.Core.Constants.Security.BackOfficeAuthenticationType);
return result;
return await controller.HttpContext.AuthenticateBackOfficeAsync();
}
/// <summary>

View File

@@ -1,11 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Media;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Core.Models;
namespace Umbraco.Extensions
{
@@ -337,10 +337,14 @@ namespace Umbraco.Extensions
);
[Obsolete("Use GetCrop to merge local and media crops, get automatic cache buster value and have more parameters.")]
public static string GetLocalCropUrl(
this MediaWithCrops mediaWithCrops,
string alias,
string cacheBusterValue = null)
=> mediaWithCrops.GetLocalCropUrl(alias, cacheBusterValue);
{
return mediaWithCrops.LocalCrops.Src +
mediaWithCrops.LocalCrops.GetCropUrl(alias, ImageUrlGenerator, cacheBusterValue: cacheBusterValue);
}
}
}

View File

@@ -1,12 +1,60 @@
using System;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;
namespace Umbraco.Extensions
{
public static class HttpContextExtensions
{
/// <summary>
/// Try to get the basic auth username and password from the http context.
/// </summary>
public static bool TryGetBasicAuthCredentials(this HttpContext httpContext, out string username, out string password)
{
username = null;
password = null;
if (httpContext.Request.Headers.TryGetValue("Authorization", out StringValues authHeaders))
{
var authHeader = authHeaders.ToString();
if (authHeader is not null && authHeader.StartsWith("Basic"))
{
// Extract credentials.
var encodedUsernamePassword = authHeader.Substring(6).Trim();
Encoding encoding = Encoding.UTF8;
var usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));
var seperatorIndex = usernamePassword.IndexOf(':');
username = usernamePassword.Substring(0, seperatorIndex);
password = usernamePassword.Substring(seperatorIndex + 1);
}
return true;
}
return false;
}
/// <summary>
/// Runs the authentication process
/// </summary>
public static async Task<AuthenticateResult> AuthenticateBackOfficeAsync(this HttpContext httpContext)
{
if (httpContext == null)
{
return AuthenticateResult.NoResult();
}
var result = await httpContext.AuthenticateAsync(Cms.Core.Constants.Security.BackOfficeAuthenticationType);
return result;
}
/// <summary>
/// Get the value in the request form or query string for the key
/// </summary>

View File

@@ -7,7 +7,6 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Routing;
using Umbraco.Core.Models;
namespace Umbraco.Extensions
{

View File

@@ -217,9 +217,11 @@ namespace Umbraco.Extensions
}
var url = mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
return CreateHtmlString(url, htmlEncode);
}
private static IHtmlContent CreateHtmlString(string url, bool htmlEncode) => htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias, bool htmlEncode = true)
{
if (mediaItem == null)
@@ -228,7 +230,7 @@ namespace Umbraco.Extensions
}
var url = mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
return CreateHtmlString(url, htmlEncode);
}
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper,
@@ -256,7 +258,7 @@ namespace Umbraco.Extensions
var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode,
upScale);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
return CreateHtmlString(url, htmlEncode);
}
public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper,
@@ -281,7 +283,7 @@ namespace Umbraco.Extensions
var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode,
imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode,
upScale);
return htmlEncode ? new HtmlString(HttpUtility.HtmlEncode(url)) : new HtmlString(url);
return CreateHtmlString(url, htmlEncode);
}
/// <summary>

View File

@@ -10,9 +10,13 @@ namespace Umbraco.Cms.Web.Common.Security
public sealed class ConfigureMemberIdentityOptions : IConfigureOptions<IdentityOptions>
{
private readonly MemberPasswordConfigurationSettings _memberPasswordConfiguration;
private readonly SecuritySettings _securitySettings;
public ConfigureMemberIdentityOptions(IOptions<MemberPasswordConfigurationSettings> memberPasswordConfiguration)
=> _memberPasswordConfiguration = memberPasswordConfiguration.Value;
public ConfigureMemberIdentityOptions(IOptions<MemberPasswordConfigurationSettings> memberPasswordConfiguration, IOptions<SecuritySettings> securitySettings)
{
_memberPasswordConfiguration = memberPasswordConfiguration.Value;
_securitySettings = securitySettings.Value;
}
public void Configure(IdentityOptions options)
{
@@ -22,6 +26,9 @@ namespace Umbraco.Cms.Web.Common.Security
options.User.RequireUniqueEmail = true;
// Support validation of member names using Down-Level Logon Name format
options.User.AllowedUserNameCharacters = _securitySettings.AllowedUserNameCharacters;
options.Lockout.AllowedForNewUsers = true;
// TODO: Implement this
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30);

View File

@@ -25,8 +25,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.8" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

File diff suppressed because it is too large Load Diff

View File

@@ -126,7 +126,9 @@
// close all editors
if (args && !args.editor && args.editors.length === 0) {
editorCount = 0;
scope.editors = [];
scope.editors = [];
// Remove the inert attribute from the #mainwrapper
focusLockService.removeInertAttribute();
}
}));

View File

@@ -6,7 +6,7 @@
**/
angular.module("umbraco.directives")
.directive('umbImageCrop',
function ($timeout, $window, cropperHelper) {
function ($timeout, cropperHelper, windowResizeListener) {
const MAX_SCALE = 4;
@@ -26,7 +26,7 @@ angular.module("umbraco.directives")
forceUpdate: '@?'
},
link: function (scope, element, attrs, windowResizeListener) {
link: function (scope, element, attrs) {
var unsubscribe = [];
let sliderRef = null;
@@ -72,7 +72,7 @@ angular.module("umbraco.directives")
};
function updateSlider() {
if(sliderRef) {
if (sliderRef) {
// Update slider range min/max
sliderRef.noUiSlider.updateOptions({
"range": {
@@ -102,7 +102,7 @@ angular.module("umbraco.directives")
// cross-browser wheel delta
var delta = Math.max(-50, Math.min(50, (event.wheelDelta || -event.detail)));
if(sliderRef) {
if (sliderRef) {
var currentScale =sliderRef.noUiSlider.get();
var newScale = Math.min(Math.max(currentScale + delta*.001*scope.dimensions.image.ratio, scope.dimensions.scale.min), scope.dimensions.scale.max);
@@ -127,8 +127,8 @@ angular.module("umbraco.directives")
'left': (parseInt(scope.dimensions.margin.left, 10)) + 'px'
}
};
updateStyles();
updateStyles();
//elements
var $viewport = element.find(".viewport");
@@ -138,11 +138,11 @@ angular.module("umbraco.directives")
$overlay.bind("focus", function () {
$overlay.bind("DOMMouseScroll mousewheel onmousewheel", onScroll);
});
$overlay.bind("blur", function () {
$overlay.unbind("DOMMouseScroll mousewheel onmousewheel", onScroll);
});
//default constraints for drag n drop
var constraints = { left: { max: 0, min: 0 }, top: { max: 0, min: 0 } };
scope.constraints = constraints;

View File

@@ -57,7 +57,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* Do stuff...
* });
* </pre>
*
*
* @returns {Promise} resourcePromise object.
*
*/
@@ -691,11 +691,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
getScaffoldByKeys: function (parentId, scaffoldKeys) {
return umbRequestHelper.resourcePromise(
$http.get(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmptyByKeys",
{ contentTypeKeys: scaffoldKeys, parentId: parentId })),
"GetEmptyByKeys"),
{ contentTypeKeys: scaffoldKeys, parentId: parentId }
),
'Failed to retrieve data for empty content items ids' + scaffoldKeys.join(", "))
.then(function (result) {
Object.keys(result).map(function(key) {
@@ -804,7 +805,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
else if (options.orderDirection === "desc") {
options.orderDirection = "Descending";
}
//converts the value to a js bool
function toBool(v) {
if (Utilities.isNumber(v)) {

View File

@@ -142,7 +142,7 @@ function entityResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetUrl",
[{ udi: udi }, {culture: culture }])),
[{ id: udi }, {culture: culture }])),
'Failed to retrieve url for UDI:' + udi);
},

View File

@@ -487,7 +487,7 @@
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
getScaffoldFromKey: function (contentTypeKey) {
return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey);
return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey) || null;
},
/**
@@ -499,7 +499,7 @@
* @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context.
*/
getScaffoldFromAlias: function (contentTypeAlias) {
return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias);
return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias) || null;
},
/**
@@ -609,10 +609,14 @@
blockObject.settingsData = settingsData;
// make basics from scaffold
blockObject.settings = Utilities.copy(settingsScaffold);
ensureUdiAndKey(blockObject.settings, settingsUdi);
if (settingsScaffold !== null) {// We might not have settingsScaffold
blockObject.settings = Utilities.copy(settingsScaffold);
ensureUdiAndKey(blockObject.settings, settingsUdi);
mapToElementModel(blockObject.settings, settingsData);
mapToElementModel(blockObject.settings, settingsData);
} else {
blockObject.settings = null;
}
// add settings content-app
appendSettingsContentApp(blockObject.content, this.__labels.settingsName);

View File

@@ -22,7 +22,7 @@
*/
angular.module('umbraco.services')
.factory('localizationService', function ($http, $q, eventsService, $window, $filter, userService) {
.factory('localizationService', function ($http, $q, eventsService) {
// TODO: This should be injected as server vars
var url = "LocalizedText";
@@ -61,14 +61,14 @@ angular.module('umbraco.services')
return "[" + alias + "]";
}
var service = {
// loads the language resource file from the server
initLocalizedResources: function () {
// TODO: This promise handling is super ugly, we should just be returnning the promise from $http and returning inner values.
// TODO: This promise handling is super ugly, we should just be returnning the promise from $http and returning inner values.
var deferred = $q.defer();
@@ -120,7 +120,7 @@ angular.module('umbraco.services')
* @description
* Helper to tokenize and compile a localization string
* @param {String} value the value to tokenize
* @param {Object} scope the $scope object
* @param {Object} scope the $scope object
* @returns {String} tokenized resource string
*/
tokenize: function (value, scope) {
@@ -138,8 +138,8 @@ angular.module('umbraco.services')
}
return value;
},
/**
* @ngdoc method
* @name umbraco.services.localizationService#tokenReplace
@@ -148,19 +148,19 @@ angular.module('umbraco.services')
* @description
* Helper to replace tokens
* @param {String} value the text-string to manipulate
* @param {Array} tekens An array of tokens values
* @param {Array} tekens An array of tokens values
* @returns {String} Replaced test-string
*/
tokenReplace: function (text, tokens) {
if (tokens) {
for (var i = 0; i < tokens.length; i++) {
text = text.replace("%" + i + "%", tokens[i]);
text = text.replace("%" + i + "%", _.escape(tokens[i]));
}
}
return text;
},
/**
* @ngdoc method
* @name umbraco.services.localizationService#localize
@@ -168,16 +168,16 @@ angular.module('umbraco.services')
*
* @description
* Checks the dictionary for a localized resource string
* @param {String} value the area/key to localize in the format of 'section_key'
* @param {String} value the area/key to localize in the format of 'section_key'
* alternatively if no section is set such as 'key' then we assume the key is to be looked in
* the 'general' section
*
*
* @param {Array} tokens if specified this array will be sent as parameter values
* This replaces %0% and %1% etc in the dictionary key value with the passed in strings
*
* @param {String} fallbackValue if specified this string will be returned if no matching
*
* @param {String} fallbackValue if specified this string will be returned if no matching
* entry was found in the dictionary
*
*
* @returns {String} localized resource string
*/
localize: function (value, tokens, fallbackValue) {
@@ -194,7 +194,7 @@ angular.module('umbraco.services')
* @description
* Checks the dictionary for multipe localized resource strings at once, preventing the need for nested promises
* with localizationService.localize
*
*
* ##Usage
* <pre>
* localizationService.localizeMany(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
@@ -203,11 +203,11 @@ angular.module('umbraco.services')
* notificationService.error(header, message);
* });
* </pre>
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
* alternatively if no section is set such as 'key' then we assume the key is to be looked in
* the 'general' section
*
*
* @returns {Array} An array of localized resource string in the same order
*/
localizeMany: function(keys) {
@@ -234,18 +234,18 @@ angular.module('umbraco.services')
* @description
* Checks the dictionary for multipe localized resource strings at once & concats them to a single string
* Which was not possible with localizationSerivce.localize() due to returning a promise
*
*
* ##Usage
* <pre>
* localizationService.concat(["speechBubbles_templateErrorHeader", "speechBubbles_templateErrorText"]).then(function(data){
* var combinedText = data;
* });
* </pre>
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
* alternatively if no section is set such as 'key' then we assume the key is to be looked in
* the 'general' section
*
*
* @returns {String} An concatenated string of localized resource string passed into the function in the same order
*/
concat: function(keys) {
@@ -280,7 +280,7 @@ angular.module('umbraco.services')
* @description
* Checks the dictionary for multipe localized resource strings at once & formats a tokenized message
* Which was not possible with localizationSerivce.localize() due to returning a promise
*
*
* ##Usage
* <pre>
* localizationService.format(["template_insert", "template_insertSections"], "%0% %1%").then(function(data){
@@ -288,14 +288,14 @@ angular.module('umbraco.services')
* var formattedResult = data;
* });
* </pre>
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
*
* @param {Array} keys is an array of strings of the area/key to localize in the format of 'section_key'
* alternatively if no section is set such as 'key' then we assume the key is to be looked in
* the 'general' section
*
*
* @param {String} message is the string you wish to replace containing tokens in the format of %0% and %1%
* with the localized resource strings
*
*
* @returns {String} An concatenated string of localized resource string passed into the function in the same order
*/
format: function(keys, message){
@@ -330,7 +330,7 @@ angular.module('umbraco.services')
resourceFileLoadStatus = "none";
resourceLoadingPromise = [];
});
// return the local instance when called
return service;

View File

@@ -11,7 +11,7 @@
*/
function windowResizeListener($rootScope) {
var WinReszier = (function () {
var WinResizer = (function () {
var registered = [];
var inited = false;
var resize = _.debounce(function(ev) {
@@ -51,7 +51,7 @@ function windowResizeListener($rootScope) {
* @param {Function} cb
*/
register: function (cb) {
WinReszier.register(cb);
WinResizer.register(cb);
},
/**
@@ -59,9 +59,9 @@ function windowResizeListener($rootScope) {
* @param {Function} cb
*/
unregister: function(cb) {
WinReszier.unregister(cb);
WinResizer.unregister(cb);
}
};
}
angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener);
angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener);

View File

@@ -154,9 +154,10 @@
currentForm.$dirty = false;
}
});
$scope.dialog.confirmDiscardChanges = false;
vm.saveState = "success";
vm.saveSuccces = true;
}, function(error){
vm.saveState = "error";
vm.saveError = error;

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