Merge remote-tracking branch 'origin/v11/dev' into v12/dev

This commit is contained in:
Bjarke Berg
2023-03-20 08:41:29 +01:00
88 changed files with 4481 additions and 1099 deletions

View File

@@ -24,7 +24,6 @@
<ProjectReference Include="..\Umbraco.New.Cms.Core\Umbraco.New.Cms.Core.csproj" />
<ProjectReference Include="..\Umbraco.New.Cms.Infrastructure\Umbraco.New.Cms.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.New.Cms.Web.Common\Umbraco.New.Cms.Web.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,8 +10,6 @@
<ItemGroup>
<ProjectReference Include="..\Umbraco.Cms.StaticAssets\Umbraco.Cms.StaticAssets.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridItem.get_ForceLeft</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridItem.get_ForceRight</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridItem.set_ForceLeft(System.Boolean)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridItem.set_ForceRight(System.Boolean)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IDataTypeConfigurationConnector.FromArtifact(Umbraco.Cms.Core.Models.IDataType,System.String,Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IDataTypeConfigurationConnector.ToArtifact(Umbraco.Cms.Core.Models.IDataType,System.Collections.Generic.ICollection{Umbraco.Cms.Core.Deploy.ArtifactDependency},Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IServiceConnector.GetArtifact(System.Object,Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IServiceConnector.GetArtifact(Umbraco.Cms.Core.Udi,Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IValueConnector.FromArtifact(System.String,Umbraco.Cms.Core.Models.IPropertyType,System.Object,Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IValueConnector.ToArtifact(System.Object,Umbraco.Cms.Core.Models.IPropertyType,System.Collections.Generic.ICollection{Umbraco.Cms.Core.Deploy.ArtifactDependency},Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>

View File

