Merge remote-tracking branch 'origin/v10/dev' into v10/feature/nullable-reference-types-in-Umbraco.Web.Backoffice

# Conflicts:
#	src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs
#	src/Umbraco.Core/Extensions/PublishedContentExtensions.cs
#	src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs
#	src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MultiUrlPickerValueConverter.cs
#	src/Umbraco.PublishedCache.NuCache/ContentStore.cs
#	src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs
#	src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs
#	src/Umbraco.Web.Common/Security/MemberManager.cs
#	src/Umbraco.Web.Website/Routing/ControllerActionSearcher.cs
#	src/Umbraco.Web.Website/Routing/IControllerActionSearcher.cs
#	src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs
This commit is contained in:
Nikolaj Geisle
2022-04-21 10:26:51 +02:00
172 changed files with 4578 additions and 1710 deletions

View File

@@ -198,7 +198,7 @@ namespace Umbraco.Cms.Core.Composing
IEnumerable<Assembly>? assemblies = null,
bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses,
//the additional filter will ensure that any found types also have the attribute applied.
@@ -214,7 +214,7 @@ namespace Umbraco.Cms.Core.Composing
/// <returns></returns>
public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly>? assemblies = null, bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses);
}
@@ -231,7 +231,7 @@ namespace Umbraco.Cms.Core.Composing
IEnumerable<Assembly>? assemblies = null,
bool onlyConcreteClasses = true)
{
var assemblyList = (assemblies ?? AssembliesToScan).ToList();
var assemblyList = assemblies ?? AssembliesToScan;
return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses);
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
internal const bool StaticHideTopLevelNodeFromPath = true;
internal const bool StaticUseHttps = false;
internal const int StaticVersionCheckPeriod = 7;
internal const string StaticUmbracoPath = "~/umbraco";
internal const string StaticUmbracoPath = Constants.System.DefaultUmbracoPath;
internal const string StaticIconsPath = "~/umbraco/assets/icons";
internal const string StaticUmbracoCssPath = "~/css";
internal const string StaticUmbracoScriptsPath = "~/scripts";

View File

@@ -12,6 +12,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
public class KeepAliveSettings
{
internal const bool StaticDisableKeepAliveTask = false;
internal const string StaticKeepAlivePingUrl = "~/api/keepalive/ping";
/// <summary>
/// Gets or sets a value indicating whether the keep alive task is disabled.
@@ -20,8 +21,9 @@ namespace Umbraco.Cms.Core.Configuration.Models
public bool DisableKeepAliveTask { get; set; } = StaticDisableKeepAliveTask;
/// <summary>
/// Gets a value for the keep alive ping URL.
/// Gets or sets a value for the keep alive ping URL.
/// </summary>
public string KeepAlivePingUrl => "~/api/keepalive/ping";
[DefaultValue(StaticKeepAlivePingUrl)]
public string KeepAlivePingUrl { get; set; } = StaticKeepAlivePingUrl;
}
}

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Cms.Core.Configuration.Models
{
internal const string StaticNuCacheSerializerType = "MessagePack";
internal const int StaticSqlPageSize = 1000;
internal const int StaticKitBatchSize = 1;
/// <summary>
/// Gets or sets a value defining the BTree block size.
@@ -31,6 +32,12 @@ namespace Umbraco.Cms.Core.Configuration.Models
[DefaultValue(StaticSqlPageSize)]
public int SqlPageSize { get; set; } = StaticSqlPageSize;
/// <summary>
/// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time.
/// </summary>
[DefaultValue(StaticKitBatchSize)]
public int KitBatchSize { get; set; } = StaticKitBatchSize;
public bool UnPublishedContentCompression { get; set; } = false;
}
}

View File

@@ -62,7 +62,7 @@
public const string UmbracoDefaultDatabaseName = "Umbraco";
public const string UmbracoConnectionName = "umbracoDbDSN";
public const string UmbracoConnectionName = "umbracoDbDSN";public const string DefaultUmbracoPath = "~/umbraco";
}
}
}

View File

