diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/IPageService.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/IPageService.cs new file mode 100644 index 00000000000..8d19654b8d7 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/IPageService.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// Provides services for managing page content with priority-based storage. + /// + public interface IPageService + { + /// + /// Set the Page Title. + /// + /// The title value to set. + /// The priority of this title (lower number = higher priority). + void SetTitle(string value, int priority = PagePriority.Default); + + /// + /// Set the Page Description. + /// + /// The description value to set. + /// The priority of this description (lower number = higher priority). + void SetDescription(string value, int priority = PagePriority.Default); + + /// + /// Set the Page Keywords. + /// + /// The keywords value to set. + /// The priority of these keywords (lower number = higher priority). + void SetKeyWords(string value, int priority = PagePriority.Default); + + /// + /// Set the Page Canonical Link URL. + /// + /// The canonical link URL value to set. + /// The priority of this canonical link URL (lower number = higher priority). + void SetCanonicalLinkUrl(string value, int priority = PagePriority.Default); + + /// + /// Add a tag to the header of the page. + /// + /// Priority item containing the tag and priority information. + void AddToHead(PageTag tagItem); + + /// + /// Add a standard meta header tag. + /// + /// Priority meta item containing the meta tag information and priority. + void AddMeta(PageMeta metaItem); + + /// + /// Add a message to be displayed on the page. + /// + /// Priority message item containing the message information and priority. + void AddMessage(PageMessage messageItem); + + /// + /// Gets the current title value with highest priority. + /// + /// The title value or null if not set. + string GetTitle(); + + /// + /// Gets the current description value with highest priority. + /// + /// The description value or null if not set. + string GetDescription(); + + /// + /// Gets the current keywords value with highest priority. + /// + /// The keywords value or null if not set. + string GetKeyWords(); + + /// + /// Gets the canonical link URL. + /// + /// The canonical link URL or null if not set. + string GetCanonicalLinkUrl(); + + /// + /// Gets all head tags ordered by priority (lowest priority first). + /// + /// List of head tags ordered by priority. + IEnumerable GetHeadTags(); + + /// + /// Gets all meta tags ordered by priority (lowest priority first). + /// + /// List of meta tags ordered by priority. + IEnumerable GetMetaTags(); + + /// + /// Gets all messages ordered by priority (lowest priority first). + /// + /// List of messages ordered by priority. + IEnumerable GetMessages(); + + /// + /// Clears all stored page data. Useful for testing or resetting state. + /// + void Clear(); + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessage.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessage.cs new file mode 100644 index 00000000000..75d2b5add53 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessage.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + /// + /// Represents a message with priority information for display on a page. + /// Messages can include success notifications, warnings, errors, or informational content. + /// + /// + /// This class is used to store messages that will be displayed to users on a page. + /// Priority determines the order in which messages are displayed, with lower numbers having higher priority. + /// Common priority values are defined in . + /// + /// + /// + /// // Create a success message with high priority + /// var successMessage = new PriorityMessage( + /// "Operation Successful", + /// "The data has been saved successfully.", + /// MessageType.Success, + /// "/images/success-icon.png", + /// PagePriority.Site); + /// + /// // Create an error message with default priority + /// var errorMessage = new PriorityMessage( + /// "Validation Error", + /// "Please check the required fields.", + /// MessageType.Error, + /// "", + /// PagePriority.Default); + /// + /// + public class PageMessage + { + /// + /// Initializes a new instance of the class. + /// + /// The heading text for the message. + /// The message text content. Cannot be null or empty. + /// The type/severity of the message. + /// The optional icon source URL for the message. Use empty string if no icon is needed. + /// The priority of the message. Use values from for consistency. + public PageMessage(string heading, string message, PageMessageType messageType, string iconSrc, int priority) + { + this.Heading = heading; + this.Message = message; + this.MessageType = messageType; + this.IconSrc = iconSrc; + this.Priority = priority; + } + + /// + /// Gets or sets the heading text for the message. + /// + /// + /// A string containing the heading text that will be displayed prominently. + /// This should be a concise summary of the message. + /// + public string Heading { get; set; } + + /// + /// Gets or sets the message text content. + /// + /// + /// A string containing the detailed message content that will be displayed to the user. + /// This can contain HTML markup for formatting. + /// + public string Message { get; set; } + + /// + /// Gets or sets the type/severity of the message. + /// + /// + /// A value indicating the severity or type of the message. + /// This affects how the message is styled and displayed to the user. + /// + public PageMessageType MessageType { get; set; } + + /// + /// Gets or sets the optional icon source URL for the message. + /// + /// + /// A string containing the URL or path to an icon image, or an empty string if no icon is needed. + /// The icon will be displayed alongside the message to provide visual context. + /// + public string IconSrc { get; set; } + + /// + /// Gets or sets the priority of the message (lower number = higher priority). + /// + /// + /// An integer representing the display priority of the message. + /// Messages with lower priority numbers will be displayed before those with higher numbers. + /// Use constants from for consistent priority values. + /// + public int Priority { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessageType.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessageType.cs new file mode 100644 index 00000000000..6bcb3206a64 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMessageType.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + /// + /// Defines the types and severity levels for page messages. + /// Message types determine how messages are styled and presented to users. + /// + /// + /// These enumeration values are used to categorize messages by their purpose and severity. + /// The UI layer typically uses these values to apply appropriate styling, colors, and icons + /// to provide visual context to users about the nature of the message. + /// + /// + /// + /// // Success message for completed operations + /// var successMsg = new PageMessage("Operation Complete", "Data saved successfully", MessageType.Success, "", PagePriority.Default); + /// + /// // Warning message for potential issues + /// var warningMsg = new PageMessage("Warning", "This action cannot be undone", MessageType.Warning, "", PagePriority.Default); + /// + /// // Error message for failed operations + /// var errorMsg = new PageMessage("Error", "Failed to save data", MessageType.Error, "", PagePriority.Default); + /// + /// // Informational message for general notifications + /// var infoMsg = new PageMessage("Notice", "System maintenance scheduled", MessageType.Info, "", PagePriority.Default); + /// + /// + public enum PageMessageType + { + /// + /// Success message type. + /// Used for messages indicating successful completion of operations. + /// Typically displayed with green styling and success icons. + /// + /// + /// Use for: Data saved, user created, operation completed, etc. + /// + Success = 0, + + /// + /// Warning message type. + /// Used for messages indicating potential issues or important notices that require user attention. + /// Typically displayed with yellow/orange styling and warning icons. + /// + /// + /// Use for: Validation warnings, deprecation notices, cautionary information, etc. + /// + Warning = 1, + + /// + /// Error message type. + /// Used for messages indicating failed operations or critical issues. + /// Typically displayed with red styling and error icons. + /// + /// + /// Use for: Validation errors, operation failures, system errors, etc. + /// + Error = 2, + + /// + /// Informational message type. + /// Used for general notifications and informational content. + /// Typically displayed with blue styling and info icons. + /// + /// + /// Use for: General notifications, tips, system status updates, etc. + /// + Info = 3, + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/PageMeta.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMeta.cs new file mode 100644 index 00000000000..d68fa97c475 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/PageMeta.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + /// + /// Represents a meta tag with priority information for inclusion in the HTML head section. + /// Meta tags provide metadata about the HTML document and are used by search engines and browsers. + /// + /// + /// This class is used to store meta tag information that will be rendered in the HTML head section. + /// Priority determines the order in which meta tags are rendered, with lower numbers having higher priority. + /// Common priority values are defined in . + /// + /// + /// + /// // Create a description meta tag with page priority + /// var descriptionMeta = new PriorityMeta( + /// "description", + /// "This is the page description for SEO purposes.", + /// PagePriority.Page); + /// + /// // Create a keywords meta tag with default priority + /// var keywordsMeta = new PriorityMeta( + /// "keywords", + /// "DNN, CMS, content management", + /// PagePriority.Default); + /// + /// // Create a viewport meta tag with site priority + /// var viewportMeta = new PriorityMeta( + /// "viewport", + /// "width=device-width, initial-scale=1.0", + /// PagePriority.Site); + /// + /// + public class PageMeta + { + /// + /// Initializes a new instance of the class. + /// + /// The name attribute of the meta tag. Cannot be null or empty. + /// The content attribute of the meta tag. Cannot be null. + /// The priority of the meta tag (lower number = higher priority). Use values from for consistency. + /// Thrown when name or content is null. + /// Thrown when name is empty. + public PageMeta(string name, string content, int priority) + { + this.Name = name; + this.Content = content; + this.Priority = priority; + } + + /// + /// Gets or sets the name attribute of the meta tag. + /// + /// + /// A string containing the name attribute value for the meta tag. + /// Common values include "description", "keywords", "author", "viewport", etc. + /// This will be rendered as: <meta name="[Name]" content="[Content]" />. + /// + public string Name { get; set; } + + /// + /// Gets or sets the content attribute of the meta tag. + /// + /// + /// A string containing the content attribute value for the meta tag. + /// This contains the actual metadata information associated with the name attribute. + /// The content should be appropriate for the specified name attribute. + /// + public string Content { get; set; } + + /// + /// Gets or sets the priority of the meta tag (lower number = higher priority). + /// + /// + /// An integer representing the rendering priority of the meta tag in the HTML head section. + /// Meta tags with lower priority numbers will be rendered before those with higher numbers. + /// Use constants from for consistent priority values. + /// + public int Priority { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/PagePriority.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/PagePriority.cs new file mode 100644 index 00000000000..8638ad3ec18 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/PagePriority.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + /// + /// Defines standard priority values for page content ordering. + /// Lower numbers indicate higher priority and will be processed/rendered first. + /// + /// + /// These constants provide a standardized way to assign priorities to page content such as + /// scripts, styles, meta tags, and messages. Using these predefined values ensures consistent + /// ordering across the application and makes the code more maintainable. + /// + /// Priority processing order: + /// 1. Site-level content (priority 10) + /// 2. Page-level content (priority 20) + /// 3. Default content (priority 100) + /// 4. Module-level content (priority 200). + /// + public static class PagePriority + { + /// + /// Site-level priority (value: 10). + /// Used for framework site-wide content + /// that should be loaded before any other content. + /// + public const int Site = 10; + + /// + /// Page-level priority (value: 20). + /// Used for page-specific content that should be loaded after site-level content + /// but before default and module content. + /// + /// + /// + /// // Page-specific JavaScript + /// pageService.AddToHead(new PriorityItem("<script src=\"page-analytics.js\"></script>", PagePriority.Page)); + /// + /// // Page-specific meta description + /// pageService.AddMeta(new PriorityMeta("description", "Page-specific description", PagePriority.Page)); + /// + /// + public const int Page = 20; + + /// + /// Default priority (value: 100). + /// Used as the standard priority when no specific priority is needed. + /// Most content should use this priority unless there's a specific ordering requirement. + /// + /// + /// + /// // Standard content without specific ordering requirements + /// pageService.AddMessage(new PriorityMessage("Info", "General information", MessageType.Info, "", PagePriority.Default)); + /// + /// // Regular meta tags + /// pageService.AddMeta(new PriorityMeta("author", "John Doe", PagePriority.Default)); + /// + /// + public const int Default = 100; + + /// + /// Module-level priority (value: 200). + /// Used for module-specific content that should be loaded after all other content. + /// This ensures that module content doesn't interfere with core functionality. + /// + /// + /// + /// // Module-specific success message + /// pageService.AddMessage(new PriorityMessage("Success", "Module operation completed", MessageType.Success, "", PagePriority.Module)); + /// + /// + public const int Module = 200; + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/PageTag.cs b/DNN Platform/DotNetNuke.Abstractions/Pages/PageTag.cs new file mode 100644 index 00000000000..99837a6e096 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/PageTag.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Abstractions.Pages +{ + /// + /// Represents a string item with priority information for inclusion in page content. + /// This class is used for HTML tags, scripts, styles, and other string-based content that needs priority-based ordering. + /// + /// + /// This class is commonly used for HTML tags that need to be injected into different sections of a page + /// (head, body top, body end) with specific priority ordering. Priority determines the order in which + /// items are rendered. + /// Common priority values are defined in . + /// + /// + /// + /// // Create a script tag with high priority for the head section + /// var scriptTag = new PriorityItem( + /// "<script src=\"/js/framework.js\"></script>", + /// PagePriority.Site); + /// + /// // Create a style tag with module priority + /// var styleTag = new PriorityItem( + /// "<link rel=\"stylesheet\" href=\"/css/module.css\" />", + /// PagePriority.Module); + /// + /// // Create a meta tag with page priority + /// var metaTag = new PriorityItem( + /// "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />", + /// PagePriority.Page); + /// + /// + public class PageTag + { + /// + /// Initializes a new instance of the class. + /// + /// The string value of the item. Cannot be null. + /// The priority of the item . Use values from for consistency. + /// Thrown when value is null. + public PageTag(string value, int priority) + { + this.Value = value; + this.Priority = priority; + } + + /// + /// Gets or sets the string value of the item. + /// + /// + /// A string containing the content to be rendered. This is typically HTML content such as + /// script tags, link tags, meta tags, or other HTML elements. The content should be properly + /// formatted HTML that is valid for the intended injection location. + /// + public string Value { get; set; } + + /// + /// Gets or sets the priority of the item. + /// + /// + /// An integer representing the rendering priority of the item. + /// Items with lower priority numbers will be rendered before those with higher numbers. + /// Use constants from for consistent priority values: + /// - (10): Framework and site-level content + /// - (20): Page-specific content + /// - (100): Default priority for most content + /// - (200): Module-specific content. + /// + public int Priority { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Abstractions/Pages/readme.md b/DNN Platform/DotNetNuke.Abstractions/Pages/readme.md new file mode 100644 index 00000000000..8042ce7b144 --- /dev/null +++ b/DNN Platform/DotNetNuke.Abstractions/Pages/readme.md @@ -0,0 +1,100 @@ +### Enhancement: Priority-based IPageService for page metadata, head tags, and messages + +#### Summary +Add a simple, priority-driven API for setting page title/description/keywords/canonical URL, injecting head tags and meta tags, and collecting user-visible messages. This formalizes page composition logic and decouples it from the rendering pipeline, supporting both the current WebForms pipeline and the new MVC pipeline. + +#### Motivation +- **Unify page composition**: Provide a single abstraction for metadata, head elements, and messages across pipelines. +- **Deterministic ordering**: Ensure consistent output using explicit priorities. +- **Progress toward MVC/Core**: Keeps page state management out of WebForms, easing the hybrid transition. +- **Testability**: A small, mockable surface that’s easy to unit test. + +#### Scope +- Interfaces and models: + - `IPageService` + - `PageMessage`, `PageMessageType` + - `PageMeta` + - `PageTag` + - `PagePriority` + + +#### Proposed API (already defined) +- **Title/SEO** + - `void SetTitle(string value, int priority = PagePriority.Default)` + - `void SetDescription(string value, int priority = PagePriority.Default)` + - `void SetKeyWords(string value, int priority = PagePriority.Default)` + - `void SetCanonicalLinkUrl(string value, int priority = PagePriority.Default)` + - `string GetTitle()`, `string GetDescription()`, `string GetKeyWords()`, `string GetCanonicalLinkUrl()` +- **Head content** + - `void AddToHead(PageTag tagItem)` + - `List GetHeadTags()` + - `void AddMeta(PageMeta metaItem)` + - `List GetMetaTags()` +- **Messages** + - `void AddMessage(PageMessage messageItem)` + - `List GetMessages()` +- **Maintenance** + - `void Clear()` +- **Priority model** + - `PagePriority.Site = 10`, `PagePriority.Page = 20`, `PagePriority.Default = 100`, `PagePriority.Module = 200` +- **Message model** + - `PageMessageType`: `Success`, `Warning`, `Error`, `Info` + - `PageMessage`: `Heading`, `Message`, `MessageType`, `IconSrc`, `Priority` +- **Meta/head models** + - `PageMeta`: `Name`, `Content`, `Priority` + - `PageTag`: `Value`, `Priority` + +#### Example usage +```csharp +pageService.SetTitle("Products", PagePriority.Page); +pageService.SetDescription("Browse our product catalog.", PagePriority.Page); +pageService.SetCanonicalLinkUrl("https://www.mysite.com/products", PagePriority.Page); + +pageService.AddMeta(new PageMeta("viewport", "width=device-width, initial-scale=1.0", PagePriority.Site)); +pageService.AddToHead(new PageTag("", PagePriority.Page)); + +pageService.AddMessage(new PageMessage( + "Saved", "Your product was updated.", PageMessageType.Success, "", PagePriority.Default)); +``` + +#### Rendering contract (follow-up implementation) +- The renderer (WebForms skin, MVC layout) must: + - Pick the highest-priority values for title/description/keywords/canonical link. + - Render `GetMetaTags()` and `GetHeadTags()` in ascending `Priority` order. + - Render `GetMessages()` in ascending `Priority` order, with styling determined by `PageMessageType`. + + + + + +#### Convenience extensions (`Library/Services/Pages/PageExtensions.cs`) +These helpers provide simpler overloads for common operations: + +```csharp +// Head content +void IPageService.AddToHead(string tag, int priority = PagePriority.Default); +void IPageService.AddMeta(string name, string content, int priority = PagePriority.Default); + +// Messages with explicit type +void IPageService.AddMessage(string heading, string message, PageMessageType messageType, int priority = PagePriority.Default); +void IPageService.AddMessage(string heading, string message, PageMessageType messageType, string iconSrc, int priority = PagePriority.Default); + +// Convenience by type +void IPageService.AddSuccessMessage(string heading, string message, int priority = PagePriority.Default); +void IPageService.AddErrorMessage(string heading, string message, int priority = PagePriority.Default); +void IPageService.AddWarningMessage(string heading, string message, int priority = PagePriority.Default); +void IPageService.AddInfoMessage(string heading, string message, int priority = PagePriority.Default); + +// Mapping helper for UI layer +DotNetNuke.UI.Skins.Controls.ModuleMessage.ModuleMessageType PageMessageType.ToModuleMessageType(); +``` + +Example: +```csharp +pageService.AddToHead("", PagePriority.Site); +pageService.AddMeta("robots", "max-image-preview:large", PagePriority.Page); + +pageService.AddSuccessMessage("Saved", "Settings updated successfully."); +pageService.AddWarningMessage("Heads up", "This feature is experimental.", PagePriority.Page); +``` + diff --git a/DNN Platform/HttpModules/Membership/MembershipModule.cs b/DNN Platform/HttpModules/Membership/MembershipModule.cs index 13b581af4b9..1c1ccccb67e 100644 --- a/DNN Platform/HttpModules/Membership/MembershipModule.cs +++ b/DNN Platform/HttpModules/Membership/MembershipModule.cs @@ -5,12 +5,14 @@ namespace DotNetNuke.HttpModules.Membership { using System; using System.Linq; + using System.Runtime.CompilerServices; using System.Security.Principal; using System.Threading; using System.Web; using System.Web.Security; using DotNetNuke.Abstractions.Application; + using DotNetNuke.Abstractions.Pages; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Host; @@ -66,10 +68,12 @@ public MembershipModule(IHostSettingsService hostSettingsService, IPortalControl /// Called when unverified user skin initialize. /// The sender. /// The instance containing the event data. + [Obsolete("This method has been deprecated. No equivalent. Scheduled removal in v11.0.0.")] public static void OnUnverifiedUserSkinInit(object sender, SkinEventArgs e) { + var pageService = Globals.GetCurrentServiceProvider().GetRequiredService(); var strMessage = Localization.GetString("UnverifiedUser", Localization.SharedResourceFile, Thread.CurrentThread.CurrentCulture.Name); - UI.Skins.Skin.AddPageMessage(e.Skin, string.Empty, strMessage, ModuleMessage.ModuleMessageType.YellowWarning); + pageService.AddMessage(new PageMessage(string.Empty, strMessage, PageMessageType.Warning, string.Empty, PagePriority.Site)); } /// Authenticates the request. diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 650546a4ae6..2a2598b5584 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -890,6 +890,8 @@ + + diff --git a/DNN Platform/Library/Framework/JavaScriptLibraries/JavaScript.cs b/DNN Platform/Library/Framework/JavaScriptLibraries/JavaScript.cs index 76e541932a0..1d3b7d4088a 100644 --- a/DNN Platform/Library/Framework/JavaScriptLibraries/JavaScript.cs +++ b/DNN Platform/Library/Framework/JavaScriptLibraries/JavaScript.cs @@ -12,6 +12,7 @@ namespace DotNetNuke.Framework.JavaScriptLibraries using DotNetNuke.Abstractions.Application; using DotNetNuke.Abstractions.Logging; + using DotNetNuke.Abstractions.Pages; using DotNetNuke.Abstractions.Portals; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; @@ -602,9 +603,11 @@ private static void LogCollision(IEventLogger eventLogger, IPortalSettings porta UserController.Instance.GetCurrentUserInfo().UserID, EventLogType.SCRIPT_COLLISION); var strMessage = Localization.GetString("ScriptCollision", Localization.SharedResourceFile); - if (HttpContextSource.Current?.Handler is Page page) + + if (HttpContextSource.Current != null) { - Skin.AddPageMessage(page, string.Empty, strMessage, ModuleMessage.ModuleMessageType.YellowWarning); + var pageService = Globals.GetCurrentServiceProvider().GetRequiredService(); + pageService.AddMessage(new PageMessage(string.Empty, strMessage, PageMessageType.Warning, string.Empty, PagePriority.Default)); } } diff --git a/DNN Platform/Library/Services/Pages/PageExtensions.cs b/DNN Platform/Library/Services/Pages/PageExtensions.cs new file mode 100644 index 00000000000..3222468033f --- /dev/null +++ b/DNN Platform/Library/Services/Pages/PageExtensions.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Services.Pages +{ + using DotNetNuke.Abstractions.Pages; + using DotNetNuke.UI.Skins.Controls; + + /// + /// Extension methods for that provide convenient overloads with simple parameters. + /// These methods create the appropriate priority objects internally for easier usage. + /// + public static class PageExtensions + { + /// + /// Adds a tag to the header of the page using simple parameters. + /// + /// The page service instance. + /// The HTML tag to add to the head section. + /// The priority of this tag. Defaults to . + public static void AddToHead(this IPageService pageService, string tag, int priority = PagePriority.Default) + { + var priorityItem = new PageTag(tag, priority); + pageService.AddToHead(priorityItem); + } + + /// + /// Adds a meta tag using simple parameters. + /// + /// The page service instance. + /// The name attribute of the meta tag. + /// The content attribute of the meta tag. + /// The priority of this meta tag. Defaults to . + public static void AddMeta(this IPageService pageService, string name, string content, int priority = PagePriority.Default) + { + var priorityMeta = new PageMeta(name, content, priority); + pageService.AddMeta(priorityMeta); + } + + /// + /// Adds a message using simple parameters with default message type. + /// + /// The page service instance. + /// The heading text for the message. + /// The message text content. + /// The priority of this message. Defaults to . + public static void AddMessage(this IPageService pageService, string heading, string message, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, PageMessageType.Info, string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Adds a message using simple parameters with specified message type and icon. + /// + /// The page service instance. + /// The heading text for the message. + /// The message text content. + /// The type/severity of the message. + /// The optional icon source URL for the message. + /// The priority of this message. Defaults to . + public static void AddMessage(this IPageService pageService, string heading, string message, PageMessageType messageType, string iconSrc, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, messageType, iconSrc ?? string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Adds a success message using simple parameters. + /// + /// The page service instance. + /// The heading text for the success message. + /// The success message text content. + /// The priority of this message. Defaults to . + public static void AddSuccessMessage(this IPageService pageService, string heading, string message, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, PageMessageType.Success, string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Adds an error message using simple parameters. + /// + /// The page service instance. + /// The heading text for the error message. + /// The error message text content. + /// The priority of this message. Defaults to . + public static void AddErrorMessage(this IPageService pageService, string heading, string message, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, PageMessageType.Error, string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Adds a warning message using simple parameters. + /// + /// The page service instance. + /// The heading text for the warning message. + /// The warning message text content. + /// The priority of this message. Defaults to . + public static void AddWarningMessage(this IPageService pageService, string heading, string message, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, PageMessageType.Warning, string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Adds an informational message using simple parameters. + /// + /// The page service instance. + /// The heading text for the info message. + /// The info message text content. + /// The priority of this message. Defaults to . + public static void AddInfoMessage(this IPageService pageService, string heading, string message, int priority = PagePriority.Default) + { + var priorityMessage = new PageMessage(heading, message, PageMessageType.Info, string.Empty, priority); + pageService.AddMessage(priorityMessage); + } + + /// + /// Converts a to a . + /// + /// The to convert. + /// The . + public static ModuleMessage.ModuleMessageType ToModuleMessageType(this PageMessageType priorityMessage) + { + return priorityMessage switch + { + PageMessageType.Success => ModuleMessage.ModuleMessageType.GreenSuccess, + PageMessageType.Warning => ModuleMessage.ModuleMessageType.YellowWarning, + PageMessageType.Error => ModuleMessage.ModuleMessageType.RedError, + PageMessageType.Info => ModuleMessage.ModuleMessageType.BlueInfo, + _ => ModuleMessage.ModuleMessageType.BlueInfo, + }; + } + } +} diff --git a/DNN Platform/Library/Services/Pages/PageService.cs b/DNN Platform/Library/Services/Pages/PageService.cs new file mode 100644 index 00000000000..ad83a146218 --- /dev/null +++ b/DNN Platform/Library/Services/Pages/PageService.cs @@ -0,0 +1,272 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Services.Pages +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using DotNetNuke.Abstractions.Pages; + + /// + /// Provides services for managing page-level elements including meta tags, head tags, messages, and SEO properties. + /// This service supports priority-based management where higher priority values take precedence. + /// + /// + /// The PageService manages various page-level elements: + /// - Page metadata (title, description, keywords) with priority-based overrides + /// - Custom head tags for additional HTML head content + /// - Meta tags for SEO and page information + /// - Page messages for user notifications + /// Priority system: Higher priority values (e.g., 200) will override lower priority values (e.g., 100). + /// Default priority is 100 for most operations. + /// + public class PageService : IPageService + { + /// + /// Collection of head tags to be included in the HTML head section, ordered by priority. + /// + private readonly List headTags = new List(); + + /// + /// Collection of meta tags for SEO and page information, ordered by priority. + /// + private readonly List metaTags = new List(); + + /// + /// Collection of page messages for user notifications, ordered by priority. + /// + private readonly List messages = new List(); + + /// + /// The current page title with the highest priority. + /// + private PageTag title; + + /// + /// The current page description with the highest priority. + /// + private PageTag description; + + /// + /// The current page keywords with the highest priority. + /// + private PageTag keywords; + + /// + /// The current page canonical link URL with the highest priority. + /// + private PageTag canonicalLinkUrl; + + /// + /// Adds a message to the page's message collection. + /// + /// The message item to add to the page. Cannot be null. + /// Thrown when is null. + /// + /// Messages are used to display notifications, alerts, or informational content to users. + /// They are ordered by priority when retrieved, with lower priority values appearing first. + /// + public void AddMessage(PageMessage messageItem) + { + if (messageItem == null) + { + throw new ArgumentNullException(nameof(messageItem)); + } + + this.messages.Add(messageItem); + } + + /// + /// Adds a meta tag to the page's meta tag collection. + /// + /// The meta tag item to add to the page. Cannot be null. + /// Thrown when is null. + /// + /// Meta tags provide metadata about the HTML document and are typically used for SEO, + /// social media sharing, and browser behavior. They are rendered in the HTML head section + /// and ordered by priority when retrieved. + /// + public void AddMeta(PageMeta metaItem) + { + if (metaItem == null) + { + throw new ArgumentNullException(nameof(metaItem)); + } + + this.metaTags.Add(metaItem); + } + + /// + /// Adds a custom tag to the page's HTML head section. + /// + /// The tag item to add to the head section. Cannot be null. + /// Thrown when is null. + /// + /// Head tags allow you to include custom HTML elements in the page's head section, + /// such as custom CSS links, JavaScript files, or other HTML elements. + /// Tags are ordered by priority when retrieved, with lower priority values appearing first. + /// + public void AddToHead(PageTag tagItem) + { + if (tagItem == null) + { + throw new ArgumentNullException(nameof(tagItem)); + } + + this.headTags.Add(tagItem); + } + + /// + /// Sets the page description with the specified priority. + /// + /// The description text for the page. + /// The priority level for this description. Higher values take precedence. Default is 100. + /// + /// The page description is typically used in meta tags for SEO purposes and may appear + /// in search engine results. Only the description with the highest priority will be used. + /// If multiple descriptions have the same priority, the last one set will be used. + /// + public void SetDescription(string value, int priority = 100) + { + this.SetHighestPriorityValue(ref this.description, value, priority); + } + + /// + /// Sets the page keywords with the specified priority. + /// + /// The keywords for the page, typically comma-separated. + /// The priority level for these keywords. Higher values take precedence. Default is 100. + /// + /// Page keywords are used for SEO purposes and help categorize the page content. + /// Only the keywords with the highest priority will be used. + /// If multiple keyword sets have the same priority, the last one set will be used. + /// + public void SetKeyWords(string value, int priority = 100) + { + this.SetHighestPriorityValue(ref this.keywords, value, priority); + } + + /// + /// Sets the page title with the specified priority. + /// + /// The title text for the page. + /// The priority level for this title. Higher values take precedence. Default is 100. + /// + /// The page title appears in the browser tab, search engine results, and when sharing on social media. + /// Only the title with the highest priority will be used. + /// If multiple titles have the same priority, the last one set will be used. + /// + public void SetTitle(string value, int priority = 100) + { + this.SetHighestPriorityValue(ref this.title, value, priority); + } + + /// + /// Sets the page canonical link URL with the specified priority. + /// + /// The canonical link URL for the page. + /// The priority level for this canonical link URL. Higher values take precedence. Default is 100. + public void SetCanonicalLinkUrl(string value, int priority = 100) + { + this.SetHighestPriorityValue(ref this.canonicalLinkUrl, value, priority); + } + + /// + /// Gets the current canonical link URL value with highest priority. + /// + /// The canonical link URL value or null if not set. + public string GetCanonicalLinkUrl() + { + return this.canonicalLinkUrl?.Value; + } + + /// + /// Gets the current title value with highest priority. + /// + /// The title value or null if not set. + public string GetTitle() + { + return this.title?.Value; + } + + /// + /// Gets the current description value with highest priority. + /// + /// The description value or null if not set. + public string GetDescription() + { + return this.description?.Value; + } + + /// + /// Gets the current keywords value with highest priority. + /// + /// The keywords value or null if not set. + public string GetKeyWords() + { + return this.keywords?.Value; + } + + /// + /// Gets all head tags ordered by priority (lowest priority first). + /// + /// List of head tags ordered by priority. + public IEnumerable GetHeadTags() + { + return this.headTags.OrderBy(x => x.Priority).ToList(); + } + + /// + /// Gets all meta tags ordered by priority (lowest priority first). + /// + /// List of meta tags ordered by priority. + public IEnumerable GetMetaTags() + { + return this.metaTags.OrderBy(x => x.Priority).ToList(); + } + + /// + /// Gets all messages ordered by priority (lowest priority first). + /// + /// List of messages ordered by priority. + public IEnumerable GetMessages() + { + return this.messages.OrderBy(x => x.Priority).ToList(); + } + + /// + /// Clears all stored page data. Useful for testing or resetting state. + /// + public void Clear() + { + this.title = null; + this.description = null; + this.keywords = null; + this.headTags.Clear(); + this.metaTags.Clear(); + this.messages.Clear(); + } + + /// + /// Sets the highest priority value for a PageTag field. + /// + /// Reference to the current PageTag field to potentially update. + /// The new value to set. + /// The priority of the new value. + /// + /// This method implements the priority-based override system. It only updates the current item + /// if the new priority is higher than the existing priority, or if no item is currently set. + /// This ensures that higher priority values always take precedence. + /// + private void SetHighestPriorityValue(ref PageTag currentItem, string value, int priority) + { + if (currentItem == null || priority > currentItem.Priority) + { + currentItem = new PageTag(value, priority); + } + } + } +} diff --git a/DNN Platform/Library/Startup.cs b/DNN Platform/Library/Startup.cs index 415de11fe49..5cc33c47f8e 100644 --- a/DNN Platform/Library/Startup.cs +++ b/DNN Platform/Library/Startup.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // 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 @@ -10,6 +10,7 @@ namespace DotNetNuke using DotNetNuke.Abstractions.Application; using DotNetNuke.Abstractions.Logging; using DotNetNuke.Abstractions.Modules; + using DotNetNuke.Abstractions.Pages; using DotNetNuke.Abstractions.Portals; using DotNetNuke.Abstractions.Portals.Templates; using DotNetNuke.Abstractions.Prompt; @@ -47,6 +48,7 @@ namespace DotNetNuke using DotNetNuke.Services.Log.EventLog; using DotNetNuke.Services.Mail.OAuth; using DotNetNuke.Services.Mobile; + using DotNetNuke.Services.Pages; using DotNetNuke.Services.Personalization; using DotNetNuke.Services.Search.Controllers; using DotNetNuke.Services.Search.Internals; @@ -90,6 +92,7 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); diff --git a/DNN Platform/Library/UI/Skins/Skin.cs b/DNN Platform/Library/UI/Skins/Skin.cs index 052cb62226b..9bc18a06f00 100644 --- a/DNN Platform/Library/UI/Skins/Skin.cs +++ b/DNN Platform/Library/UI/Skins/Skin.cs @@ -214,7 +214,8 @@ public static void AddModuleMessage(Control control, string heading, string mess /// The Message Heading. /// The Message Text. /// The Icon to display. - public static void AddPageMessage(Page page, string heading, string message, string iconSrc) + [DnnDeprecated(10, 2, 0, "Please use IPageService.AddMessage")] + public static partial void AddPageMessage(Page page, string heading, string message, string iconSrc) { AddPageMessage(page, heading, message, ModuleMessage.ModuleMessageType.GreenSuccess, iconSrc); } @@ -224,7 +225,8 @@ public static void AddPageMessage(Page page, string heading, string message, str /// The Message Heading. /// The Message Text. /// The Icon to display. - public static void AddPageMessage(Skin skin, string heading, string message, string iconSrc) + [DnnDeprecated(10, 2, 0, "Please use IPageService.AddMessage")] + public static partial void AddPageMessage(Skin skin, string heading, string message, string iconSrc) { AddPageMessage(skin, heading, message, ModuleMessage.ModuleMessageType.GreenSuccess, iconSrc); } @@ -234,7 +236,8 @@ public static void AddPageMessage(Skin skin, string heading, string message, str /// The Message Heading. /// The Message Text. /// The type of the message. - public static void AddPageMessage(Skin skin, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType) + [DnnDeprecated(10, 2, 0, "Please use IPageService.AddMessage")] + public static partial void AddPageMessage(Skin skin, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType) { AddPageMessage(skin, heading, message, moduleMessageType, Null.NullString); } @@ -244,7 +247,8 @@ public static void AddPageMessage(Skin skin, string heading, string message, Mod /// The Message Heading. /// The Message Text. /// The type of the message. - public static void AddPageMessage(Page page, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType) + [DnnDeprecated(10, 2, 0, "Please use IPageService.AddMessage")] + public static partial void AddPageMessage(Page page, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType) { AddPageMessage(page, heading, message, moduleMessageType, Null.NullString); } @@ -474,6 +478,26 @@ public void RegisterModuleActionEvent(int moduleId, ActionEventHandler e) this.ActionEventListeners.Add(new ModuleActionEventListener(moduleId, e)); } + /// AddPageMessage adds a Page Message control to the Skin. + /// The control. + /// The Message Heading. + /// The Message Text. + /// The type of the message. + /// The Icon to display. + internal static void AddPageMessage(Control control, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType, string iconSrc) + { + if (!string.IsNullOrEmpty(message)) + { + Control contentPane = FindControlRecursive(control, Globals.glbDefaultPane); + + if (contentPane != null) + { + ModuleMessage moduleMessage = GetModuleMessageControl(heading, message, moduleMessageType, iconSrc); + contentPane.Controls.AddAt(0, moduleMessage); + } + } + } + /// protected override void OnInit(EventArgs e) { @@ -491,7 +515,7 @@ protected override void OnInit(EventArgs e) // Register any error messages on the Skin if (this.Request.QueryString["error"] != null && this.hostSettings.ShowCriticalErrors) { - AddPageMessage(this, Localization.GetString("CriticalError.Error"), " ", ModuleMessage.ModuleMessageType.RedError); + AddPageMessage(this, Localization.GetString("CriticalError.Error"), " ", ModuleMessage.ModuleMessageType.RedError, string.Empty); if (UserController.Instance.GetCurrentUserInfo().IsSuperUser) { @@ -507,7 +531,7 @@ protected override void OnInit(EventArgs e) if (!TabPermissionController.CanAdminPage() && !success) { // only display the warning to non-administrators (administrators will see the errors) - AddPageMessage(this, Localization.GetString("ModuleLoadWarning.Error"), string.Format(Localization.GetString("ModuleLoadWarning.Text"), this.PortalSettings.Email), ModuleMessage.ModuleMessageType.YellowWarning); + AddPageMessage(this, Localization.GetString("ModuleLoadWarning.Error"), string.Format(Localization.GetString("ModuleLoadWarning.Text"), this.PortalSettings.Email), ModuleMessage.ModuleMessageType.YellowWarning, string.Empty); } this.InvokeSkinEvents(SkinEventType.OnSkinInit); @@ -520,7 +544,7 @@ protected override void OnInit(EventArgs e) messageType = (ModuleMessage.ModuleMessageType)Enum.Parse(typeof(ModuleMessage.ModuleMessageType), HttpContextSource.Current.Items[OnInitMessageType].ToString(), true); } - AddPageMessage(this, string.Empty, HttpContextSource.Current.Items[OnInitMessage].ToString(), messageType); + AddPageMessage(this, string.Empty, HttpContextSource.Current.Items[OnInitMessage].ToString(), messageType, string.Empty); this.javaScript.RequestRegistration(CommonJs.DnnPlugins); ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); @@ -604,20 +628,6 @@ private static void AddModuleMessage(Control control, string heading, string mes } } - private static void AddPageMessage(Control control, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType, string iconSrc) - { - if (!string.IsNullOrEmpty(message)) - { - Control contentPane = FindControlRecursive(control, Globals.glbDefaultPane); - - if (contentPane != null) - { - ModuleMessage moduleMessage = GetModuleMessageControl(heading, message, moduleMessageType, iconSrc); - contentPane.Controls.AddAt(0, moduleMessage); - } - } - } - private static Control FindControlRecursive(Control rootControl, string controlId) { if (rootControl.ID == controlId) @@ -837,7 +847,7 @@ private void HandleAccessDenied(bool redirect = false) } else { - AddPageMessage(this, string.Empty, message, ModuleMessage.ModuleMessageType.YellowWarning); + AddPageMessage(this, string.Empty, message, ModuleMessage.ModuleMessageType.YellowWarning, string.Empty); } } @@ -891,7 +901,8 @@ private bool ProcessMasterModules() this, string.Empty, string.Format(Localization.GetString("ContractExpired.Error"), this.PortalSettings.PortalName, Globals.GetMediumDate(this.PortalSettings.ExpiryDate.ToString(CultureInfo.InvariantCulture)), this.PortalSettings.Email), - ModuleMessage.ModuleMessageType.RedError); + ModuleMessage.ModuleMessageType.RedError, + string.Empty); } } else diff --git a/DNN Platform/Website/Default.aspx.cs b/DNN Platform/Website/Default.aspx.cs index 4a6df5cda09..d769db2bdf1 100644 --- a/DNN Platform/Website/Default.aspx.cs +++ b/DNN Platform/Website/Default.aspx.cs @@ -17,6 +17,7 @@ namespace DotNetNuke.Framework using DotNetNuke.Abstractions; using DotNetNuke.Abstractions.Application; using DotNetNuke.Abstractions.Logging; + using DotNetNuke.Abstractions.Pages; using DotNetNuke.Abstractions.Portals; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Portals; @@ -29,6 +30,7 @@ namespace DotNetNuke.Framework using DotNetNuke.Services.FileSystem; using DotNetNuke.Services.Installer.Blocker; using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Pages; using DotNetNuke.Services.Personalization; using DotNetNuke.UI; using DotNetNuke.UI.Internals; @@ -59,11 +61,12 @@ public partial class DefaultPage : CDefault, IClientAPICallbackEventHandler private readonly IHostSettingsService hostSettingsService; private readonly IEventLogger eventLogger; private readonly IPortalSettingsController portalSettingsController; + private readonly IPageService pageService; /// Initializes a new instance of the class. [Obsolete("Deprecated in DotNetNuke 10.0.2. Please use overload with INavigationManager. Scheduled removal in v12.0.0.")] public DefaultPage() - : this(null, null, null, null, null, null, null, null, null) + : this(null, null, null, null, null, null, null, null, null, null) { } @@ -77,7 +80,8 @@ public DefaultPage() /// The event logger. /// The portal controller. /// The portal settings controller. - public DefaultPage(INavigationManager navigationManager, IApplicationInfo appInfo, IApplicationStatusInfo appStatus, IModuleControlPipeline moduleControlPipeline, IHostSettings hostSettings, IHostSettingsService hostSettingsService, IEventLogger eventLogger, IPortalController portalController, IPortalSettingsController portalSettingsController) + /// The page service. + public DefaultPage(INavigationManager navigationManager, IApplicationInfo appInfo, IApplicationStatusInfo appStatus, IModuleControlPipeline moduleControlPipeline, IHostSettings hostSettings, IHostSettingsService hostSettingsService, IEventLogger eventLogger, IPortalController portalController, IPortalSettingsController portalSettingsController, IPageService pageService) : base(portalController, appStatus, hostSettings) { this.NavigationManager = navigationManager ?? Globals.GetCurrentServiceProvider().GetRequiredService(); @@ -88,6 +92,7 @@ public DefaultPage(INavigationManager navigationManager, IApplicationInfo appInf this.hostSettingsService = hostSettingsService ?? Globals.GetCurrentServiceProvider().GetRequiredService(); this.eventLogger = eventLogger ?? Globals.GetCurrentServiceProvider().GetRequiredService(); this.portalSettingsController = portalSettingsController ?? Globals.GetCurrentServiceProvider().GetRequiredService(); + this.pageService = pageService ?? Globals.GetCurrentServiceProvider().GetRequiredService(); } public string CurrentSkinPath => ((PortalSettings)HttpContext.Current.Items["PortalSettings"]).ActiveTab.SkinPath; @@ -228,11 +233,12 @@ protected override void OnInit(EventArgs e) { var heading = Localization.GetString("PageDisabled.Header"); var message = Localization.GetString("PageDisabled.Text"); - UI.Skins.Skin.AddPageMessage( - ctlSkin, + this.pageService.AddMessage(new PageMessage( heading, message, - ModuleMessage.ModuleMessageType.YellowWarning); + PageMessageType.Warning, + string.Empty, + PagePriority.Page)); } else { @@ -337,6 +343,13 @@ protected override void OnPreRender(EventArgs evt) this.metaPanel.Visible = !UrlUtils.InPopUp(); if (!UrlUtils.InPopUp()) { + this.pageService.SetTitle(this.Title, PagePriority.Page); + this.Title = this.pageService.GetTitle(); + this.pageService.SetDescription(this.Description, PagePriority.Page); + this.Description = this.pageService.GetDescription(); + this.pageService.SetKeyWords(this.KeyWords, PagePriority.Page); + this.KeyWords = this.pageService.GetKeyWords(); + this.MetaGenerator.Content = this.Generator; this.MetaGenerator.Visible = !string.IsNullOrEmpty(this.Generator); this.MetaAuthor.Content = this.PortalSettings.PortalName; @@ -352,6 +365,8 @@ protected override void OnPreRender(EventArgs evt) this.Page.Response.AddHeader("X-UA-Compatible", this.PortalSettings.AddCompatibleHttpHeader); } + this.pageService.SetCanonicalLinkUrl(this.CanonicalLinkUrl, PagePriority.Page); + this.CanonicalLinkUrl = this.pageService.GetCanonicalLinkUrl(); if (!string.IsNullOrEmpty(this.CanonicalLinkUrl)) { // Add Canonical using the primary alias @@ -362,6 +377,21 @@ protected override void OnPreRender(EventArgs evt) // Add the HtmlLink to the Head section of the page. this.Page.Header.Controls.Add(canonicalLink); } + + foreach (var item in this.pageService.GetHeadTags()) + { + this.Page.Header.Controls.Add(new LiteralControl(item.Value)); + } + + foreach (var item in this.pageService.GetMetaTags()) + { + this.Page.Header.Controls.Add(new Meta() { Name = item.Name, Content = item.Content }); + } + + foreach (var item in this.pageService.GetMessages()) + { + Skin.AddPageMessage(this, item.Heading, item.Message, item.MessageType.ToModuleMessageType(), item.IconSrc); + } } ///