Merge remote-tracking branch 'origin/v11/dev' into v12/dev
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
2878
src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
Normal file
2878
src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
18
src/Umbraco.Core/EmbeddedResources/Lang/ro.xml
Normal file
18
src/Umbraco.Core/EmbeddedResources/Lang/ro.xml
Normal 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>
|
||||
19
src/Umbraco.Core/Extensions/HtmlEncodedStringExtensions.cs
Normal file
19
src/Umbraco.Core/Extensions/HtmlEncodedStringExtensions.cs
Normal 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()));
|
||||
}
|
||||
@@ -30,5 +30,5 @@ public static class PasswordConfigurationExtensions
|
||||
};
|
||||
|
||||
public static int GetMinNonAlphaNumericChars(this IPasswordConfiguration passwordConfiguration) =>
|
||||
passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0;
|
||||
passwordConfiguration.RequireNonLetterOrDigit ? 1 : 0;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
16
src/Umbraco.Core/Models/ContentEditing/PublishContent.cs
Normal file
16
src/Umbraco.Core/Models/ContentEditing/PublishContent.cs
Normal 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; }
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
.umb-editor {
|
||||
box-shadow: 0px 0 30px 0 rgba(0,0,0,.3);
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -11,4 +11,6 @@ umb-media-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.checkeredBackground();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user