@@ -9,6 +9,7 @@ using Umbraco.Cms.Web.Common.DependencyInjection;
namespace Umbraco.Cms.Core.Configuration.Grid;
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridConfig : IGridConfig
{
public GridConfig(

View File

@@ -158,6 +158,7 @@ public class ContentSettings
internal const bool StaticDisableDeleteWhenReferenced = false;
internal const bool StaticDisableUnpublishWhenReferenced = false;
internal const bool StaticAllowEditInvariantFromNonDefault = false;
internal const bool StaticShowDomainWarnings = true;
/// <summary>
/// Gets or sets a value for the content notification settings.
@@ -267,4 +268,10 @@ public class ContentSettings
/// Gets or sets the allowed external host for media. If empty only relative paths are allowed.
/// </summary>
public string[] AllowedMediaHosts { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets a value indicating whether to show domain warnings.
/// </summary>
[DefaultValue(StaticShowDomainWarnings)]
public bool ShowDomainWarnings { get; set; } = StaticShowDomainWarnings;
}

View File

@@ -50,6 +50,7 @@ public static partial class Constants
public const string ClickJackingCheck = "https://umbra.co/healthchecks-click-jacking";
public const string HstsCheck = "https://umbra.co/healthchecks-hsts";
public const string NoSniffCheck = "https://umbra.co/healthchecks-no-sniff";
[Obsolete("This link is not used anymore in the XSS protected check.")]
public const string XssProtectionCheck = "https://umbra.co/healthchecks-xss-protection";
public const string ExcessiveHeadersCheck = "https://umbra.co/healthchecks-excessive-headers";

File diff suppressed because it is too large Load Diff

View File

@@ -342,6 +342,7 @@
<key alias="createNewMember">Opret et nyt medlem</key>
<key alias="allMembers">Alle medlemmer</key>
<key alias="memberGroupNoProperties">Medlemgrupper har ingen yderligere egenskaber til redigering.</key>
<key alias="2fa">Totrinsbekræftelse</key>
</area>
<area alias="contentType">
<key alias="copyFailed">Kopiering af indholdstypen fejlede</key>
@@ -1902,6 +1903,7 @@ Mange hilsner fra Umbraco robotten
genkende dig. Klik på cirklen ovenfor for at uploade et billede.
</key>
<key alias="writer">Forfatter</key>
<key alias="configureTwoFactor">Konfigurer totrinsbekræftelse</key>
<key alias="change">Skift</key>
<key alias="yourProfile">Din profil</key>
<key alias="yourHistory">Din historik</key>
@@ -1929,11 +1931,11 @@ Mange hilsner fra Umbraco robotten
<key alias="sortCreateDateDescending">Ældste</key>
<key alias="sortLastLoginDateDescending">Sidst logget ind</key>
<key alias="noUserGroupsAdded">Ingen brugere er blevet tilføjet</key>
<key alias="2faDisableText">Hvis du ønsker at slå denne autentificeringsmetode fra, så skal du nu indtaste koden fra dit device:</key>
<key alias="2faProviderIsEnabled">Denne autentificeringsmetode er slået til</key>
<key alias="2faProviderIsDisabledMsg">Den valgte autentificeringsmetode er nu slået fra</key>
<key alias="2faProviderIsNotDisabledMsg">Der skete en ukendt fejl da denne autentificeringsmetode skulles slåes fra</key>
<key alias="2faDisableForUser">Er du sikker på, at du vil fjerne denne autentificeringsmetode for denne bruger?</key>
<key alias="2faDisableText">Hvis du ønsker at slå denne totrinsbekræftelse fra, så skal du nu indtaste koden fra din enhed:</key>
<key alias="2faProviderIsEnabled">Denne totrinsbekræftelse er slået til</key>
<key alias="2faProviderIsDisabledMsg">Den valgte totrinsbekræftelse er nu slået fra</key>
<key alias="2faProviderIsNotDisabledMsg">Der skete en ukendt fejl da denne totrinsbekræftelse skulles slåes fra</key>
<key alias="2faDisableForUser">Er du sikker på, at du vil fjerne denne totrinsbekræftelse for denne bruger?</key>
</area>
<area alias="validation">
<key alias="validation">Validering</key>

View File

@@ -878,6 +878,8 @@
<key alias="header">Header</key>
<key alias="systemField">system field</key>
<key alias="lastUpdated">Last Updated</key>
<key alias="skipToMenu">Skip to menu</key>
<key alias="skipToContent">Skip to content</key>
</area>
<area alias="colors">
<key alias="blue">Blue</key>
@@ -1329,7 +1331,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="packageLikes">Likes</key>
<key alias="packageCompatibility">Compatibility</key>
<key alias="packageCompatibilityDescription">This package is compatible with the following versions of Umbraco, as
reported by community members. Full compatability cannot be guaranteed for versions reported below 100%
reported by community members. Full compatibility cannot be guaranteed for versions reported below 100%
</key>
<key alias="packageExternalSources">External sources</key>
<key alias="packageAuthor">Author</key>
@@ -1540,7 +1542,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="contentTypeDublicatePropertyType">Property type already exists</key>
<key alias="contentTypePropertyTypeCreated">Property type created</key>
<key alias="contentTypePropertyTypeCreatedText"><![CDATA[Name: %0% <br /> DataType: %1%]]></key>
<key alias="contentTypePropertyTypeDeleted">Propertytype deleted</key>
<key alias="contentTypePropertyTypeDeleted">Property type deleted</key>
<key alias="contentTypeSavedHeader">Document Type saved</key>
<key alias="contentTypeTabCreated">Tab created</key>
<key alias="contentTypeTabDeleted">Tab deleted</key>
@@ -2358,9 +2360,13 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="hSTSCheckHeaderNotFoundOnLocalhost">
<![CDATA[The header <strong>Strict-Transport-Security</strong> was not found. This header should not be present on localhost.]]>
</key>
<key alias="xssProtectionCheckHeaderFound"><![CDATA[The header <strong>X-XSS-Protection</strong> was found.]]></key>
<key alias="xssProtectionCheckHeaderFound">
<![CDATA[The header <strong>X-XSS-Protection</strong> was found. <strong>It is recommended not to add this header to your website</strong>.<br />
You can read about this on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection" target="_blank" rel="noopener" class="btn-link -underline">Mozilla</a> website ]]>
</key>
<key alias="xssProtectionCheckHeaderNotFound">
<![CDATA[The header <strong>X-XSS-Protection</strong> was not found.]]></key>
<![CDATA[The header <strong>X-XSS-Protection</strong> was not found.]]>
</key>
<!-- The following key get these tokens passed in:
0: Comma delimitted list of headers found
-->
@@ -2775,7 +2781,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="focusParentBlock">Set focus on the container block</key>
<key alias="areaIdentification">Identification</key>
<key alias="areaValidation">Validation</key>
<key alias="areaValidationEntriesShort"><![CDATA[<strong>%0%</strong> must be present atleast <strong>%2%</strong> time(s).]]></key>
<key alias="areaValidationEntriesShort"><![CDATA[<strong>%0%</strong> must be present at least <strong>%2%</strong> time(s).]]></key>
<key alias="areaValidationEntriesExceed"><![CDATA[<strong>%0%</strong> must maximum be present <strong>%3%</strong> time(s).]]></key>
<key alias="areaNumberOfBlocks">Number of blocks</key>
<key alias="areaDisallowAllBlocks">Only allow specific block types</key>

View File

@@ -908,6 +908,8 @@
<key alias="header">Header</key>
<key alias="systemField">system field</key>
<key alias="lastUpdated">Last Updated</key>
<key alias="skipToMenu">Skip to menu</key>
<key alias="skipToContent">Skip to content</key>
</area>
<area alias="colors">
<key alias="blue">Blue</key>
@@ -2461,7 +2463,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="hSTSCheckHeaderNotFoundOnLocalhost">
<![CDATA[The header <strong>Strict-Transport-Security</strong> was not found. This header should not be present on localhost.]]>
</key>
<key alias="xssProtectionCheckHeaderFound"><![CDATA[The header <strong>X-XSS-Protection</strong> was found.]]></key>
<key alias="xssProtectionCheckHeaderFound">
<![CDATA[The header <strong>X-XSS-Protection</strong> was found. <strong>It is recommended not to add this header to your website</strong>.<br />
You can read about this on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection" target="_blank" rel="noopener" class="btn-link -underline">Mozilla</a> website ]]></key>
<key alias="xssProtectionCheckHeaderNotFound">
<![CDATA[The header <strong>X-XSS-Protection</strong> was not found.]]></key>
<!-- The following key get these tokens passed in:

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language alias="ro" intName="Romanian (Romania)" localName="romana (Romania)" lcid="" culture="ro-RO">
<creator>
<name>The Umbraco community</name>
<link>https://docs.umbraco.com/umbraco-cms/extending/language-files</link>
</creator>
<area alias="sections">
<key alias="content">Conţinut</key>
<key alias="forms">Formulare</key>
<key alias="media">Media</key>
<key alias="member">Membrii</key>
<key alias="packages">Pachete</key>
<key alias="marketplace">Marketplace</key>
<key alias="settings">Setări</key>
<key alias="translation">Traduceri</key>
<key alias="users">Utilizatori</key>
</area>
</language>

View File

@@ -0,0 +1,19 @@
using System.Diagnostics.CodeAnalysis;
using Umbraco.Cms.Core.Strings;
namespace Umbraco.Extensions;
public static class HtmlEncodedStringExtensions
{
/// <summary>
/// Checks if the specified <see cref="IHtmlEncodedString" /> is <c>null</c> or only contains whitespace, optionally after all HTML tags have been stripped/removed.
/// </summary>
/// <param name="htmlEncodedString">The encoded HTML string.</param>
/// <param name="stripHtml">If set to <c>true</c> strips/removes all HTML tags.</param>
/// <returns>
/// Returns <c>true</c> if the HTML string is <c>null</c> or only contains whitespace, optionally after all HTML tags have been stripped/removed.
/// </returns>
public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this IHtmlEncodedString? htmlEncodedString, bool stripHtml = false)
=> (htmlEncodedString?.ToHtmlString() is var htmlString && string.IsNullOrWhiteSpace(htmlString)) ||
(stripHtml && string.IsNullOrWhiteSpace(htmlString.StripHtml()));
}

View File

@@ -30,5 +30,5 @@ public static class PasswordConfigurationExtensions
};
public static int GetMinNonAlphaNumericChars(this IPasswordConfiguration passwordConfiguration) =>
passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0;
passwordConfiguration.RequireNonLetterOrDigit ? 1 : 0;
}

View File

@@ -13,27 +13,40 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security;
/// </summary>
public abstract class BaseHttpHeaderCheck : HealthCheck
{
private static HttpClient? httpClient;
private static HttpClient? _httpClient;
private readonly string _header;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly string _localizedTextPrefix;
private readonly bool _metaTagOptionAvailable;
private readonly bool _shouldNotExist;
/// <summary>
/// Initializes a new instance of the <see cref="BaseHttpHeaderCheck" /> class.
/// </summary>
[Obsolete("Use constructor that takes all parameters instead.")]
protected BaseHttpHeaderCheck(
IHostingEnvironment hostingEnvironment,
ILocalizedTextService textService,
string header,
string localizedTextPrefix,
bool metaTagOptionAvailable)
: this(hostingEnvironment, textService, header, localizedTextPrefix, metaTagOptionAvailable, false)
{ }
protected BaseHttpHeaderCheck(
IHostingEnvironment hostingEnvironment,
ILocalizedTextService textService,
string header,
string localizedTextPrefix,
bool metaTagOptionAvailable,
bool shouldNotExist)
{
LocalizedTextService = textService ?? throw new ArgumentNullException(nameof(textService));
_hostingEnvironment = hostingEnvironment;
_header = header;
_localizedTextPrefix = localizedTextPrefix;
_metaTagOptionAvailable = metaTagOptionAvailable;
_shouldNotExist = shouldNotExist;
}
[Obsolete("Save ILocalizedTextService in a field on the super class instead of using this")]
@@ -44,7 +57,7 @@ public abstract class BaseHttpHeaderCheck : HealthCheck
/// </summary>
protected abstract string ReadMoreLink { get; }
private static HttpClient HttpClient => httpClient ??= new HttpClient();
private static HttpClient HttpClient => _httpClient ??= new HttpClient();
/// <summary>
/// Get the status for this health check
@@ -66,6 +79,7 @@ public abstract class BaseHttpHeaderCheck : HealthCheck
{
string message;
var success = false;
StatusResultType resultType = StatusResultType.Warning;
// Access the site home page and check for the click-jack protection header or meta tag
var url = _hostingEnvironment.ApplicationMainUrl?.GetLeftPart(UriPartial.Authority);
@@ -86,6 +100,16 @@ public abstract class BaseHttpHeaderCheck : HealthCheck
message = success
? LocalizedTextService.Localize("healthcheck", $"{_localizedTextPrefix}CheckHeaderFound")
: LocalizedTextService.Localize("healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound");
if (_shouldNotExist)
{
resultType = success ? StatusResultType.Error : StatusResultType.Success;
}
else
{
resultType = success ? StatusResultType.Success : StatusResultType.Error;
}
}
catch (Exception ex)
{
@@ -95,8 +119,8 @@ public abstract class BaseHttpHeaderCheck : HealthCheck
return
new HealthCheckStatus(message)
{
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
ReadMoreLink = success ? null : ReadMoreLink,
ResultType = resultType,
ReadMoreLink = success && !string.IsNullOrEmpty(ReadMoreLink) ? null : ReadMoreLink,
};
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security;
[HealthCheck(
"F4D2B02E-28C5-4999-8463-05759FA15C3A",
"Cross-site scripting Protection (X-XSS-Protection header)",
Description = "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.",
Description = "This checks for the presence of the X-XSS-Protection-header.",
Group = "Security")]
public class XssProtectionCheck : BaseHttpHeaderCheck
{
@@ -20,17 +20,14 @@ public class XssProtectionCheck : BaseHttpHeaderCheck
/// Initializes a new instance of the <see cref="XssProtectionCheck" /> class.
/// </summary>
/// <remarks>
/// The check is mostly based on the instructions in the OWASP CheatSheet
/// (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet)
/// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/)
/// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/,
/// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites.
/// This check should not find the header in newer browsers as this can cause security vulnerabilities
/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
/// </remarks>
public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService)
: base(hostingEnvironment, textService, "X-XSS-Protection", "xssProtection", true)
: base(hostingEnvironment, textService, "X-XSS-Protection", "xssProtection", true, true)
{
}
/// <inheritdoc />
protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.XssProtectionCheck;
protected override string ReadMoreLink => string.Empty;
}

View File

@@ -0,0 +1,16 @@
using System.Runtime.Serialization;
namespace Umbraco.Cms.Core.Models.ContentEditing;
/// <summary>
/// Used to publish content and variants
/// </summary>
[DataContract(Name = "publish", Namespace = "")]
public class PublishContent
{
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "cultures")]
public string[]? Cultures { get; set; }
}

View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.Xml.Linq;
using System.Xml.XPath;
using Umbraco.Cms.Core.PropertyEditors;
@@ -190,7 +190,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent
}
_cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot;
_modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this);
_modelClrType = _converter?.GetPropertyValueType(this) ?? typeof(object);
}
/// <inheritdoc />