@@ -0,0 +1,32 @@
namespace Umbraco.Cms.Core
{
public static partial class Constants
{
public static class Telemetry
{
public static string RootCount = "RootCount";
public static string DomainCount = "DomainCount";
public static string ExamineIndexCount = "ExamineIndexCount";
public static string LanguageCount = "LanguageCount";
public static string MacroCount = "MacroCount";
public static string MediaCount = "MediaCount";
public static string MemberCount = "MemberCount";
public static string TemplateCount = "TemplateCount";
public static string ContentCount = "ContentCount";
public static string DocumentTypeCount = "DocumentTypeCount";
public static string Properties = "Properties";
public static string UserCount = "UserCount";
public static string UserGroupCount = "UserGroupCount";
public static string ServerOs = "ServerOs";
public static string ServerFramework = "ServerFramework";
public static string OsLanguage = "OsLanguage";
public static string WebServer = "WebServer";
public static string ModelsBuilderMode = "ModelBuilderMode";
public static string CustomUmbracoPath = "CustomUmbracoPath";
public static string AspEnvironment = "AspEnvironment";
public static string IsDebug = "IsDebug";
public static string DatabaseProvider = "DatabaseProvider";
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Umbraco.Cms.Core.Dashboards
{
public class AnalyticsDashboard : IDashboard
{
public string Alias => "settingsAnalytics";
public string[] Sections => new [] { "settings" };
public string View => "views/dashboard/settings/analytics.html";
public IAccessRule[] AccessRules => Array.Empty<IAccessRule>();
}
}

View File

@@ -118,7 +118,8 @@ namespace Umbraco.Cms.Core.DependencyInjection
.Append<Soundcloud>()
.Append<Issuu>()
.Append<Hulu>()
.Append<Giphy>();
.Append<Giphy>()
.Append<LottieFiles>();
builder.SearchableTrees()?.Add(() => builder.TypeLoader.GetTypes<ISearchableTree>());
builder.BackOfficeAssets();
}

View File

@@ -25,7 +25,7 @@ using Umbraco.Cms.Core.Install;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Logging;
using Umbraco.Cms.Core.Mail;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Packaging;
@@ -42,7 +42,6 @@ using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Core.Telemetry;
using Umbraco.Cms.Core.Templates;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.DependencyInjection
@@ -200,7 +199,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton<UriUtility>();
Services.AddUnique<IDashboardService, DashboardService>();
Services.AddUnique<IUserDataService, UserDataService>();
Services.AddSingleton<IMetricsConsentService, MetricsConsentService>();
// will be injected in controllers when needed to invoke rest endpoints on Our
Services.AddUnique<IInstallationService, InstallationService>();

View File

@@ -1106,7 +1106,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.Children(variationContextAccessor, culture)
: publishedSnapshot?.Content?.GetAtRoot().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot?.Content?.GetAtRoot(culture).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
/// <summary>
@@ -1122,7 +1122,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)
: publishedSnapshot?.Content?.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
/// <summary>
@@ -1139,7 +1139,7 @@ namespace Umbraco.Extensions
{
return content.Parent != null
? content.Parent.Children<T>(variationContextAccessor, culture)
: publishedSnapshot?.Content?.GetAtRoot().OfType<T>().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
: publishedSnapshot?.Content?.GetAtRoot(culture).OfType<T>().WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
}
#endregion

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.Media.EmbedProviders
{
/// <summary>
/// Embed Provider for lottiefiles.com the popular opensource JSON-based animation format platform.
/// </summary>
public class LottieFiles : OEmbedProviderBase
{
public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer)
{
}
public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed";
public override string[] UrlSchemeRegex => new string[]
{
@"lottiefiles\.com/*"
};
public override Dictionary<string, string> RequestParams => new Dictionary<string, string>();
public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0)
{
var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight);
OEmbedResponse oembed = base.GetJsonResponse<OEmbedResponse>(requestUrl);
var html = oembed.GetHtml();
//LottieFiles doesn't seem to support maxwidth and maxheight via oembed
// this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc
// otherwise it always defaults to 300...
if (maxWidth > 0 && maxHeight > 0)
{
html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"" + maxWidth + "\"");
html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"" + maxHeight + "\"");
}
else
{
//if set to 0, let's default to 100% as an easter egg
html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\"");
html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\"");
}
return html;
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public enum TelemetryLevel
{
Minimal,
Basic,
Detailed,
}
}

View File

