From 4d73d7f8bf6c5be7e2d78d3c54654f2a5e575b13 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Fri, 5 Dec 2025 16:00:39 +0100 Subject: [PATCH 1/9] Fix webforms lifecycle issues --- .../Controls/ClientResourceExclude.cs | 2 +- .../DotNetNuke.Web.Client/Controls/DnnCssInclude.cs | 11 +++++------ .../DotNetNuke.Web.Client/Controls/DnnHtmlInclude.cs | 4 ++-- .../DotNetNuke.Web.Client/Controls/DnnJsInclude.cs | 11 +++-------- .../Website/admin/Skins/DnnCssExclude.ascx.cs | 4 ++-- .../Website/admin/Skins/DnnCssInclude.ascx.cs | 4 ++-- DNN Platform/Website/admin/Skins/DnnJsExclude.ascx.cs | 4 ++-- DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs | 4 ++-- 8 files changed, 19 insertions(+), 25 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceExclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceExclude.cs index a4bbc788814..e44a629d4ef 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceExclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceExclude.cs @@ -36,7 +36,7 @@ protected ClientResourceExclude(IClientResourceController clientResourceControll /// public ClientDependencyType DependencyType { get; internal set; } - protected override void OnInit(EventArgs e) + protected override void OnLoad(EventArgs e) { switch (this.DependencyType) { diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs index 7db67e08452..14e00c7ae69 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs @@ -33,18 +33,17 @@ public DnnCssInclude(IClientResourceController clientResourceController) /// protected override void OnInit(EventArgs e) { + } + + /// + protected override void OnLoad(System.EventArgs e) + { this.clientResourceController.CreateStylesheet(this.FilePath, this.PathNameAlias) .SetNameAndVersion(this.Name, this.Version, this.ForceVersion) .SetProvider(this.ForceProvider) .SetPriority(this.Priority) .SetMedia(this.CssMedia) .Register(); - } - - /// - protected override void OnLoad(System.EventArgs e) - { - this.PathNameAlias = string.IsNullOrEmpty(this.PathNameAlias) ? string.Empty : this.PathNameAlias.ToLowerInvariant(); base.OnLoad(e); } diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnHtmlInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnHtmlInclude.cs index 1d34c4c10e6..94ae57ec92d 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnHtmlInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnHtmlInclude.cs @@ -47,9 +47,9 @@ public DnnHtmlInclude(IClientResourceController clientResourceController) /// public int Group { get; set; } = 100; - protected override void OnInit(EventArgs e) + protected override void OnLoad(EventArgs e) { - base.OnInit(e); + base.OnLoad(e); this.RegisterIncludes(this.Text); this.Text = string.Empty; } diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs index fe227bacf93..10975b54699 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs @@ -29,19 +29,14 @@ public DnnJsInclude(IClientResourceController clientResourceController) this.DependencyType = ClientDependencyType.Javascript; } - protected override void OnInit(EventArgs e) - { + /// + protected override void OnLoad(System.EventArgs e) + { this.clientResourceController.CreateScript(this.FilePath, this.PathNameAlias) .SetNameAndVersion(this.Name, this.Version, this.ForceVersion) .SetProvider(this.ForceProvider) .SetPriority(this.Priority) .Register(); - } - - /// - protected override void OnLoad(System.EventArgs e) - { - this.PathNameAlias = string.IsNullOrEmpty(this.PathNameAlias) ? string.Empty : this.PathNameAlias.ToLowerInvariant(); base.OnLoad(e); } diff --git a/DNN Platform/Website/admin/Skins/DnnCssExclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnCssExclude.ascx.cs index 167776c1258..55804abe4ca 100644 --- a/DNN Platform/Website/admin/Skins/DnnCssExclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnCssExclude.ascx.cs @@ -11,9 +11,9 @@ public partial class DnnCssExclude : SkinObjectBase public string Name { get; set; } /// - protected override void OnLoad(EventArgs e) + protected override void OnInit(EventArgs e) { - base.OnLoad(e); + base.OnInit(e); this.ctlExclude.Name = this.Name; } } diff --git a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs index 80482ee689e..2c25b079de5 100644 --- a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs @@ -31,9 +31,9 @@ public partial class DnnCssInclude : SkinObjectBase public bool ForceBundle { get; set; } /// - protected override void OnLoad(EventArgs e) + protected override void OnInit(EventArgs e) { - base.OnLoad(e); + base.OnInit(e); this.ctlInclude.AddTag = this.AddTag; this.ctlInclude.FilePath = this.FilePath; this.ctlInclude.ForceProvider = this.ForceProvider; diff --git a/DNN Platform/Website/admin/Skins/DnnJsExclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnJsExclude.ascx.cs index f8148ef1313..c0bbd771b7e 100644 --- a/DNN Platform/Website/admin/Skins/DnnJsExclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnJsExclude.ascx.cs @@ -11,9 +11,9 @@ public partial class DnnJsExclude : SkinObjectBase public string Name { get; set; } /// - protected override void OnLoad(EventArgs e) + protected override void OnInit(EventArgs e) { - base.OnLoad(e); + base.OnInit(e); this.ctlExclude.Name = this.Name; } } diff --git a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs index b5ee3fdae90..b376543e190 100644 --- a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs @@ -27,9 +27,9 @@ public partial class DnnJsInclude : SkinObjectBase public bool ForceBundle { get; set; } /// - protected override void OnLoad(EventArgs e) + protected override void OnInit(EventArgs e) { - base.OnLoad(e); + base.OnInit(e); this.ctlInclude.AddTag = this.AddTag; this.ctlInclude.FilePath = this.FilePath; this.ctlInclude.ForceProvider = this.ForceProvider; From ed0da06a0c58fd07a6e0a6bd015d8f920243ed18 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Sun, 7 Dec 2025 14:53:54 +0100 Subject: [PATCH 2/9] Fix the default for the web.config variable for upgrade uploading --- DNN Platform/Website/Install/Config/10.02.00.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DNN Platform/Website/Install/Config/10.02.00.config b/DNN Platform/Website/Install/Config/10.02.00.config index 0f12e8a6a09..9ea48fcaea3 100644 --- a/DNN Platform/Website/Install/Config/10.02.00.config +++ b/DNN Platform/Website/Install/Config/10.02.00.config @@ -2,7 +2,7 @@ - + From 466704375ff7dd0571fe19edda974412f4bc4882 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Tue, 9 Dec 2025 20:09:01 +0100 Subject: [PATCH 3/9] Update DNN Platform/Website/Install/Config/10.02.00.config Co-authored-by: Brian Dukes --- DNN Platform/Website/Install/Config/10.02.00.config | 1 - 1 file changed, 1 deletion(-) diff --git a/DNN Platform/Website/Install/Config/10.02.00.config b/DNN Platform/Website/Install/Config/10.02.00.config index 9ea48fcaea3..c3982e97c63 100644 --- a/DNN Platform/Website/Install/Config/10.02.00.config +++ b/DNN Platform/Website/Install/Config/10.02.00.config @@ -1,7 +1,6 @@ - From 19a135b0eb8c59193fc7cdf05872ed9d535a2a72 Mon Sep 17 00:00:00 2001 From: Daniel Valadas Date: Tue, 9 Dec 2025 15:08:33 -0500 Subject: [PATCH 4/9] Reintoduces "role" JWT claim to attempt to limit impact of breaking change (#6832) In #6356 a dependency update made an unintentional breaking change. Instead of the classic "role" claim we now give a "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" claim for roles. JWT in itself has no specs for roles or claims which is left to whatever implementation is targetted like OIDC, OAuth, etc. In DNN context it is a simple JWT but we do provide roles and the new package email values uses microsoft "standard" values that have been around in the Microsoft ecosystem since .Net Framework 4.5 and still lives all the way up to .Net 10. I am not sure if we should support our old "role" claim forever or the Microsoft claim as there are 0 specs about "role". IETF does have a spec about "roles" so if we would change it to that spec, it would still be a breaking change. What this PR does is provide both the old (<=9.13.9) behavior of "role" as well as the new microsoft way which may be better known in the .Net ecosystem. It also adds a deprecation note on both the plain and encrypted token to try and bring attention to this breacking change with the old way being obsolete and removed in v12. Closes #6829 --- .../Components/Common/Controllers/JwtController.cs | 14 +++++++++++++- .../Components/Entity/LoginResultData.cs | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/DNN Platform/Dnn.AuthServices.Jwt/Components/Common/Controllers/JwtController.cs b/DNN Platform/Dnn.AuthServices.Jwt/Components/Common/Controllers/JwtController.cs index c1c8766979c..2396b0ca20d 100644 --- a/DNN Platform/Dnn.AuthServices.Jwt/Components/Common/Controllers/JwtController.cs +++ b/DNN Platform/Dnn.AuthServices.Jwt/Components/Common/Controllers/JwtController.cs @@ -304,7 +304,19 @@ private static string CreateJwtToken(byte[] symmetricKey, string issuer, Persist var subject = new ClaimsIdentity(); subject.AddClaim(new Claim(SessionClaimType, persistedToken.TokenId)); - subject.AddClaims(roles.Select(r => new Claim(ClaimTypes.Role, r))); + + // Add roles using both the standard schema URI (ClaimTypes.Role) for standards compliance + // and the legacy "role" claim type for backward compatibility with existing consumers + foreach (var role in roles) + { + subject.AddClaim(new Claim(ClaimTypes.Role, role)); + subject.AddClaim(new Claim("role", role)); + } + + // Add deprecation notice for the legacy "role" claim format + subject.AddClaim(new Claim( + "dnn:deprecation:role", + "The role claim is deprecated. Use http://schemas.microsoft.com/ws/2008/06/identity/claims/role instead. The role claim will be removed in DNN v12.0.0.")); var notBefore = DateTime.UtcNow.AddMinutes(-ClockSkew); var expires = persistedToken.TokenExpiry; diff --git a/DNN Platform/Dnn.AuthServices.Jwt/Components/Entity/LoginResultData.cs b/DNN Platform/Dnn.AuthServices.Jwt/Components/Entity/LoginResultData.cs index f88cb1e0023..9f6656e9816 100644 --- a/DNN Platform/Dnn.AuthServices.Jwt/Components/Entity/LoginResultData.cs +++ b/DNN Platform/Dnn.AuthServices.Jwt/Components/Entity/LoginResultData.cs @@ -29,5 +29,10 @@ public class LoginResultData /// Gets or sets any error message. [JsonIgnore] public string Error { get; set; } + + /// Gets deprecation warnings about the JWT token format (included in all responses). + [JsonProperty("deprecationNotice")] + public string DeprecationNotice => + "The role claim format in JWT tokens is deprecated. Please use http://schemas.microsoft.com/ws/2008/06/identity/claims/role instead. The legacy role claim will be removed in DNN Platform v12.0.0."; } } From b5af3a8f511af5360bba52c57ca19c8be72bc1b1 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 10 Dec 2025 09:31:43 -0600 Subject: [PATCH 5/9] Add new CDF properties to skin object --- .../Controls/ClientResourceInclude.cs | 54 +++++++++++++++++- .../Controls/DnnCssInclude.cs | 50 +++++++---------- .../Controls/DnnJsInclude.cs | 55 +++++++++---------- .../Website/admin/Skins/DnnCssInclude.ascx.cs | 26 +++++++++ .../Website/admin/Skins/DnnJsInclude.ascx.cs | 27 +++++++++ 5 files changed, 149 insertions(+), 63 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs index 33dd38bef64..bd55e6f45d1 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs @@ -4,9 +4,13 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement { + using System; + using System.Collections.Generic; using System.Web.UI; + using DotNetNuke.Abstractions.ClientResources; using DotNetNuke.Web.Client.Cdf; + using DotNetNuke.Web.Client.ResourceManager; /// Represents an included client resource. public abstract class ClientResourceInclude : Control @@ -28,9 +32,6 @@ protected ClientResourceInclude() /// Gets or sets the priority for the client resource. Resources with lower priority values are included before those with higher values. public int Priority { get; set; } - /// Gets or sets the group for the client resource. Resources in the same group are processed together. - public int Group { get; set; } - /// Gets or sets the name of the script (e.g. jQuery, Bootstrap, Angular, etc.). public string Name { get; set; } @@ -48,5 +49,52 @@ protected ClientResourceInclude() /// Gets or sets a value indicating whether to add the HTML tag for this resource to the page output. public bool AddTag { get; set; } + + /// Gets or sets the CDN URL of the resource. + public string CdnUrl { get; set; } + + /// Gets or sets a value indicating whether to render the blocking attribute. + public bool Blocking { get; set; } + + /// Gets or sets the integrity hash of the resource. + public string Integrity { get; set; } + + /// Gets or sets the value of the crossorigin attribute. + public CrossOrigin CrossOrigin { get; set; } + + /// Gets or sets the value of the fetchpriority attribute. + public FetchPriority FetchPriority { get; set; } + + /// Gets or sets the value of the referrerpolicy attribute. + public ReferrerPolicy ReferrerPolicy { get; set; } + + /// Gets or sets the group for the client resource. Resources in the same group are processed together. + [Obsolete("Deprecated in DotNetNuke 10.2.0. Grouping is no longer supported, there is no replacement within DNN for this functionality. Scheduled removal in v12.0.0.")] + public int Group { get; set; } + + /// Gets or sets a value indicating whether to force this resource to be bundled. No longer supported. + [Obsolete("Deprecated in DotNetNuke 10.2.0. Bundling is no longer supported, there is no replacement within DNN for this functionality. Scheduled removal in v12.0.0.")] + public bool ForceBundle { get; set; } + + /// Sets common properties on the and registers it. + /// The resource to register. + protected void RegisterResource(IResource resource) + { + resource = resource + .SetNameAndVersion(this.Name, this.Version, this.ForceVersion) + .SetProvider(this.ForceProvider) + .SetPriority(this.Priority) + .SetCdnUrl(this.CdnUrl) + .SetIntegrity(this.Integrity) + .SetCrossOrigin(this.CrossOrigin) + .SetFetchPriority(this.FetchPriority) + .SetReferrerPolicy(this.ReferrerPolicy); + if (this.Blocking) + { + resource = resource.SetBlocking(); + } + + resource.Register(); + } } } diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs index 14e00c7ae69..fa3e729279c 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Web.Client.ClientResourceManagement +namespace DotNetNuke.Web.Client.ClientResourceManagement { using System; using System.Web.UI; @@ -11,49 +11,39 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement using DotNetNuke.Web.Client.Cdf; using DotNetNuke.Web.Client.ResourceManager; - /// Registers a CSS resource. - public class DnnCssInclude : ClientResourceInclude + /// Registers a CSS resource. + public class DnnCssInclude : ClientResourceInclude { private readonly IClientResourceController clientResourceController; - /// - /// Initializes a new instance of the class. - /// + /// Initializes a new instance of the class. /// The client resources controller. public DnnCssInclude(IClientResourceController clientResourceController) - : base() - { + { this.clientResourceController = clientResourceController; this.ForceProvider = ClientResourceProviders.DefaultCssProvider; - this.DependencyType = ClientDependencyType.Css; + this.DependencyType = ClientDependencyType.Css; } + /// Gets or sets the media attribute on the stylesheet. public string CssMedia { get; set; } /// - protected override void OnInit(EventArgs e) + protected override void OnLoad(System.EventArgs e) { + var stylesheet = this.clientResourceController.CreateStylesheet(this.FilePath, this.PathNameAlias) + .SetMedia(this.CssMedia); + this.RegisterResource(stylesheet); + base.OnLoad(e); } /// - protected override void OnLoad(System.EventArgs e) - { - this.clientResourceController.CreateStylesheet(this.FilePath, this.PathNameAlias) - .SetNameAndVersion(this.Name, this.Version, this.ForceVersion) - .SetProvider(this.ForceProvider) - .SetPriority(this.Priority) - .SetMedia(this.CssMedia) - .Register(); - base.OnLoad(e); + protected override void Render(HtmlTextWriter writer) + { + if (this.AddTag || this.Context.IsDebuggingEnabled) + { + writer.Write("", this.FilePath, this.ForceProvider, this.Priority); + } } - - /// - protected override void Render(HtmlTextWriter writer) - { - if (this.AddTag || this.Context.IsDebuggingEnabled) - { - writer.Write("", this.FilePath, this.ForceProvider, this.Priority); - } - } - } -} + } +} diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs index 10975b54699..2b5bd840c63 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs @@ -2,51 +2,46 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information -namespace DotNetNuke.Web.Client.ClientResourceManagement -{ +namespace DotNetNuke.Web.Client.ClientResourceManagement +{ using System; using System.Web.UI; using DotNetNuke.Abstractions.ClientResources; using DotNetNuke.Web.Client.Cdf; - using DotNetNuke.Web.Client.ResourceManager; - /// Registers a JavaScript resource. - public class DnnJsInclude : ClientResourceInclude - { + /// Registers a JavaScript resource. + public class DnnJsInclude : ClientResourceInclude + { private readonly IClientResourceController clientResourceController; - /// - /// Initializes a new instance of the class. - /// Sets up default settings for the control. - /// + /// + /// Initializes a new instance of the class. + /// Sets up default settings for the control. + /// /// The client resources controller. public DnnJsInclude(IClientResourceController clientResourceController) - : base() - { + { this.clientResourceController = clientResourceController; this.ForceProvider = ClientResourceProviders.DefaultJsProvider; - this.DependencyType = ClientDependencyType.Javascript; + this.DependencyType = ClientDependencyType.Javascript; } /// - protected override void OnLoad(System.EventArgs e) - { - this.clientResourceController.CreateScript(this.FilePath, this.PathNameAlias) - .SetNameAndVersion(this.Name, this.Version, this.ForceVersion) - .SetProvider(this.ForceProvider) - .SetPriority(this.Priority) - .Register(); - base.OnLoad(e); + protected override void OnLoad(System.EventArgs e) + { + var script = this.clientResourceController.CreateScript(this.FilePath, this.PathNameAlias); + this.RegisterResource(script); + base.OnLoad(e); } /// - protected override void Render(HtmlTextWriter writer) - { - if (this.AddTag || this.Context.IsDebuggingEnabled) - { - writer.Write("", this.FilePath, this.ForceProvider, this.Priority); - } - } - } -} + protected override void Render(HtmlTextWriter writer) + { + if (this.AddTag || this.Context.IsDebuggingEnabled) + { + writer.Write("", this.FilePath, this.ForceProvider, this.Priority); + } + } + } +} diff --git a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs index 2c25b079de5..3dc393005c0 100644 --- a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs @@ -5,6 +5,7 @@ namespace DotNetNuke.UI.Skins.Controls { using System; + using DotNetNuke.Abstractions.ClientResources; using DotNetNuke.Web.Client.Cdf; /// A control which causes CSS to be included on the page. @@ -28,8 +29,27 @@ public partial class DnnCssInclude : SkinObjectBase public string ForceProvider { get; set; } + [Obsolete("Deprecated in DotNetNuke 10.2.0. Bundling is no longer supported, there is no replacement within DNN for this functionality. Scheduled removal in v12.0.0.")] public bool ForceBundle { get; set; } + /// Gets or sets the CDN URL of the resource. + public string CdnUrl { get; set; } + + /// Gets or sets a value indicating whether to render the blocking attribute. + public bool Blocking { get; set; } + + /// Gets or sets the integrity hash of the resource. + public string Integrity { get; set; } + + /// Gets or sets the value of the crossorigin attribute. + public CrossOrigin CrossOrigin { get; set; } + + /// Gets or sets the value of the fetchpriority attribute. + public FetchPriority FetchPriority { get; set; } + + /// Gets or sets the value of the referrerpolicy attribute. + public ReferrerPolicy ReferrerPolicy { get; set; } + /// protected override void OnInit(EventArgs e) { @@ -42,6 +62,12 @@ protected override void OnInit(EventArgs e) this.ctlInclude.PathNameAlias = this.PathNameAlias; this.ctlInclude.Priority = this.Priority; this.ctlInclude.Version = this.Version; + this.ctlInclude.CdnUrl = this.CdnUrl; + this.ctlInclude.Blocking = this.Blocking; + this.ctlInclude.Integrity = this.Integrity; + this.ctlInclude.CrossOrigin = this.CrossOrigin; + this.ctlInclude.FetchPriority = this.FetchPriority; + this.ctlInclude.ReferrerPolicy = this.ReferrerPolicy; if (this.CssMedia != CssMediaType.None) { this.ctlInclude.CssMedia = this.CssMedia.ToString().ToLowerInvariant(); diff --git a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs index b376543e190..83db4a56493 100644 --- a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs @@ -5,6 +5,8 @@ namespace DotNetNuke.UI.Skins.Controls { using System; + using DotNetNuke.Abstractions.ClientResources; + /// A control which causes JavaScript to be included on the page. public partial class DnnJsInclude : SkinObjectBase { @@ -24,8 +26,27 @@ public partial class DnnJsInclude : SkinObjectBase public string ForceProvider { get; set; } + [Obsolete("Deprecated in DotNetNuke 10.2.0. Bundling is no longer supported, there is no replacement within DNN for this functionality. Scheduled removal in v12.0.0.")] public bool ForceBundle { get; set; } + /// Gets or sets the CDN URL of the resource. + public string CdnUrl { get; set; } + + /// Gets or sets a value indicating whether to render the blocking attribute. + public bool Blocking { get; set; } + + /// Gets or sets the integrity hash of the resource. + public string Integrity { get; set; } + + /// Gets or sets the value of the crossorigin attribute. + public CrossOrigin CrossOrigin { get; set; } + + /// Gets or sets the value of the fetchpriority attribute. + public FetchPriority FetchPriority { get; set; } + + /// Gets or sets the value of the referrerpolicy attribute. + public ReferrerPolicy ReferrerPolicy { get; set; } + /// protected override void OnInit(EventArgs e) { @@ -38,6 +59,12 @@ protected override void OnInit(EventArgs e) this.ctlInclude.PathNameAlias = this.PathNameAlias; this.ctlInclude.Priority = this.Priority; this.ctlInclude.Version = this.Version; + this.ctlInclude.CdnUrl = this.CdnUrl; + this.ctlInclude.Blocking = this.Blocking; + this.ctlInclude.Integrity = this.Integrity; + this.ctlInclude.CrossOrigin = this.CrossOrigin; + this.ctlInclude.FetchPriority = this.FetchPriority; + this.ctlInclude.ReferrerPolicy = this.ReferrerPolicy; } } } From 99bf50b9b0f6ba7933004f249be9d28cb8a28962 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 10 Dec 2025 09:32:03 -0600 Subject: [PATCH 6/9] Add HtmlAttributesAsString property back Fixes #6839 --- .../Controls/ClientResourceInclude.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs index bd55e6f45d1..c6bc71bc476 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/ClientResourceInclude.cs @@ -68,6 +68,13 @@ protected ClientResourceInclude() /// Gets or sets the value of the referrerpolicy attribute. public ReferrerPolicy ReferrerPolicy { get; set; } + /// Gets additional attributes in the HTML markup for the resource. + public IDictionary HtmlAttributes { get; private set; } = new Dictionary(); + + /// Gets or sets the for this resource via a string which is parsed. + /// The syntax for the string must be: key1:value1,key2:value2 etc. + public string HtmlAttributesAsString { get; set; } + /// Gets or sets the group for the client resource. Resources in the same group are processed together. [Obsolete("Deprecated in DotNetNuke 10.2.0. Grouping is no longer supported, there is no replacement within DNN for this functionality. Scheduled removal in v12.0.0.")] public int Group { get; set; } @@ -94,7 +101,91 @@ protected void RegisterResource(IResource resource) resource = resource.SetBlocking(); } + var attributes = this.HtmlAttributes; + ParseHtmlAttributesIntoDictionary(this.HtmlAttributesAsString, attributes); + + foreach (var attribute in attributes) + { + resource.AddAttribute(attribute.Key, attribute.Value); + } + resource.Register(); } + + /// + private static void ParseHtmlAttributesIntoDictionary(string attributes, IDictionary destination) + { + if (string.IsNullOrEmpty(attributes)) + { + return; + } + + var key = string.Empty; + var val = string.Empty; + var isKey = true; + var isVal = false; + var isValDelimited = false; + for (var i = 0; i < attributes.Length; i++) + { + var c = attributes.ToCharArray()[i]; + if (isKey && c == ':') + { + isKey = false; + isVal = true; + continue; + } + + if (isKey) + { + key += c; + } + + if (!isVal) + { + continue; + } + + if (c == '\'') + { + if (!isValDelimited) + { + isValDelimited = true; + continue; + } + else + { + isValDelimited = false; + if (i == attributes.Length - 1) + { + // if it is the end, add/replace the value + destination[key] = val; + } + + continue; + } + } + + if (c == ',' && !isValDelimited) + { + // we've reached a comma and the value is no longer delimited, this means we create a new key + isKey = true; + isVal = false; + + // now we can add/replace the current value to the dictionary + destination[key] = val; + key = string.Empty; + val = string.Empty; + continue; + } + + val += c; + + if (i == attributes.Length - 1) + { + // if it is the end, add/replace the value + destination[key] = val; + } + } + } } } From 66f80e891ff07530a28da9d815df79b320521ff2 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 10 Dec 2025 09:42:43 -0600 Subject: [PATCH 7/9] Add specific JS & CSS properties --- .../Controls/DnnCssInclude.cs | 10 +++++++- .../Controls/DnnJsInclude.cs | 25 +++++++++++++++++++ .../Website/admin/Skins/DnnCssInclude.ascx.cs | 4 +++ .../Website/admin/Skins/DnnJsInclude.ascx.cs | 12 +++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs index fa3e729279c..7cc7cf213cc 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnCssInclude.cs @@ -25,14 +25,22 @@ public DnnCssInclude(IClientResourceController clientResourceController) this.DependencyType = ClientDependencyType.Css; } - /// Gets or sets the media attribute on the stylesheet. + /// public string CssMedia { get; set; } + /// + public bool Preload { get; set; } + /// protected override void OnLoad(System.EventArgs e) { var stylesheet = this.clientResourceController.CreateStylesheet(this.FilePath, this.PathNameAlias) .SetMedia(this.CssMedia); + if (this.Preload) + { + stylesheet.SetPreload(); + } + this.RegisterResource(stylesheet); base.OnLoad(e); } diff --git a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs index 2b5bd840c63..80a01f7a295 100644 --- a/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs +++ b/DNN Platform/DotNetNuke.Web.Client/Controls/DnnJsInclude.cs @@ -9,6 +9,7 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement using DotNetNuke.Abstractions.ClientResources; using DotNetNuke.Web.Client.Cdf; + using DotNetNuke.Web.Client.ResourceManager; /// Registers a JavaScript resource. public class DnnJsInclude : ClientResourceInclude @@ -27,10 +28,34 @@ public DnnJsInclude(IClientResourceController clientResourceController) this.DependencyType = ClientDependencyType.Javascript; } + /// + public bool Async { get; set; } + + /// + public bool Defer { get; set; } + + /// + public bool NoModule { get; set; } + /// protected override void OnLoad(System.EventArgs e) { var script = this.clientResourceController.CreateScript(this.FilePath, this.PathNameAlias); + if (this.Async) + { + script = script.SetAsync(); + } + + if (this.Defer) + { + script = script.SetDefer(); + } + + if (this.NoModule) + { + script = script.SetNoModule(); + } + this.RegisterResource(script); base.OnLoad(e); } diff --git a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs index 3dc393005c0..3cb0acec304 100644 --- a/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnCssInclude.ascx.cs @@ -50,6 +50,9 @@ public partial class DnnCssInclude : SkinObjectBase /// Gets or sets the value of the referrerpolicy attribute. public ReferrerPolicy ReferrerPolicy { get; set; } + /// Gets or sets a value indicating whether the client resource should be preloaded. + public bool Preload { get; set; } + /// protected override void OnInit(EventArgs e) { @@ -68,6 +71,7 @@ protected override void OnInit(EventArgs e) this.ctlInclude.CrossOrigin = this.CrossOrigin; this.ctlInclude.FetchPriority = this.FetchPriority; this.ctlInclude.ReferrerPolicy = this.ReferrerPolicy; + this.ctlInclude.Preload = this.Preload; if (this.CssMedia != CssMediaType.None) { this.ctlInclude.CssMedia = this.CssMedia.ToString().ToLowerInvariant(); diff --git a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs index 83db4a56493..5569ee782d4 100644 --- a/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs +++ b/DNN Platform/Website/admin/Skins/DnnJsInclude.ascx.cs @@ -47,6 +47,15 @@ public partial class DnnJsInclude : SkinObjectBase /// Gets or sets the value of the referrerpolicy attribute. public ReferrerPolicy ReferrerPolicy { get; set; } + /// + public bool Async { get; set; } + + /// + public bool Defer { get; set; } + + /// + public bool NoModule { get; set; } + /// protected override void OnInit(EventArgs e) { @@ -65,6 +74,9 @@ protected override void OnInit(EventArgs e) this.ctlInclude.CrossOrigin = this.CrossOrigin; this.ctlInclude.FetchPriority = this.FetchPriority; this.ctlInclude.ReferrerPolicy = this.ReferrerPolicy; + this.ctlInclude.Async = this.Async; + this.ctlInclude.Defer = this.Defer; + this.ctlInclude.NoModule = this.NoModule; } } } From 50c5228505304ff6b424bd61be0e47ef33f9948d Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 10 Dec 2025 09:47:53 -0600 Subject: [PATCH 8/9] Throw for unexpected enum values --- .../Models/ResourceBase.cs | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs b/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs index d73c8f9b2b2..eee573fbafa 100644 --- a/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs +++ b/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs @@ -14,8 +14,6 @@ namespace DotNetNuke.Web.Client.ResourceManager.Models /// Base class for all resource types. public abstract class ResourceBase : IResource { - private string name; - /// public string FilePath { get; set; } @@ -123,16 +121,18 @@ protected void RenderBlocking(StringBuilder htmlString) /// The HTML string builder to append to. protected void RenderCrossOriginAttribute(StringBuilder htmlString) { - if (this.CrossOrigin != CrossOrigin.None) + switch (this.CrossOrigin) { - if (this.CrossOrigin == CrossOrigin.UseCredentials) - { + case CrossOrigin.UseCredentials: htmlString.Append($" crossorigin=\"use-credentials\""); - } - else - { + return; + case CrossOrigin.Anonymous: htmlString.Append($" crossorigin=\"anonymous\""); - } + return; + case CrossOrigin.None: + return; + default: + throw new InvalidOperationException($"Unexpected CrossOrigin value: {this.CrossOrigin}"); } } @@ -140,16 +140,18 @@ protected void RenderCrossOriginAttribute(StringBuilder htmlString) /// The HTML string builder to append to. protected void RenderFetchPriority(StringBuilder htmlString) { - if (this.FetchPriority != FetchPriority.Auto) + switch (this.FetchPriority) { - if (this.FetchPriority == FetchPriority.High) - { + case FetchPriority.High: htmlString.Append($" fetchpriority=\"high\""); - } - else if (this.FetchPriority == FetchPriority.Low) - { + return; + case FetchPriority.Low: htmlString.Append($" fetchpriority=\"low\""); - } + return; + case FetchPriority.Auto: + return; + default: + throw new InvalidOperationException($"Unexpected FetchPriority value: {this.FetchPriority}"); } } @@ -167,35 +169,36 @@ protected void RenderIntegrity(StringBuilder htmlString) /// The HTML string builder to append to. protected void RenderReferrerPolicy(StringBuilder htmlString) { - if (this.ReferrerPolicy != ReferrerPolicy.None) + switch (this.ReferrerPolicy) { - switch (this.ReferrerPolicy) - { - case ReferrerPolicy.NoReferrer: - htmlString.Append(" referrerpolicy=\"no-referrer\""); - break; - case ReferrerPolicy.NoReferrerWhenDowngrade: - htmlString.Append(" referrerpolicy=\"no-referrer-when-downgrade\""); - break; - case ReferrerPolicy.Origin: - htmlString.Append(" referrerpolicy=\"origin\""); - break; - case ReferrerPolicy.OriginWhenCrossOrigin: - htmlString.Append(" referrerpolicy=\"origin-when-cross-origin\""); - break; - case ReferrerPolicy.SameOrigin: - htmlString.Append(" referrerpolicy=\"same-origin\""); - break; - case ReferrerPolicy.StrictOrigin: - htmlString.Append(" referrerpolicy=\"strict-origin\""); - break; - case ReferrerPolicy.StrictOriginWhenCrossOrigin: - htmlString.Append(" referrerpolicy=\"strict-origin-when-cross-origin\""); - break; - case ReferrerPolicy.UnsafeUrl: - htmlString.Append(" referrerpolicy=\"unsafe-url\""); - break; - } + case ReferrerPolicy.NoReferrer: + htmlString.Append(" referrerpolicy=\"no-referrer\""); + break; + case ReferrerPolicy.NoReferrerWhenDowngrade: + htmlString.Append(" referrerpolicy=\"no-referrer-when-downgrade\""); + break; + case ReferrerPolicy.Origin: + htmlString.Append(" referrerpolicy=\"origin\""); + break; + case ReferrerPolicy.OriginWhenCrossOrigin: + htmlString.Append(" referrerpolicy=\"origin-when-cross-origin\""); + break; + case ReferrerPolicy.SameOrigin: + htmlString.Append(" referrerpolicy=\"same-origin\""); + break; + case ReferrerPolicy.StrictOrigin: + htmlString.Append(" referrerpolicy=\"strict-origin\""); + break; + case ReferrerPolicy.StrictOriginWhenCrossOrigin: + htmlString.Append(" referrerpolicy=\"strict-origin-when-cross-origin\""); + break; + case ReferrerPolicy.UnsafeUrl: + htmlString.Append(" referrerpolicy=\"unsafe-url\""); + break; + case ReferrerPolicy.None: + return; + default: + throw new InvalidOperationException($"Unexpected ReferrerPolicy value: {this.ReferrerPolicy}"); } } From 8b9550d70bbbd6ebd891d5d75ab5cb9b8e8470d6 Mon Sep 17 00:00:00 2001 From: Brian Dukes Date: Wed, 10 Dec 2025 12:00:47 -0600 Subject: [PATCH 9/9] Add back name field --- .../Models/ResourceBase.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs b/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs index eee573fbafa..4d3807740b0 100644 --- a/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs +++ b/DNN Platform/DotNetNuke.Web.Client.ResourceManager/Models/ResourceBase.cs @@ -14,6 +14,8 @@ namespace DotNetNuke.Web.Client.ResourceManager.Models /// Base class for all resource types. public abstract class ResourceBase : IResource { + private string name; + /// public string FilePath { get; set; }