View File

@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Configuration.Grid;
namespace Umbraco.Cms.Core.PropertyEditors;
[DataContract]
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridEditor : IGridEditorConfig
{
public GridEditor()

View File

@@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration for the media picker value editor.
/// </summary>
[Obsolete("Please use the MediaPicker3 instead, will be removed in V13")]
public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig
{
[ConfigurationField("multiPicker", "Pick multiple items", "boolean")]

View File

@@ -11,6 +11,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration editor for the media picker value editor.
/// </summary>
[Obsolete("Please use the MediaPicker3 instead, will be removed in V13")]
public class MediaPickerConfigurationEditor : ConfigurationEditor<MediaPickerConfiguration>
{
// Scheduled for removal in v12

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration for the nested content value editor.
/// </summary>
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentConfiguration
{
[ConfigurationField("contentTypes", "Element Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the Element Types to use as models for the items.")]

View File

@@ -11,6 +11,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration editor for the nested content value editor.
/// </summary>
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentConfigurationEditor : ConfigurationEditor<NestedContentConfiguration>
{
// Scheduled for removal in v12

View File

@@ -5,7 +5,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
internal class ContentPickerValueConverter : PropertyValueConverterBase
public class ContentPickerValueConverter : PropertyValueConverterBase
{
private static readonly List<string> PropertiesToExclude = new()
{

View File

@@ -9,6 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
/// The media picker property value converter.
/// </summary>
[DefaultPropertyValueConverter]
[Obsolete("Please use the MediaPicker3 instead, will be removed in V13")]
public class MediaPickerValueConverter : PropertyValueConverterBase
{
// hard-coding "image" here but that's how it works at UI level too

View File

@@ -16,9 +16,9 @@ public class UmbracoRequestPaths
private readonly string _appPath;
private readonly string _backOfficeMvcPath;
private readonly string _backOfficePath;
private readonly List<string> _defaultUmbPaths;
private readonly string _defaultUmbPath;
private readonly string _defaultUmbPathWithSlash;
private readonly string _installPath;
private readonly string _mvcArea;
private readonly string _previewMvcPath;
private readonly string _surfaceMvcPath;
private readonly IOptions<UmbracoRequestPathsOptions> _umbracoRequestPathsOptions;
@@ -34,18 +34,19 @@ public class UmbracoRequestPaths
/// </summary>
public UmbracoRequestPaths(IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment, IOptions<UmbracoRequestPathsOptions> umbracoRequestPathsOptions)
{
var applicationPath = hostingEnvironment.ApplicationVirtualPath;
_appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash);
_appPath = hostingEnvironment.ApplicationVirtualPath;
_backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment)
.EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/');
.EnsureStartsWith('/').TrimStart(_appPath).EnsureStartsWith('/');
_mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
_defaultUmbPaths = new List<string> { "/" + _mvcArea, "/" + _mvcArea + "/" };
_backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/";
_previewMvcPath = "/" + _mvcArea + "/Preview/";
_surfaceMvcPath = "/" + _mvcArea + "/Surface/";
_apiMvcPath = "/" + _mvcArea + "/Api/";
string mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
_defaultUmbPath = "/" + mvcArea;
_defaultUmbPathWithSlash = "/" + mvcArea + "/";
_backOfficeMvcPath = "/" + mvcArea + "/BackOffice/";
_previewMvcPath = "/" + mvcArea + "/Preview/";
_surfaceMvcPath = "/" + mvcArea + "/Surface/";
_apiMvcPath = "/" + mvcArea + "/Api/";
_installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install);
_umbracoRequestPathsOptions = umbracoRequestPathsOptions;
}
@@ -78,34 +79,28 @@ public class UmbracoRequestPaths
/// </remarks>
public bool IsBackOfficeRequest(string absPath)
{
var fullUrlPath = absPath.TrimStart(Constants.CharArrays.ForwardSlash);
var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/');
string urlPath = absPath.TrimStart(_appPath).EnsureStartsWith('/');
// check if this is in the umbraco back office
var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath);
// if not, then def not back office
if (isUmbracoPath == false)
if (!urlPath.InvariantStartsWith(_backOfficePath))
{
return false;
}
// if its the normal /umbraco path
if (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x)))
if (urlPath.InvariantEquals(_defaultUmbPath) || urlPath.InvariantEquals(_defaultUmbPathWithSlash))
{
return true;
}
// check for special back office paths
if (urlPath.InvariantStartsWith(_backOfficeMvcPath)
|| urlPath.InvariantStartsWith(_previewMvcPath))
if (urlPath.InvariantStartsWith(_backOfficeMvcPath) || urlPath.InvariantStartsWith(_previewMvcPath))
{
return true;
}
// check for special front-end paths
if (urlPath.InvariantStartsWith(_surfaceMvcPath)
|| urlPath.InvariantStartsWith(_apiMvcPath))
if (urlPath.InvariantStartsWith(_surfaceMvcPath) || urlPath.InvariantStartsWith(_apiMvcPath))
{
return false;
}
@@ -115,18 +110,39 @@ public class UmbracoRequestPaths
return true;
}
// if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
// checking how many parts the route has, for example, all PluginController routes will be routed like
// if its none of the above, we will have to try to detect if it's a PluginController route
return !IsPluginControllerRoute(urlPath);
}
/// <summary>
/// Checks if the path is from a PluginController route.
/// </summary>
private static bool IsPluginControllerRoute(string path)
{
// Detect this by checking how many parts the route has, for example, all PluginController routes will be routed like
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
// plugin controller for the front-end.
if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a plugin controller for the front-end.
int count = 0;
for (int i = 0; i < path.Length; i++)
{
return false;
char chr = path[i];
if (chr == '/')
{
count++;
continue;
}
// Check last char so we can properly determine the number of parts, e.g. /url/path/ has two parts, /url/path/test has three.
if (count == 3)
{
return true;
}
}
// if its anything else we can assume it's back office
return true;
return false;
}
/// <summary>

View File

@@ -1,8 +1,10 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Strings;
@@ -20,6 +22,8 @@ public class TemporaryMediaService : ITemporaryMediaService
private readonly IHostEnvironment _hostingEnvironment;
private readonly ILogger<TemporaryMediaService> _logger;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IEntityService _entityService;
private readonly AppCaches _appCaches;
public TemporaryMediaService(
IShortStringHelper shortStringHelper,
@@ -29,7 +33,9 @@ public class TemporaryMediaService : ITemporaryMediaService
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider,
IHostEnvironment hostingEnvironment,
ILogger<TemporaryMediaService> logger,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IEntityService entityService,
AppCaches appCaches)
{
_shortStringHelper = shortStringHelper;
_mediaFileManager = mediaFileManager;
@@ -39,21 +45,26 @@ public class TemporaryMediaService : ITemporaryMediaService
_hostingEnvironment = hostingEnvironment;
_logger = logger;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_entityService = entityService;
_appCaches = appCaches;
}
public IMedia Save(string temporaryLocation, Guid? startNode, string? mediaTypeAlias)
{
var userId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? Constants.Security.SuperUserId;
IUser? user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser;
int userId = user?.Id ?? Constants.Security.SuperUserId;
var absoluteTempImagePath = _hostingEnvironment.MapPathContentRoot(temporaryLocation);
var fileName = Path.GetFileName(absoluteTempImagePath);
var safeFileName = fileName.ToSafeFileName(_shortStringHelper);
var mediaItemName = safeFileName.ToFriendlyName();
IMedia mediaFile;
if (startNode is null)
{
mediaFile = _mediaService.CreateMedia(mediaItemName, Constants.System.Root, mediaTypeAlias ?? Constants.Conventions.MediaTypes.File, userId);
int[]? userStartNodes = user?.CalculateMediaStartNodeIds(_entityService, _appCaches);
mediaFile = _mediaService.CreateMedia(mediaItemName, userStartNodes != null && userStartNodes.Any() ? userStartNodes[0] : Constants.System.Root, mediaTypeAlias ?? Constants.Conventions.MediaTypes.File, userId);
}
else
{

View File

@@ -11,7 +11,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridLayoutItem.get_ForceLeft</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridLayoutItem.get_ForceRight</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridLayoutItem.set_ForceLeft(System.Boolean)</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Models.Blocks.BlockGridLayoutItem.set_ForceRight(System.Boolean)</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IGridCellValueConnector.GetValue(Umbraco.Cms.Core.Models.GridValue.GridControl,System.Collections.Generic.ICollection{Umbraco.Cms.Core.Deploy.ArtifactDependency},Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Deploy.IGridCellValueConnector.SetValue(Umbraco.Cms.Core.Models.GridValue.GridControl,Umbraco.Cms.Core.Deploy.IContextCache)</Target>
<Left>lib/net7.0/Umbraco.Infrastructure.dll</Left>
<Right>lib/net7.0/Umbraco.Infrastructure.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.Deploy;
/// <remarks>
/// These extension methods will be removed in Umbraco 13.
/// </remarks>
[Obsolete("The grid is obsolete, will be removed in V13")]
public static class GridCellValueConnectorExtensions
{
/// <summary>

View File

@@ -111,7 +111,7 @@ public abstract class RecurringHostedServiceBase : IHostedService, IDisposable
{
using (!ExecutionContext.IsFlowSuppressed() ? (IDisposable)ExecutionContext.SuppressFlow() : null)
{
_timer = new Timer(ExecuteAsync, null, (int)_delay.TotalMilliseconds, (int)_period.TotalMilliseconds);
_timer = new Timer(ExecuteAsync, null, _delay, _period);
}
return Task.CompletedTask;
@@ -151,7 +151,7 @@ public abstract class RecurringHostedServiceBase : IHostedService, IDisposable
{
// Resume now that the task is complete - Note we use period in both because we don't want to execute again after the delay.
// So first execution is after _delay, and the we wait _period between each
_timer?.Change((int)_period.TotalMilliseconds, (int)_period.TotalMilliseconds);
_timer?.Change(_period, _period);
}
}

View File

@@ -40,10 +40,15 @@ public class MacroTagParser
{
if (match.Groups.Count >= 3)
{
var macroCanBeInlinedInParagraph = match.Value?.Contains("enableInlineMacro=\"1\"") ?? false;
var macroElementType = macroCanBeInlinedInParagraph ? "span" : "div";
// <div class="umb-macro-holder myMacro mceNonEditable">
var alias = match.Groups[2].Value;
var sb = new StringBuilder("<div class=\"umb-macro-holder ");
var sb = new StringBuilder($"<{macroElementType} class=\"umb-macro-holder ");
if (macroCanBeInlinedInParagraph)
{
sb.Append("inlined-macro ");
}
// sb.Append(alias.ToSafeAlias());
sb.Append("mceNonEditable\"");
foreach (KeyValuePair<string, string> htmlAttribute in htmlAttributes)
@@ -64,7 +69,7 @@ public class MacroTagParser
sb.Append("Macro alias: ");
sb.Append("<strong>");
sb.Append(alias);
sb.Append("</strong></ins></div>");
sb.Append($"</strong></ins></{macroElementType}>");
return sb.ToString();
}

View File

@@ -1757,6 +1757,7 @@ internal class DatabaseDataCreator
{
// Insert the specified languages, ensuring the first is marked as default.
bool isDefault = true;
short id = 1;
foreach (var isoCode in languageInstallDefaultDataSettings.Values)
{
if (!TryCreateCulture(isoCode, out CultureInfo? culture))
@@ -1766,12 +1767,14 @@ internal class DatabaseDataCreator
var dto = new LanguageDto
{
Id = id,
IsoCode = culture.Name,
CultureName = culture.EnglishName,
IsDefault = isDefault,
};
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", true, dto);
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, dto);
isDefault = false;
id++;
}
}
else

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.Models;
/// <summary>
/// A model representing the value saved for the grid
/// </summary>
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridValue
{
[JsonProperty("name")]
@@ -43,6 +44,7 @@ public class GridValue
public JToken? Config { get; set; }
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridArea
{
[JsonProperty("grid")]
@@ -58,6 +60,7 @@ public class GridValue
public JToken? Config { get; set; }
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridControl
{
[JsonProperty("value")]
@@ -73,6 +76,7 @@ public class GridValue
public JToken? Config { get; set; }
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridEditor
{
[JsonProperty("alias")]

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration for the grid value editor.
/// </summary>
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridConfiguration : IIgnoreUserStartNodesConfig
{
// TODO: Make these strongly typed, for now this works though

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents the configuration editor for the grid value editor.
/// </summary>
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridConfigurationEditor : ConfigurationEditor<GridConfiguration>
{
// Scheduled for removal in v12
@@ -31,6 +32,7 @@ public class GridConfigurationEditor : ConfigurationEditor<GridConfiguration>
}
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridValidator : IValueValidator
{
public IEnumerable<ValidationResult> Validate(object? rawValue, string? valueType, object? dataTypeConfiguration)
@@ -51,6 +53,7 @@ public class GridValidator : IValueValidator
}
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridEditorModel
{
public GridEditorTemplateModel[]? Templates { get; set; }
@@ -58,11 +61,13 @@ public class GridEditorModel
public int Columns { get; set; }
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridEditorTemplateModel
{
public GridEditorSectionModel[]? Sections { get; set; }
}
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridEditorSectionModel
{
public int Grid { get; set; }

View File

@@ -31,6 +31,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
Group = Constants.PropertyEditors.Groups.RichContent,
ValueEditorIsReusable = false,
IsDeprecated = true)]
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridPropertyEditor : DataEditor
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

View File

@@ -14,6 +14,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
/// <summary>
/// Parses the grid value into indexable values
/// </summary>
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridPropertyIndexValueFactory : IPropertyIndexValueFactory
{
public IEnumerable<KeyValuePair<string, IEnumerable<object?>>> GetIndexValues(IProperty property, string? culture, string? segment, bool published)

View File

@@ -28,6 +28,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
Icon = Constants.Icons.MediaImage,
IsDeprecated = false,
ValueEditorIsReusable = true)]
[Obsolete("Please use the MediaPicker3 instead, will be removed in V13")]
public class MediaPickerPropertyEditor : DataEditor
{
private readonly IEditorConfigurationParser _editorConfigurationParser;

View File

@@ -28,6 +28,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
Icon = "icon-thumbnail-list",
ValueEditorIsReusable = false,
IsDeprecated = true)]
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentPropertyEditor : DataEditor
{
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";

View File

@@ -10,6 +10,7 @@ namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// A handler for NestedContent used to bind to notifications
/// </summary>
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentPropertyHandler : ComplexPropertyEditorContentNotificationHandler
{
protected override string EditorAlias => Constants.PropertyEditors.Aliases.NestedContent;

View File

@@ -74,6 +74,7 @@ public sealed class RichTextEditorPastedImages
// we have already processed to avoid dupes
var uploadedImages = new Dictionary<string, GuidUdi>();
foreach (HtmlNode? img in tmpImages)
{
// The data attribute contains the path to the tmp img to persist as a media item
@@ -84,6 +85,11 @@ public sealed class RichTextEditorPastedImages
continue;
}
if (IsValidPath(tmpImgPath) == false)
{
continue;
}
var absoluteTempImagePath = _hostingEnvironment.MapPathContentRoot(tmpImgPath);
var fileName = Path.GetFileName(absoluteTempImagePath);
var safeFileName = fileName.ToSafeFileName(_shortStringHelper);
@@ -184,4 +190,6 @@ public sealed class RichTextEditorPastedImages
return htmlDoc.DocumentNode.OuterHtml;
}
private bool IsValidPath(string imagePath) => imagePath.StartsWith(Constants.SystemDirectories.TempImageUploads);
}

View File

@@ -14,6 +14,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
/// This ensures that the grid config is merged in with the front-end value
/// </summary>
[DefaultPropertyValueConverter(typeof(JsonValueConverter))] // this shadows the JsonValueConverter
[Obsolete("The grid is obsolete, will be removed in V13")]
public class GridValueConverter : JsonValueConverter
{
private readonly IGridConfig _config;

View File

@@ -16,6 +16,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
/// content.
/// </summary>
[DefaultPropertyValueConverter(typeof(JsonValueConverter))]
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentManyValueConverter : NestedContentValueConverterBase
{
private readonly IProfilingLogger _proflog;

View File

@@ -15,6 +15,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
/// content.
/// </summary>
[DefaultPropertyValueConverter(typeof(JsonValueConverter))]
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentSingleValueConverter : NestedContentValueConverterBase
{
private readonly IProfilingLogger _proflog;

View File

@@ -8,6 +8,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;
[Obsolete("Nested content is obsolete, will be removed in V13")]
public abstract class NestedContentValueConverterBase : PropertyValueConverterBase
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;

View File

@@ -19,7 +19,7 @@ public class JITOptimizerValidator : RuntimeModeProductionValidatorBase
DebuggableAttribute? debuggableAttribute = Assembly.GetEntryAssembly()?.GetCustomAttribute<DebuggableAttribute>();
if (debuggableAttribute != null && debuggableAttribute.IsJITOptimizerDisabled)
{
validationErrorMessage = "The JIT/runtime optimizer of the entry assembly needs to be enabled in production mode.";
validationErrorMessage = "The JIT/runtime optimizer of the entry assembly needs to be enabled in production mode. The project should be built/published in Release (or similar) configuration in production mode.";
return false;
}

View File

@@ -4,35 +4,88 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache;
internal static class CacheKeys
{
public static string PublishedContentChildren(Guid contentUid, bool previewing) =>
"NuCache.Content.Children[" + DraftOrPub(previewing) + ":" + contentUid + "]";
public static string PublishedContentChildren(Guid contentUid, bool previewing)
{
if (previewing)
{
return "NuCache.Content.Children[D::" + contentUid + "]";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string DraftOrPub(bool previewing) => previewing ? "D:" : "P:";
return "NuCache.Content.Children[P::" + contentUid + "]";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string LangId(string? culture)
=> string.IsNullOrEmpty(culture) ? string.Empty : "-L:" + culture;
public static string ContentCacheRoots(bool previewing)
{
if (previewing)
{
return "NuCache.ContentCache.Roots[D:]";
}
public static string ContentCacheRoots(bool previewing) =>
"NuCache.ContentCache.Roots[" + DraftOrPub(previewing) + "]";
return "NuCache.ContentCache.Roots[P:]";
}
public static string MediaCacheRoots(bool previewing) => "NuCache.MediaCache.Roots[" + DraftOrPub(previewing) + "]";
public static string MediaCacheRoots(bool previewing)
{
if (previewing)
{
return "NuCache.MediaCache.Roots[D:]";
}
return "NuCache.MediaCache.Roots[P:]";
}
public static string PublishedContentAsPreviewing(Guid contentUid) =>
"NuCache.Content.AsPreviewing[" + contentUid + "]";
public static string ProfileName(int userId) => "NuCache.Profile.Name[" + userId + "]";
public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) =>
"NuCache.Property.CacheValues[" + DraftOrPub(previewing) + contentUid + ":" + typeAlias + "]";
public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing)
{
if (previewing)
{
return "NuCache.Property.CacheValues[D:" + contentUid + ":" + typeAlias + "]";
}
return "NuCache.Property.CacheValues[P:" + contentUid + ":" + typeAlias + "]";
}
// routes still use int id and not Guid uid, because routable nodes must have
// a valid ID in the database at that point, whereas content and properties
// may be virtual (and not in umbracoNode).
public static string ContentCacheRouteByContent(int id, bool previewing, string? culture) =>
"NuCache.ContentCache.RouteByContent[" + DraftOrPub(previewing) + id + LangId(culture) + "]";
public static string ContentCacheRouteByContent(int id, bool previewing, string? culture)
{
if (string.IsNullOrEmpty(culture))
{
if (previewing)
{
return "NuCache.ContentCache.RouteByContent[D:" + id +"]";
}
public static string ContentCacheContentByRoute(string route, bool previewing, string? culture) =>
"NuCache.ContentCache.ContentByRoute[" + DraftOrPub(previewing) + route + LangId(culture) + "]";
return "NuCache.ContentCache.RouteByContent[P:" + id + "]";
}
else if (previewing)
{
return "NuCache.ContentCache.RouteByContent[D:" + id + "-L:" + culture + "]";
}
return "NuCache.ContentCache.RouteByContent[P:" + id + "-L:" + culture + "]";
}
public static string ContentCacheContentByRoute(string route, bool previewing, string? culture)
{
if (string.IsNullOrEmpty(culture))
{
if (previewing)
{
return "NuCache.ContentCache.ContentByRoute[D:" + route + "]";
}
return "NuCache.ContentCache.ContentByRoute[P:" + route + "]";
}
else if (previewing)
{
return "NuCache.ContentCache.ContentByRoute[D:" + route + "-L:" + culture + "]";
}
return "NuCache.ContentCache.ContentByRoute[P:" + route + "-L:" + culture + "]";
}
}

View File

@@ -108,7 +108,23 @@ public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepositor
| ContentCacheDataSerializerEntityType.Media
| ContentCacheDataSerializerEntityType.Member);
if(contentTypeIds != null)
// If contentTypeIds, mediaTypeIds and memberTypeIds are null, truncate table as all records will be deleted (as these 3 are the only types in the table).
if ((contentTypeIds == null || !contentTypeIds.Any())
&& (mediaTypeIds == null || !mediaTypeIds.Any())
&& (memberTypeIds == null || !memberTypeIds.Any()))
{
if (Database.DatabaseType == DatabaseType.SqlServer2012)
{
Database.Execute($"TRUNCATE TABLE cmsContentNu");
}
if (Database.DatabaseType == DatabaseType.SQLite)
{
Database.Execute($"DELETE FROM cmsContentNu");
}
}
if (contentTypeIds != null)
{
RebuildContentDbCache(serializer, _nucacheSettings.Value.SqlPageSize, contentTypeIds);
}

View File

@@ -16,7 +16,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
</ItemGroup>

View File

@@ -5,8 +5,10 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.ContentApps;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Dictionary;
@@ -65,6 +67,7 @@ public class ContentController : ContentControllerBase
private readonly ISqlContext _sqlContext;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IUserService _userService;
private readonly ContentSettings _contentSettings;
[ActivatorUtilitiesConstructor]
public ContentController(
@@ -91,7 +94,8 @@ public class ContentController : ContentControllerBase
ICoreScopeProvider scopeProvider,
IAuthorizationService authorizationService,
IContentVersionService contentVersionService,
ICultureImpactFactory cultureImpactFactory)
ICultureImpactFactory cultureImpactFactory,
IOptions<ContentSettings> contentSettings)
: base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer)
{
_propertyEditors = propertyEditors;
@@ -115,7 +119,63 @@ public class ContentController : ContentControllerBase
_logger = loggerFactory.CreateLogger<ContentController>();
_scopeProvider = scopeProvider;
_allLangs = new Lazy<IDictionary<string, ILanguage>>(() =>
_localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase));
_localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase));
_contentSettings = contentSettings.Value;
}
[Obsolete("Use constructor that accepts ContentSettings as a parameter, scheduled for removal in V13")]
public ContentController(
ICultureDictionary cultureDictionary,
ILoggerFactory loggerFactory,
IShortStringHelper shortStringHelper,
IEventMessagesFactory eventMessages,
ILocalizedTextService localizedTextService,
PropertyEditorCollection propertyEditors,
IContentService contentService,
IUserService userService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IContentTypeService contentTypeService,
IUmbracoMapper umbracoMapper,
IPublishedUrlProvider publishedUrlProvider,
IDomainService domainService,
IDataTypeService dataTypeService,
ILocalizationService localizationService,
IFileService fileService,
INotificationService notificationService,
ActionCollection actionCollection,
ISqlContext sqlContext,
IJsonSerializer serializer,
ICoreScopeProvider scopeProvider,
IAuthorizationService authorizationService,
IContentVersionService contentVersionService,
ICultureImpactFactory cultureImpactFactory)
: this(
cultureDictionary,
loggerFactory,
shortStringHelper,
eventMessages,
localizedTextService,
propertyEditors,
contentService,
userService,
backofficeSecurityAccessor,
contentTypeService,
umbracoMapper,
publishedUrlProvider,
domainService,
dataTypeService,
localizationService,
fileService,
notificationService,
actionCollection,
sqlContext,
serializer,
scopeProvider,
authorizationService,
contentVersionService,
cultureImpactFactory,
StaticServiceProvider.Instance.GetRequiredService<IOptions<ContentSettings>>())
{
}
[Obsolete("Use constructor that accepts ICultureImpactService as a parameter, scheduled for removal in V12")]
@@ -168,8 +228,8 @@ public class ContentController : ContentControllerBase
authorizationService,
contentVersionService,
StaticServiceProvider.Instance.GetRequiredService<ICultureImpactFactory>())
{
}
{
}
public object? Domains { get; private set; }
@@ -1710,6 +1770,11 @@ public class ContentController : ContentControllerBase
/// <param name="globalNotifications"></param>
internal void AddDomainWarnings(IContent? persistedContent, string[]? culturesPublished, SimpleNotificationModel globalNotifications)
{
if (_contentSettings.ShowDomainWarnings is false)
{
return;
}
// Don't try to verify if no cultures were published
if (culturesPublished is null)
{
@@ -1950,6 +2015,7 @@ public class ContentController : ContentControllerBase
}
PublishResult publishResult = _contentService.SaveAndPublish(foundContent, userId: _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0);
if (publishResult.Success == false)
{
var notificationModel = new SimpleNotificationModel();
@@ -1960,6 +2026,56 @@ public class ContentController : ContentControllerBase
return Ok();
}
/// <summary>
/// Publishes a document with a given ID and cultures.
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
/// <remarks>
/// The EnsureUserPermissionForContent attribute will deny access to this method if the current user
/// does not have Publish access to this node.
/// </remarks>
[Authorize(Policy = AuthorizationPolicies.ContentPermissionPublishById)]
public IActionResult PostPublishByIdAndCulture(PublishContent model)
{
var languageCount = _allLangs.Value.Count();
// If there is no culture specified or the cultures specified are equal to the total amount of languages, publish the content in all cultures.
if (model.Cultures == null || !model.Cultures.Any() || model.Cultures.Length == languageCount)
{
return PostPublishById(model.Id);
}
IContent? foundContent = GetObjectFromRequest(() => _contentService.GetById(model.Id));
if (foundContent == null)
{
return HandleContentNotFound(model.Id);
}
var results = new Dictionary<string, PublishResult>();
foreach (var culture in model.Cultures)
{
PublishResult publishResult = _contentService.SaveAndPublish(foundContent, culture, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0);
results[culture] = publishResult;
}
if (results.Any(x => x.Value.Success == false))
{
var notificationModel = new SimpleNotificationModel();
foreach (var culture in results.Where(x => x.Value.Success == false))
{
AddMessageForPublishStatus(new[] { culture.Value }, notificationModel);
}
return ValidationProblem(notificationModel);
}
return Ok();
}
[HttpDelete]
[HttpPost]
public IActionResult DeleteBlueprint(int id)

View File

@@ -167,6 +167,11 @@ internal class ContentMapDefinition : IMapDefinition
target.Updater = source.Updater;
target.Urls = source.Urls;
target.Variants = context.MapEnumerable<ContentVariantDisplay, ContentVariantScheduleDisplay>(source.Variants);
foreach (BackOfficeNotification backOfficeNotification in source.Notifications)
{
target.Notifications.Add(backOfficeNotification);
}
}
// Umbraco.Code.MapAll

View File

@@ -12,6 +12,7 @@ using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.PropertyEditors;
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[Obsolete("Nested content is obsolete, will be removed in V13")]
public class NestedContentController : UmbracoAuthorizedJsonController
{
private readonly IContentTypeService _contentTypeService;

View File

@@ -17,9 +17,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Examine.Lucene\Umbraco.Examine.Lucene.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>

View File

@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Extensions;
[Obsolete("The grid is obsolete, will be removed in V13")]
public static class GridTemplateExtensions
{
public static IHtmlContent GetGridHtml(this IHtmlHelper html, IPublishedProperty property, string framework = "bootstrap3")

View File

@@ -20,9 +20,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Examine.Lucene\Umbraco.Examine.Lucene.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.PublishedCache.NuCache\Umbraco.PublishedCache.NuCache.csproj" />
</ItemGroup>

View File

@@ -127,6 +127,20 @@
}
};
scope.skipToMenu = function() {
document.querySelector('#applications a').focus();
};
scope.skipToContent = function() {
var focusableElements = document.querySelectorAll('.umb-app-content a, .umb-app-content button');
for(var i=0; i < focusableElements.length; i++){
if(focusableElements[i].offsetParent !== null) {
focusableElements[i].focus();
break;
}
}
};
}
var directive = {

View File

@@ -70,8 +70,8 @@ Use this directive to render an avatar.
function getNameInitials(name) {
if (name) {
const notAllowed = /[\[\]\{\}\*\?\&\$\@\!\(\)\%\#]+/g;
var names = name.replace(notAllowed,'').trim().split(' '),
const notAllowedRegex = /[^\p{Letter}\p{Number} ]+/gu;
var names = name.replace(notAllowedRegex, '').trim().split(' '),
initials = names[0].substring(0, 1);
if (names.length > 1) {

View File

@@ -278,7 +278,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* alert("node wasnt unpublished:" + err.data.Message);
* });
* </pre>
* @param {Int} id the ID of the node to unpublish
* @param {Int} id the ID of the node to unpublish.
* @param {array} cultures the cultures to unpublish.
* @returns {Promise} resourcePromise object.
*
*/
@@ -1086,23 +1087,31 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* </pre>
*
* @param {Int} id The ID of the conten to publish
* @param {array} cultures the cultures to publish.
* @returns {Promise} resourcePromise object containing the published content item.
*
*/
publishById: function (id) {
publishById: function (id, cultures) {
if (!id) {
throw "id cannot be null";
}
return umbRequestHelper.resourcePromise(
$http.post(
if (!cultures) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostPublishById",
[{ id: id }])),
'Failed to publish content with id ' + id);
"contentApiBaseUrl",
"PostPublishById"), { id: id }),
'Failed to publish content with id ' + id);
}
else {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostPublishByIdAndCulture"), { id: id, cultures: cultures }),
'Failed to publish content with id ' + id);
}
},
/**

View File

@@ -641,7 +641,7 @@
};
// first time instant update of label.
blockObject.label = blockObject.content?.contentTypeName || "";
blockObject.label = (blockObject.config.label || blockObject.content?.contentTypeName) ?? "" ;
blockObject.index = 0;
if (blockObject.config.label && blockObject.config.label !== "" && blockObject.config.unsupported !== true) {

View File

@@ -98,9 +98,8 @@ function cropperHelper(umbRequestHelper, $http) {
},
pixelsToCoordinates : function(image, width, height, offset){
var x1_px = Math.abs(image.left-offset);
var y1_px = Math.abs(image.top-offset);
var x1_px = Math.abs((image.left || 0)-offset);
var y1_px = Math.abs((image.top || 0)-offset);
var x2_px = image.width - (x1_px + width);
var y2_px = image.height - (y1_px + height);

View File

@@ -766,14 +766,20 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
var e = $(element).closest(".umb-macro-holder");
if (e.length > 0) {
if (e.get(0).parentNode.nodeName === "P") {
var macroHolder = e.get(0);
// In case of Inline Macro we don't need the be backward compliant
if(macroHolder.tagName === 'SPAN'){
return macroHolder;
}
if (macroHolder.parentNode.nodeName === "P") {
//now check if we're the only element
if (element.parentNode.childNodes.length === 1) {
return e.get(0).parentNode;
return macroHolder.parentNode;
}
}
return e.get(0);
return macroHolder;
}
return null;
}
@@ -830,35 +836,37 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
var macroSyntaxComment = "<!-- " + macroObject.syntax + " -->";
//create an id class for this element so we can re-select it after inserting
var uniqueId = "umb-macro-" + editor.dom.uniqueId();
var macroDiv = editor.dom.create('div',
var isInlined = macroObject.macroParamsDictionary["enableInlineMacro"] === "1";
var macroElementType = isInlined ? 'span' : 'div';
var macroElement = editor.dom.create(macroElementType,
{
'class': 'umb-macro-holder ' + macroObject.macroAlias + " " + uniqueId + ' mceNonEditable',
'class': 'umb-macro-holder ' + macroObject.macroAlias + " " + uniqueId + ' mceNonEditable' + (isInlined ? ' inlined-macro' : ''),
'contenteditable': 'false'
},
macroSyntaxComment + '<ins>Macro alias: <strong>' + macroObject.macroAlias + '</strong></ins>');
//if there's an activeMacroElement then replace it, otherwise set the contents of the selected node
if (activeMacroElement) {
activeMacroElement.replaceWith(macroDiv); //directly replaces the html node
activeMacroElement.replaceWith(macroElement); //directly replaces the html node
}
else {
editor.selection.setNode(macroDiv);
editor.selection.setNode(macroElement);
}
var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId));
var $macroElement = $(editor.dom.select(".umb-macro-holder." + uniqueId));
editor.setDirty(true);
//async load the macro content
this.loadMacroContent($macroDiv, macroObject, editor);
this.loadMacroContent($macroElement, macroObject, editor);
},
/** loads in the macro content async from the server */
loadMacroContent: function ($macroDiv, macroData, editor) {
loadMacroContent: function ($macroElement, macroData, editor) {
//if we don't have the macroData, then we'll need to parse it from the macro div
if (!macroData) {
var contents = $macroDiv.contents();
var contents = $macroElement.contents();
var comment = _.find(contents, function (item) {
return item.nodeType === 8;
});
@@ -870,15 +878,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
macroData = parsed;
}
var $ins = $macroDiv.find("ins");
var $ins = $macroElement.find("ins");
//show the throbber
$macroDiv.addClass("loading");
$macroElement.addClass("loading");
// Add the contenteditable="false" attribute
// As just the CSS class of .mceNonEditable is not working by itself?!
// TODO: At later date - use TinyMCE editor DOM manipulation as opposed to jQuery
$macroDiv.attr("contenteditable", "false");
$macroElement.attr("contenteditable", "false");
var contentId = $routeParams.id;
@@ -887,7 +895,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary)
.then(function (htmlResult) {
$macroDiv.removeClass("loading");
$macroElement.removeClass("loading");
htmlResult = htmlResult.trim();
if (htmlResult !== "") {
var wasDirty = editor.isDirty();
@@ -1144,7 +1152,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
// the href might be an external url, so check the value for an anchor/qs
// href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor
if (!target.anchor) {
if (!target.anchor && href) {
var urlParts = href.split(/(#|\?)/);
if (urlParts.length === 3) {
href = urlParts[0];

View File

@@ -110,3 +110,27 @@
.umb-app-header__button:focus .umb-app-header__action-icon {
opacity: 1;
}
.umb-app-header__skip-button {
position: absolute;
top: 7.5px;
left: 5px;
background-color: #FFF;
border: 1px solid #000;
display: block;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px,1px,1px,1px);
border-radius: 3px;
z-index: 1;
}
.umb-app-header__skip-button:focus {
height: auto;
width: auto;
clip: auto;
padding: 10px;
line-height: normal;
text-decoration: none;
}

View File

@@ -5,6 +5,7 @@
.umb-editor {
box-shadow: 0px 0 30px 0 rgba(0,0,0,.3);
position: fixed;
}
}

View File

@@ -4,9 +4,13 @@
.mce-content-body .umb-macro-holder {
border: 3px dotted @pinkLight;
padding: 7px;
display: block;
margin: 3px;
}
.mce-content-body .umb-macro-holder.inlined-macro {
border: 1px dotted @pinkLight;
padding: 1px;
margin: 0px;
}
.umb-rte .mce-content-body .umb-macro-holder.loading {

View File

@@ -1,5 +1,9 @@
<div>
<div class="umb-app-header">
<button type="button" class="umb-app-header__skip-button" ng-click="skipToMenu()"><localize key="general_skipToMenu">Go to menu</localize></button>
<button type="button" class="umb-app-header__skip-button" ng-click="skipToContent()"><localize key="general_skipToContent">Go to content</localize></button>
<div class="umb-app-header__logo" ng-if="hideBackofficeLogo !== true">
<button type="button"
id="umbraco-logo-mark"

View File

@@ -11,4 +11,6 @@ umb-media-preview {
display: flex;
justify-content: center;
align-items: center;
.checkeredBackground();
}

View File

@@ -85,9 +85,11 @@ function ExamineManagementController($http, $q, $timeout, umbRequestHelper, loca
function nextSearchResultPage(pageNumber) {
search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
}
function prevSearchResultPage(pageNumber) {
search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
}
function goToPageSearchResultPage(pageNumber) {
search(vm.selectedIndex ? vm.selectedIndex : vm.selectedSearcher, null, pageNumber);
}
@@ -137,11 +139,13 @@ function ExamineManagementController($http, $q, $timeout, umbRequestHelper, loca
}
function showIndexInfo(index) {
vm.selectedSearcher = null;
vm.selectedIndex = index;
setViewState("index-details");
}
function showSearcherInfo(searcher) {
vm.selectedIndex = null;
vm.selectedSearcher = searcher;
setViewState("searcher-details");
}

View File

@@ -5,7 +5,7 @@
{{vm.columnSpanOption.columnSpan}}
</span>
<div class="__border"></div>
<button type="button" class="btn-reset" ng-click="vm.onClickRemove()">
<button type="button" ng-attr-title="{{vm.removeLabel}}" class="btn-reset" ng-click="vm.onClickRemove()">
<span class="sr-only">
<localize key="general_remove">Remove</localize>
</span>
@@ -23,4 +23,4 @@
</span>
</button>
</div>
</div>
</div>

View File

@@ -23,9 +23,13 @@
}
});
function BlockGridColumnOptionController() {
function BlockGridColumnOptionController(localizationService) {
var vm = this;
var vm = this;
localizationService.localize("general_remove").then(function (value) {
vm.removeLabel = value;
})
vm.$onInit = function() {

View File

@@ -708,7 +708,7 @@
} else
if(allowance.elementTypeKey) {
const blockType = vm.availableBlockTypes.find(x => x.blockConfigModel.contentElementTypeKey === allowance.elementTypeKey);
if(allowedElementTypes.indexOf(blockType) === -1) {
if(blockType && allowedElementTypes.indexOf(blockType) === -1) {
allowedElementTypes.push(blockType);
}
}

View File

@@ -79,6 +79,9 @@ module.exports = function (config) {
// CLI --runner-port 9100
runnerPort: 9100,
// Add support for new DNS resolution in Node 17+
listenAddress: '::',
// enable / disable colors in the output (reporters and logs)
// CLI --colors --no-colors
colors: true,

View File

@@ -38,6 +38,8 @@ public class UmbLoginStatusController : SurfaceController
return CurrentUmbracoPage();
}
MergeRouteValuesToModel(model);
var isLoggedIn = HttpContext.User.Identity?.IsAuthenticated ?? false;
if (isLoggedIn)
@@ -56,4 +58,16 @@ public class UmbLoginStatusController : SurfaceController
// Redirect to current page by default.
return RedirectToCurrentUmbracoPage();
}
/// <summary>
/// We pass in values via encrypted route values so they cannot be tampered with and merge them into the model for use
/// </summary>
/// <param name="model"></param>
private void MergeRouteValuesToModel(PostRedirectModel model)
{
if (RouteData.Values.TryGetValue(nameof(PostRedirectModel.RedirectUrl), out var redirectUrl) && redirectUrl is not null)
{
model.RedirectUrl = redirectUrl.ToString();
}
}
}

View File

@@ -64,6 +64,7 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton<IUmbracoVirtualPageRoute, UmbracoVirtualPageRoute>();
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
builder.Services.AddSingleton<IRoutableDocumentFilter, RoutableDocumentFilter>();
builder.Services.AddSingleton<MatcherPolicy, SurfaceControllerMatcherPolicy>();
builder.Services.AddSingleton<FrontEndRoutes>();

View File

@@ -337,7 +337,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
string controllerName,
object additionalRouteVals,
object? additionalRouteVals,
FormMethod method)
=> html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary<string, object?>(), method);
@@ -348,7 +348,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
string controllerName,
object additionalRouteVals)
object? additionalRouteVals = null)
=> html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary<string, object?>());
/// <summary>
@@ -358,7 +358,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
string controllerName,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes,
FormMethod method) =>
html.BeginUmbracoForm(
@@ -375,7 +375,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
string controllerName,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes) =>
html.BeginUmbracoForm(
action,
@@ -483,7 +483,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
Type surfaceType,
object additionalRouteVals,
object? additionalRouteVals,
FormMethod method) =>
html.BeginUmbracoForm(
action,
@@ -499,21 +499,21 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
Type surfaceType,
object additionalRouteVals) =>
object? additionalRouteVals = null) =>
html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary<string, object?>());
/// <summary>
/// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin
/// </summary>
/// <typeparam name="T">The <see cref="SurfaceController" /> type</typeparam>
public static MvcForm BeginUmbracoForm<T>(this IHtmlHelper html, string action, object additionalRouteVals, FormMethod method)
public static MvcForm BeginUmbracoForm<T>(this IHtmlHelper html, string action, object? additionalRouteVals, FormMethod method)
where T : SurfaceController => html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method);
/// <summary>
/// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin
/// </summary>
/// <typeparam name="T">The <see cref="SurfaceController" /> type</typeparam>
public static MvcForm BeginUmbracoForm<T>(this IHtmlHelper html, string action, object additionalRouteVals)
public static MvcForm BeginUmbracoForm<T>(this IHtmlHelper html, string action, object? additionalRouteVals = null)
where T : SurfaceController => html.BeginUmbracoForm(action, typeof(T), additionalRouteVals);
/// <summary>
@@ -523,7 +523,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
Type surfaceType,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes) =>
html.BeginUmbracoForm(
action,
@@ -538,7 +538,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
Type surfaceType,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes,
FormMethod method) =>
html.BeginUmbracoForm(
@@ -555,7 +555,7 @@ public static class HtmlHelperRenderExtensions
this IHtmlHelper html,
string action,
Type surfaceType,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes,
FormMethod method,
bool? antiforgery) =>
@@ -574,7 +574,7 @@ public static class HtmlHelperRenderExtensions
public static MvcForm BeginUmbracoForm<T>(
this IHtmlHelper html,
string action,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes,
FormMethod method)
where T : SurfaceController =>
@@ -587,7 +587,7 @@ public static class HtmlHelperRenderExtensions
public static MvcForm BeginUmbracoForm<T>(
this IHtmlHelper html,
string action,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes,
FormMethod method,
bool? antiforgery)
@@ -600,7 +600,7 @@ public static class HtmlHelperRenderExtensions
public static MvcForm BeginUmbracoForm<T>(
this IHtmlHelper html,
string action,
object additionalRouteVals,
object? additionalRouteVals,
object htmlAttributes)
where T : SurfaceController => html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes);
@@ -690,7 +690,7 @@ public static class HtmlHelperRenderExtensions
public static MvcForm BeginUmbracoForm<T>(
this IHtmlHelper html,
string action,
object additionalRouteVals,
object? additionalRouteVals,
IDictionary<string, object?> htmlAttributes,
FormMethod method)
where T : SurfaceController =>
@@ -703,7 +703,7 @@ public static class HtmlHelperRenderExtensions
public static MvcForm BeginUmbracoForm<T>(
this IHtmlHelper html,
string action,
object additionalRouteVals,
object? additionalRouteVals,
IDictionary<string, object?> htmlAttributes)
where T : SurfaceController => html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes);
@@ -767,6 +767,12 @@ public static class HtmlHelperRenderExtensions
nameof(controllerName));
}
// Create a new form context in order to ensure client validation is set properly when adding multiple forms in a page. More context in PR #13914.
html.ViewContext.FormContext = new FormContext
{
CanRenderAtEndOfForm = true
};
IUmbracoContextAccessor umbracoContextAccessor = GetRequiredService<IUmbracoContextAccessor>(html);
IUmbracoContext umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
var formAction = umbracoContext.OriginalRequestUrl.PathAndQuery;