@@ -0,0 +1,11 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class TelemetryResource
{
[DataMember]
public TelemetryLevel TelemetryLevel { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class UsageInformation
{
[DataMember(Name = "name")]
public string Name { get; }
[DataMember(Name = "data")]
public object Data { get; }
public UsageInformation(string name, object data)
{
Name = name;
Data = data;
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models
{
[DataContract]
public class UserTwoFactorProviderModel
{
public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser)
{
ProviderName = providerName;
IsEnabledOnUser = isEnabledOnUser;
}
[DataMember(Name = "providerName")]
public string ProviderName { get; }
[DataMember(Name = "isEnabledOnUser")]
public bool IsEnabledOnUser { get; }
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Web;
namespace Umbraco.Cms.Core.Notifications
{
public class SendingAllowedChildrenNotification : INotification
{
public IUmbracoContext UmbracoContext { get; }
public IEnumerable<ContentTypeBasic> Children { get; set; }
public SendingAllowedChildrenNotification(IEnumerable<ContentTypeBasic> children, IUmbracoContext umbracoContext)
{
UmbracoContext = umbracoContext;
Children = children;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Umbraco.Cms.Core.Notifications
{
public class UserTwoFactorRequestedNotification : INotification
{
public UserTwoFactorRequestedNotification(Guid userKey)
{
UserKey = userKey;
}
public Guid UserKey { get; }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Umbraco.Cms.Core.Persistence.Repositories;
public interface INodeCountRepository
{
int GetNodeCount(Guid nodeType);
int GetMediaCount();
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Cms.Core.Services
{
public interface IExamineIndexCountService
{
public int GetCount();
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public interface IMetricsConsentService
{
TelemetryLevel GetConsentLevel();
void SetConsentLevel(TelemetryLevel telemetryLevel);
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Umbraco.Cms.Core.Services
{
public interface INodeCountService
{
int GetNodeCount(Guid nodeType);
int GetMediaCount();
}
}

View File

@@ -58,4 +58,12 @@ namespace Umbraco.Cms.Core.Services
/// </summary>
Task<IEnumerable<string>> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey);
}
[Obsolete("This will be merged into ITwoFactorLoginService in Umbraco 11")]
public interface ITwoFactorLoginService2 : ITwoFactorLoginService
{
Task<bool> DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
Task<bool> ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public interface IUsageInformationService
{
IEnumerable<UsageInformation> GetDetailed();
}
}

View File

@@ -0,0 +1,34 @@
using System;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services
{
public class MetricsConsentService : IMetricsConsentService
{
internal const string Key = "UmbracoAnalyticsLevel";
private readonly IKeyValueService _keyValueService;
public MetricsConsentService(IKeyValueService keyValueService)
{
_keyValueService = keyValueService;
}
public TelemetryLevel GetConsentLevel()
{
var analyticsLevelString = _keyValueService.GetValue(Key);
if (analyticsLevelString is null || Enum.TryParse(analyticsLevelString, out TelemetryLevel analyticsLevel) is false)
{
return TelemetryLevel.Basic;
}
return analyticsLevel;
}
public void SetConsentLevel(TelemetryLevel telemetryLevel)
{
_keyValueService.SetValue(Key, telemetryLevel.ToString());
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.Services.Implement
{
public class NodeCountService : INodeCountService
{
private readonly INodeCountRepository _nodeCountRepository;
private readonly IScopeProvider _scopeProvider;
public NodeCountService(INodeCountRepository nodeCountRepository, IScopeProvider scopeProvider)
{
_nodeCountRepository = nodeCountRepository;
_scopeProvider = scopeProvider;
}
public int GetNodeCount(Guid nodeType)
{
using var scope = _scopeProvider.CreateScope(autoComplete: true);
return _nodeCountRepository.GetNodeCount(nodeType);
}
public int GetMediaCount()
{
using var scope = _scopeProvider.CreateScope(autoComplete: true);
return _nodeCountRepository.GetMediaCount();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
@@ -9,11 +10,13 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services
{
[Obsolete("Use the IUserDataService interface instead")]
public class UserDataService : IUserDataService
{
private readonly IUmbracoVersion _version;
private readonly ILocalizationService _localizationService;
public UserDataService(IUmbracoVersion version, ILocalizationService localizationService)
{
_version = version;

View File

@@ -84,5 +84,11 @@ namespace Umbraco.Extensions
});
}
public static IUser GetByKey(this IUserService userService, Guid key)
{
int id = BitConverter.ToInt32(key.ToByteArray(), 0);
return userService.GetUserById(id);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Telemetry.Models
{
@@ -30,5 +31,8 @@ namespace Umbraco.Cms.Core.Telemetry.Models
/// </remarks>
[DataMember(Name = "packages")]
public IEnumerable<PackageTelemetry>? Packages { get; set; }
[DataMember(Name = "detailed")]
public IEnumerable<UsageInformation>? Detailed { get; set; }
}
}

View File

@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Telemetry.Models;
using Umbraco.Extensions;
@@ -16,6 +18,8 @@ namespace Umbraco.Cms.Core.Telemetry
private readonly IManifestParser _manifestParser;
private readonly IUmbracoVersion _umbracoVersion;
private readonly ISiteIdentifierService _siteIdentifierService;
private readonly IUsageInformationService _usageInformationService;
private readonly IMetricsConsentService _metricsConsentService;
/// <summary>
/// Initializes a new instance of the <see cref="TelemetryService"/> class.
@@ -23,11 +27,15 @@ namespace Umbraco.Cms.Core.Telemetry
public TelemetryService(
IManifestParser manifestParser,
IUmbracoVersion umbracoVersion,
ISiteIdentifierService siteIdentifierService)
ISiteIdentifierService siteIdentifierService,
IUsageInformationService usageInformationService,
IMetricsConsentService metricsConsentService)
{
_manifestParser = manifestParser;
_umbracoVersion = umbracoVersion;
_siteIdentifierService = siteIdentifierService;
_usageInformationService = usageInformationService;
_metricsConsentService = metricsConsentService;
}
/// <inheritdoc/>
@@ -42,14 +50,30 @@ namespace Umbraco.Cms.Core.Telemetry
telemetryReportData = new TelemetryReportData
{
Id = telemetryId,
Version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(),
Version = GetVersion(),
Packages = GetPackageTelemetry(),
Detailed = _usageInformationService.GetDetailed(),
};
return true;
}
private string GetVersion()
{
if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal)
{
return null;
}
return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild();
}
private IEnumerable<PackageTelemetry> GetPackageTelemetry()
{
if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal)
{
return null;
}
List<PackageTelemetry> packages = new();
IEnumerable<PackageManifest> manifests = _manifestParser.GetManifests();