View File

@@ -0,0 +1,118 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.Website.Routing;
/// <summary>
/// Ensures the surface controller requests takes priority over other things like virtual routes.
/// Also ensures that requests to a surface controller on a virtual route will return 405, like HttpMethodMatcherPolicy ensures for non-virtual route requests.
/// </summary>
internal class SurfaceControllerMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
{
private const string Http405EndpointDisplayName = "405 HTTP Method Not Supported";
public override int Order { get; } // default order should be okay. Count be everything positive to not conflict with MS policies
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
// In theory all endpoints can have the query string data for a surface controller
return true;
}
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
ArgumentNullException.ThrowIfNull(nameof(httpContext));
ArgumentNullException.ThrowIfNull(nameof(candidates));
if (candidates.Count < 2)
{
return Task.CompletedTask;
}
int? surfaceControllerIndex = GetSurfaceControllerCandidateIndex(candidates);
if (surfaceControllerIndex.HasValue)
{
HashSet<string> allowedHttpMethods = GetAllowedHttpMethods(candidates[surfaceControllerIndex.Value]);
if (allowedHttpMethods.Any()
&& allowedHttpMethods.Contains(httpContext.Request.Method) is false)
{
// We need to handle this as a 405 like the HttpMethodMatcherPolicy would do.
httpContext.SetEndpoint(CreateRejectEndpoint(allowedHttpMethods));
httpContext.Request.RouteValues = null!;
}
else
{
// Otherwise we invalidate all other endpoints than the surface controller that matched.
InvalidateAllCandidatesExceptIndex(candidates, surfaceControllerIndex.Value);
}
}
return Task.CompletedTask;
}
private static HashSet<string> GetAllowedHttpMethods(CandidateState candidate)
{
var surfaceControllerAllowedHttpMethods = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
IHttpMethodMetadata? httpMethodMetadata = candidate.Endpoint?.Metadata.GetMetadata<IHttpMethodMetadata>();
if (httpMethodMetadata is not null)
{
foreach (var httpMethod in httpMethodMetadata.HttpMethods)
{
surfaceControllerAllowedHttpMethods.Add(httpMethod);
}
}
return surfaceControllerAllowedHttpMethods;
}
private static int? GetSurfaceControllerCandidateIndex(CandidateSet candidates)
{
for (var i = 0; i < candidates.Count; i++)
{
if (candidates.IsValidCandidate(i))
{
CandidateState candidate = candidates[i];
ControllerActionDescriptor? controllerActionDescriptor =
candidate.Endpoint?.Metadata.GetMetadata<ControllerActionDescriptor>();
if (controllerActionDescriptor?.ControllerTypeInfo.IsType<SurfaceController>() == true)
{
return i;
}
}
}
return null;
}
private static void InvalidateAllCandidatesExceptIndex(CandidateSet candidates, int index)
{
for (var i = 0; i < candidates.Count; i++)
{
if (i != index)
{
candidates.SetValidity(i, false);
}
}
}
private static Endpoint CreateRejectEndpoint(ISet<string> allowedHttpMethods) =>
new Endpoint(
(context) =>
{
context.Response.StatusCode = 405;
context.Response.Headers.Allow = string.Join(", ", allowedHttpMethods);
return Task.CompletedTask;
},
EndpointMetadataCollection.Empty,
Http405EndpointDisplayName);
}

View File

@@ -12,8 +12,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
</ItemGroup>