From 96b395427d455b878cafb89a7f4dec3d5145f687 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 11:36:58 +0100 Subject: [PATCH 001/146] Initial port of MVC-PAGE branch to DNN 10 --- .../BaseCspContributor.cs | 33 + .../ContentSecurityPolicy.cs | 321 ++ .../CspContributor.cs | 81 + .../CspDirectiveNameMapper.cs | 43 + .../CspDirectiveType.cs | 92 + .../CspPolicyExample.cs | 39 + .../CspSource.cs | 165 + .../CspSourceType.cs | 57 + .../CspSourceTypeNameMapper.cs | 36 + .../DocumentCspContributor.cs | 109 + .../DotNetNuke.ContentSecurityPolicy.csproj | 25 + .../IContentSecurityPolicy.cs | 117 + .../ReportingCspContributor.cs | 90 + .../SourceCspContributor.cs | 175 + .../DotNetNuke.ContentSecurityPolicy/dnn.json | 16 + .../DotNetNuke.Web.Client.csproj | 22 + .../MvcClientResourceManager.cs | 583 +++ .../DotNetNuke.Web.Client/packages.config | 4 + .../Containers/SkinHelpers.Content.cs | 152 + .../Containers/SkinHelpers.Title.cs | 37 + .../Controllers/CSPReport.cs | 21 + .../Controllers/CSPReportDetail.cs | 27 + .../Controllers/CspController.cs | 95 + .../Controllers/DnnPageController.cs | 81 + .../Controllers/IMvcController.cs | 12 + .../Controllers/ModuleControllerBase.cs | 378 ++ .../Controllers/ModuleSettingsController.cs | 386 ++ .../ModuleSettingsViewController.cs | 261 ++ .../Controllers/ModuleViewControllerBase.cs | 392 ++ .../DnnMvcPipelineDependencyResolver.cs | 66 + .../DotNetNuke.Web.MvcPipeline.csproj | 44 + .../Exceptions/AccesDeniedException.cs | 32 + .../Exceptions/DisabledPageException.cs | 37 + .../Exceptions/MvcPageException.cs | 39 + .../Exceptions/NotFoundException.cs | 37 + .../Extensions/StartupExtensions.cs | 36 + .../Framework/ContainerModelFactory.cs | 264 ++ .../Framework/IContainerModelFactory.cs | 15 + .../Framework/IPageModelFactory.cs | 14 + .../Framework/IPaneModelFactory.cs | 19 + .../Framework/ISkinModelFactory.cs | 14 + .../Framework/PageModelFactory.cs | 248 ++ .../Framework/PaneModelFactory.cs | 421 +++ .../Framework/SkinModelFactory.cs | 713 ++++ .../DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs | 123 + .../Models/ContainerModel.cs | 139 + .../Models/ControlViewModel.cs | 22 + .../Models/ModuleHostModel.cs | 85 + .../Models/ModuleMessageModel.cs | 19 + .../Models/ModuleModelBase.cs | 14 + .../Models/ModuleSettingsModel.cs | 145 + .../Models/ModuleSettingsMvcViewModel.cs | 144 + .../Models/PageModel.cs | 57 + .../Models/PaneModel.cs | 30 + .../Models/RegisteredStylesheet.cs | 13 + .../Models/SkinModel.cs | 106 + .../Models/TabModel.cs | 16 + .../ModuleControl/ModuleControlBase.cs | 440 +++ .../Modules/ModuleHelpers.DnnLabel.cs | 66 + .../Modules/ModuleHelpers.TextEditor.cs | 342 ++ .../Modules/ModuleHelpers.cs | 24 + .../PermissionTriStateHelper.cs | 145 + .../Routing/DnnMvcPageHandler.cs | 58 + .../Routing/DnnMvcPageRouteHandler.cs | 52 + .../Routing/HttpConfigurationExtensions.cs | 53 + .../Routing/HttpRequestExtensions.cs | 74 + .../Routing/ITabAndModuleInfoProvider.cs | 19 + .../Routing/MvcRoutingManager.cs | 69 + .../StandardTabAndModuleInfoProvider.cs | 171 + .../ModulePermissionsGridController.cs | 378 ++ .../Controllers/PermissionsGridController.cs | 645 ++++ .../Models/ModulePermissionsGridViewModel.cs | 19 + .../Security/Models/PermissionModel.cs | 23 + .../Models/PermissionTriStateModel.cs | 23 + .../Security/Models/PermissionUpdateModel.cs | 28 + .../Models/PermissionsGridViewModel.cs | 25 + .../Security/Models/PermissionsUpdateModel.cs | 18 + .../Security/Models/RoleModel.cs | 17 + .../Security/Models/UserModel.cs | 10 + .../Security/Scripts/PermissionsGrid.js | 267 ++ .../Skins/SkinHelpers.BreadCrumb.cs | 107 + .../Skins/SkinHelpers.ControlPanel.cs | 28 + .../Skins/SkinHelpers.Copyright.cs | 39 + .../Skins/SkinHelpers.CurrentDate.cs | 29 + .../Skins/SkinHelpers.DnnCssExclude.cs | 23 + .../Skins/SkinHelpers.DnnCssInclude.cs | 60 + .../Skins/SkinHelpers.DnnJsExclude.cs | 25 + .../Skins/SkinHelpers.DnnJsInclude.cs | 39 + .../Skins/SkinHelpers.DnnLink.cs | 26 + .../Skins/SkinHelpers.DotNetNuke.cs | 28 + .../Skins/SkinHelpers.Help.cs | 27 + .../Skins/SkinHelpers.HostName.cs | 27 + .../SkinHelpers.JavaScriptLibraryInclude.cs | 34 + .../Skins/SkinHelpers.Language.cs | 218 ++ .../Skins/SkinHelpers.LeftMenu.cs | 20 + .../Skins/SkinHelpers.LinkToFullSite.cs | 29 + .../Skins/SkinHelpers.LinkToMobileSite.cs | 38 + .../Skins/SkinHelpers.LinkToTabletSite.cs | 29 + .../Skins/SkinHelpers.Links.cs | 66 + .../Skins/SkinHelpers.Login.cs | 172 + .../Skins/SkinHelpers.Logo.cs | 156 + .../Skins/SkinHelpers.Meta.cs | 37 + .../Skins/SkinHelpers.ModuleMessage.cs | 45 + .../Skins/SkinHelpers.Pane.cs | 100 + .../Skins/SkinHelpers.Privacy.cs | 50 + .../Skins/SkinHelpers.Search.cs | 394 ++ .../Skins/SkinHelpers.SkinPartial.cs | 30 + .../Skins/SkinHelpers.Styles.cs | 43 + .../Skins/SkinHelpers.Tags.cs | 36 + .../Skins/SkinHelpers.Terms.cs | 50 + .../Skins/SkinHelpers.Text.cs | 51 + .../Skins/SkinHelpers.Toast.cs | 28 + .../Skins/SkinHelpers.TreeViewMenu.cs | 28 + .../Skins/SkinHelpers.User.cs | 326 ++ .../Skins/SkinHelpers.UserAndLogin.cs | 179 + .../Skins/SkinHelpers.cs | 24 + .../Skins/SkinHelpers.jQuery.cs | 39 + .../DotNetNuke.Web.MvcPipeline/Startup.cs | 29 + .../DotNetNuke.Web.MvcPipeline/dnn.json | 16 + .../Library/DotNetNuke.Library.csproj | 4 + .../Entities/Urls/MvcAdvancedUrlRewriter.cs | 3224 +++++++++++++++++ .../IMvcServiceFrameworkInternals.cs | 9 + .../JavaScriptLibraries/MvcJavaScript.cs | 558 +++ .../Framework/MvcServicesFrameworkImpl.cs | 119 + DNN Platform/Library/Mvc/MvcClientAPI.cs | 63 + DNN Platform/Library/Mvc/MvcUtils.cs | 40 + .../Library/Properties/AssemblyInfoMvc.cs | 15 + DNN_Platform.sln | 54 + 128 files changed, 16539 insertions(+) create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/SourceCspContributor.cs create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/dnn.json create mode 100644 DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Title.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReport.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReportDetail.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CspController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/IMvcController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/DnnMvcPipelineDependencyResolver.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/AccesDeniedException.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/DisabledPageException.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/MvcPageException.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/NotFoundException.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Extensions/StartupExtensions.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IContainerModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPageModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPaneModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ISkinModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PaneModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ControlViewModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleHostModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleMessageModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleModelBase.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsMvcViewModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PageModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PaneModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/RegisteredStylesheet.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Models/TabModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/ModuleControl/ModuleControlBase.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageRouteHandler.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpConfigurationExtensions.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpRequestExtensions.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/ITabAndModuleInfoProvider.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/StandardTabAndModuleInfoProvider.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/PermissionsGridController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/ModulePermissionsGridViewModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionTriStateModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionUpdateModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsGridViewModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsUpdateModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/RoleModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/UserModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Scripts/PermissionsGrid.js create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.BreadCrumb.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ControlPanel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Copyright.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.CurrentDate.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssExclude.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsExclude.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnLink.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DotNetNuke.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Help.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.HostName.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.JavaScriptLibraryInclude.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Language.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LeftMenu.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToFullSite.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToMobileSite.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToTabletSite.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Links.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Login.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Logo.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Meta.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ModuleMessage.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Privacy.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Search.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.SkinPartial.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Styles.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Tags.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Terms.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Text.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Toast.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.TreeViewMenu.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.User.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.UserAndLogin.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.jQuery.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/dnn.json create mode 100644 DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs create mode 100644 DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs create mode 100644 DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs create mode 100644 DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs create mode 100644 DNN Platform/Library/Mvc/MvcClientAPI.cs create mode 100644 DNN Platform/Library/Mvc/MvcUtils.cs create mode 100644 DNN Platform/Library/Properties/AssemblyInfoMvc.cs diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs new file mode 100644 index 00000000000..44b12f1738c --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs @@ -0,0 +1,33 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + /// + /// Base class for all CSP directive contributors. + /// + public abstract class BaseCspContributor + { + /// + /// Gets unique identifier for the contributor. + /// + public Guid Id { get; } = Guid.NewGuid(); + + /// + /// Gets or sets type of the CSP directive. + /// + public CspDirectiveType DirectiveType { get; protected set; } + + /// + /// Generates the directive string. + /// + /// The directive string. + public abstract string GenerateDirective(); + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs new file mode 100644 index 00000000000..c5a58af5b03 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs @@ -0,0 +1,321 @@ +// 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.ContentSecurityPolicy +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Manages the entire Content Security Policy. + /// + public class ContentSecurityPolicy : IContentSecurityPolicy + { + private string nonce; + + /// Initializes a new instance of the class. + public ContentSecurityPolicy() + { + } + + /// + /// Gets a cryptographically secure random nonce value for use in CSP policies. + /// + public string Nonce + { + get + { + if (this.nonce == null) + { + var nonceBytes = new byte[32]; + var generator = System.Security.Cryptography.RandomNumberGenerator.Create(); + generator.GetBytes(nonceBytes); + this.nonce = System.Convert.ToBase64String(nonceBytes); + } + + return this.nonce; + } + } + + /// + /// Gets the default source contributor for managing default-src directives. + /// + public SourceCspContributor DefaultSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.DefaultSrc); + } + } + + /// + /// Gets the script source contributor for managing script-src directives. + /// + public SourceCspContributor ScriptSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.ScriptSrc); + } + } + + /// + /// Gets the style source contributor for managing style-src directives. + /// + public SourceCspContributor StyleSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.StyleSrc); + } + } + + /// + /// Gets the image source contributor for managing img-src directives. + /// + public SourceCspContributor ImgSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.ImgSrc); + } + } + + /// + /// Gets the connect source contributor for managing connect-src directives. + /// + public SourceCspContributor ConnectSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.ConnectSrc); + } + } + + /// + /// Gets the font source contributor for managing font-src directives. + /// + public SourceCspContributor FontSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.FontSrc); + } + } + + /// + /// Gets the object source contributor for managing object-src directives. + /// + public SourceCspContributor ObjectSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.ObjectSrc); + } + } + + /// + /// Gets the media source contributor for managing media-src directives. + /// + public SourceCspContributor MediaSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.MediaSrc); + } + } + + /// + /// Gets the frame source contributor for managing frame-src directives. + /// + public SourceCspContributor FrameSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.FrameSrc); + } + } + + /// + /// Gets the base URI source contributor for managing base-uri directives. + /// + public SourceCspContributor BaseUriSource + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.BaseUri); + } + } + + /// + /// Gets collection of CSP contributors. + /// + private List Contributors { get; } = new List(); + + /// + /// Generates the complete Content Security Policy. + /// + /// The complete Content Security Policy. + public string GeneratePolicy() + { + return string.Join( + "; ", + this.Contributors + .Select(c => c.GenerateDirective()) + .Where(d => !string.IsNullOrEmpty(d))); + } + + /// + /// Supprime les sources de script du type spécifié de la politique CSP. + /// + /// Le type de source CSP à supprimer. + public void RemoveScriptSources(CspSourceType cspSourceType) + { + this.RemoveSources(CspDirectiveType.ScriptSrc, cspSourceType); + } + + /// + /// Ajoute des types de plugins autorisés à la politique CSP. + /// + /// Le type de plugin à autoriser. + public void AddPluginTypes(string value) + { + this.AddDocumentDirective(CspDirectiveType.PluginTypes, value); + } + + /// + /// Ajoute une directive sandbox à la politique CSP. + /// + /// La valeur de la directive sandbox. + public void AddSandboxDirective(string value) + { + this.SetDocumentDirective(CspDirectiveType.SandboxDirective, value); + } + + /// + /// Ajoute une directive form-action à la politique CSP. + /// + /// Le type de source CSP à ajouter. + /// La valeur associée à la source. + public void AddFormAction(CspSourceType sourceType, string value) + { + this.AddSource(CspDirectiveType.FormAction, sourceType, value); + } + + /// + /// Ajoute une directive frame-ancestors à la politique CSP. + /// + /// Le type de source CSP à ajouter. + /// La valeur associée à la source. + public void AddFrameAncestors(CspSourceType sourceType, string value) + { + this.AddSource(CspDirectiveType.FrameAncestors, sourceType, value); + } + + /// + /// Ajoute une URI de rapport à la politique CSP. + /// + /// L'URI où les rapports de violation seront envoyés. + public void AddReportUri(string value) + { + this.AddReportingDirective(CspDirectiveType.ReportUri, value); + } + + /// + /// Ajoute un endpoint de rapport à la politique CSP. + /// + /// L'endpoint où les rapports seront envoyés. + public void AddReportTo(string value) + { + this.AddReportingDirective(CspDirectiveType.ReportTo, value); + } + + private SourceCspContributor GetOrCreateDirective(CspDirectiveType directiveType) + { + var directive = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + if (directive == null) + { + directive = new SourceCspContributor(directiveType); + this.AddContributor(directive); + } + + return directive; + } + + /// + /// Adds a contributor to the policy. + /// + private void AddContributor(BaseCspContributor contributor) + { + // Remove any existing contributor of the same directive type + this.Contributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType); + this.Contributors.Add(contributor); + } + + private void AddSource(CspDirectiveType directiveType, CspSourceType sourceType, string value = null) + { + var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + if (contributor == null) + { + contributor = new SourceCspContributor(directiveType); + this.AddContributor(contributor); + } + + if (sourceType == CspSourceType.Nonce && string.IsNullOrEmpty(value)) + { + value = this.Nonce; + } + + contributor.AddSource(new CspSource(sourceType, value)); + } + + private void RemoveSources(CspDirectiveType directiveType, CspSourceType sourceType) + { + var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + if (contributor == null) + { + contributor = new SourceCspContributor(directiveType); + this.AddContributor(contributor); + } + + contributor.RemoveSources(sourceType); + } + + private void SetDocumentDirective(CspDirectiveType directiveType, string value) + { + var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; + if (contributor == null) + { + contributor = new DocumentCspContributor(directiveType, value); + this.AddContributor(contributor); + } + + contributor.SetDirectiveValue(value); + } + + private void AddDocumentDirective(CspDirectiveType directiveType, string value) + { + var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; + if (contributor == null) + { + contributor = new DocumentCspContributor(directiveType, value); + this.AddContributor(contributor); + } + + contributor.SetDirectiveValue(value); + } + + private void AddReportingDirective(CspDirectiveType directiveType, string value) + { + var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as ReportingCspContributor; + if (contributor == null) + { + contributor = new ReportingCspContributor(directiveType); + this.AddContributor(contributor); + } + + contributor.AddReportingEndpoint(value); + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs new file mode 100644 index 00000000000..27279e58df3 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs @@ -0,0 +1,81 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + /// + /// Manages Content Security Policy contributors for a specific directive. + /// + public class CspContributor + { + /// + /// Initializes a new instance of the class. + /// + /// The directive to create the contributor for. + public CspContributor(string directive) + { + this.Directive = directive ?? throw new ArgumentNullException(nameof(directive)); + } + + /// + /// Gets name of the directive (e.g., 'script-src', 'style-src'). + /// + public string Directive { get; } + + /// + /// Gets collection of sources for this directive. + /// + private List Sources { get; } = new List(); + + /// + /// Adds a source to the directive. + /// + /// The source to add. + public void AddSource(CspSource source) + { + if (!this.Sources.Any(s => s.Type == source.Type && s.Value == source.Value)) + { + this.Sources.Add(source); + } + } + + /// + /// Removes a source from the directive. + /// + /// The source to remove. + public void RemoveSource(CspSource source) + { + this.Sources.RemoveAll(s => s.Type == source.Type && s.Value == source.Value); + } + + /// + /// Generates the complete directive string. + /// + /// The directive string. + public string GenerateDirective() + { + if (!this.Sources.Any()) + { + return string.Empty; + } + + return $"{this.Directive} {string.Join(" ", this.Sources.Select(s => s.ToString()))}"; + } + + /// + /// Gets all sources of a specific type. + /// + /// The type of sources to get. + /// The sources of the specified type. + public IEnumerable GetSourcesByType(CspSourceType type) + { + return this.Sources.Where(s => s.Type == type); + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs new file mode 100644 index 00000000000..1eb64598195 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs @@ -0,0 +1,43 @@ +// 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.ContentSecurityPolicy +{ + using System; + + /// + /// Utility class for converting directive types to their string representations. + /// + public static class CspDirectiveNameMapper + { + /// + /// Gets the directive name string. + /// + /// The directive type to get the name for. + /// The directive name string. + public static string GetDirectiveName(CspDirectiveType directiveType) + { + return directiveType switch + { + CspDirectiveType.DefaultSrc => "default-src", + CspDirectiveType.ScriptSrc => "script-src", + CspDirectiveType.StyleSrc => "style-src", + CspDirectiveType.ImgSrc => "img-src", + CspDirectiveType.ConnectSrc => "connect-src", + CspDirectiveType.FontSrc => "font-src", + CspDirectiveType.ObjectSrc => "object-src", + CspDirectiveType.MediaSrc => "media-src", + CspDirectiveType.FrameSrc => "frame-src", + CspDirectiveType.BaseUri => "base-uri", + CspDirectiveType.PluginTypes => "plugin-types", + CspDirectiveType.SandboxDirective => "sandbox", + CspDirectiveType.FormAction => "form-action", + CspDirectiveType.FrameAncestors => "frame-ancestors", + CspDirectiveType.ReportUri => "report-uri", + CspDirectiveType.ReportTo => "report-to", + _ => throw new ArgumentException("Unknown directive type") + }; + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs new file mode 100644 index 00000000000..b314adc1650 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs @@ -0,0 +1,92 @@ +// 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.ContentSecurityPolicy +{ + /// + /// Represents different types of Content Security Policy directives. + /// + public enum CspDirectiveType + { + /// + /// Directive qui définit la politique par défaut pour les types de ressources non spécifiés. + /// + DefaultSrc, + + /// + /// Directive qui contrôle les sources de scripts autorisées. + /// + ScriptSrc, + + /// + /// Directive qui contrôle les sources de styles autorisées. + /// + StyleSrc, + + /// + /// Directive qui contrôle les sources d'images autorisées. + /// + ImgSrc, + + /// + /// Directive qui contrôle les destinations de connexion autorisées. + /// + ConnectSrc, + + /// + /// Directive qui contrôle les sources de polices autorisées. + /// + FontSrc, + + /// + /// Directive qui contrôle les sources d'objets autorisées. + /// + ObjectSrc, + + /// + /// Directive qui contrôle les sources de médias autorisées. + /// + MediaSrc, + + /// + /// Directive qui contrôle les sources de frames autorisées. + /// + FrameSrc, + + /// + /// Directive qui restreint les URLs pouvant être utilisées dans la base URI du document. + /// + BaseUri, + + /// + /// Directive qui restreint les types de plugins pouvant être chargés. + /// + PluginTypes, + + /// + /// Directive qui active un bac à sable pour la ressource demandée. + /// + SandboxDirective, + + /// + /// Directive qui restreint les URLs pouvant être utilisées comme cible de formulaire. + /// + FormAction, + + /// + /// Directive qui spécifie les parents autorisés à intégrer une page dans un frame. + /// + FrameAncestors, + + /// + /// Directive qui spécifie l'URI où envoyer les rapports de violation. + /// + ReportUri, + + /// + /// Directive qui spécifie où envoyer les rapports de violation au format JSON. + /// + ReportTo, + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs new file mode 100644 index 00000000000..dbf817712c4 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs @@ -0,0 +1,39 @@ +// 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.ContentSecurityPolicy +{ + using System; + + /// + /// Démontre l'utilisation de la Content Security Policy en configurant différentes directives. + /// + public class CspPolicyExample + { + /// + /// Démontre l'utilisation de la Content Security Policy en configurant différentes directives. + /// + public static void Example() + { + // Create a Content Security Policy + var csp = new ContentSecurityPolicy(); + + // Add a source-based contributor for script sources + csp.ScriptSource + .AddSelf() + .AddHost("https://trusted-cdn.com"); + + // Add a document-based contributor for sandbox + csp.AddSandboxDirective("allow-scripts allow-same-origin"); + + // Add a reporting contributor + csp.AddReportUri("https://example.com/csp-report"); + + // Generate the complete policy + string policy = csp.GeneratePolicy(); + + Console.WriteLine(policy); + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs new file mode 100644 index 00000000000..3de637d965e --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs @@ -0,0 +1,165 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Linq; + using System.Text.RegularExpressions; + + /// + /// Represents a single source in a Content Security Policy. + /// + public class CspSource + { + /// + /// Initializes a new instance of the class. + /// + /// Type of the source. + /// Value of the source. + public CspSource(CspSourceType type, string value = null) + { + this.Type = type; + this.Value = this.ValidateSource(type, value); + } + + /// + /// Gets type of the CSP source. + /// + public CspSourceType Type { get; } + + /// + /// Gets the actual source value. + /// + public string Value { get; } + + /// + /// Returns the string representation of the source. + /// + /// The string representation of the source. + public override string ToString() => this.Value ?? CspSourceTypeNameMapper.GetSourceTypeName(this.Type); + + /// + /// Validates the source based on its type. + /// + private string ValidateSource(CspSourceType type, string value) + { + switch (type) + { + case CspSourceType.Host: + return this.ValidateHostSource(value); + case CspSourceType.Scheme: + return this.ValidateSchemeSource(value); + case CspSourceType.Nonce: + return this.ValidateNonceSource(value); + case CspSourceType.Hash: + return this.ValidateHashSource(value); + case CspSourceType.Self: + return "'self'"; + case CspSourceType.Inline: + case CspSourceType.Eval: + return "'unsafe-" + type.ToString().ToLowerInvariant() + "'"; + case CspSourceType.None: + return "'none'"; + case CspSourceType.StrictDynamic: + return "'strict-dynamic'"; + default: + throw new ArgumentException("Invalid source type"); + } + } + + /// + /// Validates host source (domain or IP). + /// + private string ValidateHostSource(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Host source cannot be empty"); + } + + // Basic domain validation + var domainRegex = new Regex(@"^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$"); + if (!domainRegex.IsMatch(value)) + { + throw new ArgumentException($"Invalid host source: {value}"); + } + + return value.StartsWith("http") ? value : $"https://{value}"; + } + + /// + /// Validates scheme source (protocol). + /// + private string ValidateSchemeSource(string value) + { + string[] validSchemes = { "http:", "https:", "data:", "blob:", "filesystem:" }; + if (!validSchemes.Contains(value)) + { + throw new ArgumentException($"Invalid scheme: {value}"); + } + + return value; + } + + /// + /// Validates nonce source. + /// + private string ValidateNonceSource(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Nonce cannot be empty"); + } + + // Basic nonce validation (base64 encoded) + if (!this.IsBase64String(value)) + { + throw new ArgumentException("Invalid nonce format"); + } + + return $"'nonce-{value}'"; + } + + /// + /// Validates hash source. + /// + private string ValidateHashSource(string value) + { + string[] hashPrefixes = { "sha256-", "sha384-", "sha512-" }; + + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Hash cannot be empty"); + } + + // Check if the value starts with a valid hash prefix and has a base64 encoded value + bool isValidHash = hashPrefixes.Any(prefix => + value.StartsWith(prefix) && this.IsBase64String(value.Substring(prefix.Length))); + + if (!isValidHash) + { + throw new ArgumentException($"Invalid hash format: {value}"); + } + + return $"'{value}'"; + } + + /// + /// Checks if a string is a valid Base64 string. + /// + private bool IsBase64String(string value) + { + try + { + Convert.FromBase64String(value); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs new file mode 100644 index 00000000000..496ab88dbb0 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs @@ -0,0 +1,57 @@ +// 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.ContentSecurityPolicy +{ + /// + /// Represents different types of Content Security Policy source types. + /// + public enum CspSourceType + { + /// + /// Permet de spécifier des domaines spécifiques comme source. + /// + Host, + + /// + /// Permet de spécifier des protocoles (ex: https:, data:) comme source. + /// + Scheme, + + /// + /// Autorise les ressources de la même origine ('self'). + /// + Self, + + /// + /// Autorise l'utilisation de code inline ('unsafe-inline'). + /// + Inline, + + /// + /// Autorise l'utilisation de eval() ('unsafe-eval'). + /// + Eval, + + /// + /// Utilise un nonce cryptographique pour valider les ressources. + /// + Nonce, + + /// + /// Utilise un hash cryptographique pour valider les ressources. + /// + Hash, + + /// + /// N'autorise aucune source ('none'). + /// + None, + + /// + /// Active le mode strict-dynamic pour le chargement des scripts. + /// + StrictDynamic, + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs new file mode 100644 index 00000000000..8e6750d7602 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs @@ -0,0 +1,36 @@ +// 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.ContentSecurityPolicy +{ + using System; + + /// + /// Utility class for converting source types to their string representations. + /// + public static class CspSourceTypeNameMapper + { + /// + /// Gets the source type name string. + /// + /// The source type to get the name for. + /// The source type name string. + public static string GetSourceTypeName(CspSourceType sourceType) + { + return sourceType switch + { + CspSourceType.Host => "host", + CspSourceType.Scheme => "scheme", + CspSourceType.Self => "'self'", + CspSourceType.Inline => "'unsafe-inline'", + CspSourceType.Eval => "'unsafe-eval'", + CspSourceType.Nonce => "nonce", + CspSourceType.Hash => "hash", + CspSourceType.None => "none", + CspSourceType.StrictDynamic => "strict-dynamic", + _ => throw new ArgumentException("Unknown source type") + }; + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs new file mode 100644 index 00000000000..4db01cd407f --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.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.ContentSecurityPolicy +{ + using System; + using System.Linq; + + /// + /// Contributor for document-level directives. + /// + public class DocumentCspContributor : BaseCspContributor + { + /// + /// Initializes a new instance of the class. + /// + /// The directive type to create the contributor for. + /// The value of the directive. + public DocumentCspContributor(CspDirectiveType directiveType, string value) + { + this.DirectiveType = directiveType; + this.SetDirectiveValue(value); + } + + /// + /// Gets value of the document directive. + /// + public string DirectiveValue { get; private set; } + + /// + /// Sets the directive value with validation. + /// + /// The value to set for the directive. + public void SetDirectiveValue(string value) + { + this.ValidateDirectiveValue(this.DirectiveType, value); + this.DirectiveValue = value; + } + + /// + /// Generates the directive string. + /// + /// The directive string. + public override string GenerateDirective() + { + if (string.IsNullOrWhiteSpace(this.DirectiveValue)) + { + return string.Empty; + } + + return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)} {this.DirectiveValue}"; + } + + /// + /// Validates directive value based on directive type. + /// + private void ValidateDirectiveValue(CspDirectiveType type, string value) + { + switch (type) + { + case CspDirectiveType.PluginTypes: + this.ValidatePluginTypes(value); + break; + case CspDirectiveType.SandboxDirective: + this.ValidateSandboxDirective(value); + break; + + // Add more specific validations as needed + } + } + + /// + /// Validates plugin types. + /// + private void ValidatePluginTypes(string value) + { + string[] validPluginTypes = { "application/pdf", "image/svg+xml" }; + var types = value.Split(' '); + + if (types.Any(t => !validPluginTypes.Contains(t))) + { + throw new ArgumentException("Invalid plugin type"); + } + } + + /// + /// Validates sandbox directive values. + /// + private void ValidateSandboxDirective(string value) + { + string[] validSandboxValues = + { + "allow-forms", + "allow-scripts", + "allow-same-origin", + "allow-top-navigation", + "allow-popups", + }; + + var values = value.Split(' '); + + if (values.Any(v => !validSandboxValues.Contains(v))) + { + throw new ArgumentException("Invalid sandbox directive value"); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj new file mode 100644 index 00000000000..7aff6a81d10 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj @@ -0,0 +1,25 @@ + + + DotNetNuke.ContentSecurityPolicy + netstandard2.0 + true + latest + bin + false + false + Sacha Trauwaen + Dnn + ContentSecurityPolicy + 2025 + ContentSecurityPolicy + 0.0.1.0 + 0.0.1.0 + DNN MVC-pipeline project + en-US + + Library + + + + + diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs new file mode 100644 index 00000000000..aff12349503 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs @@ -0,0 +1,117 @@ +// 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.ContentSecurityPolicy +{ + /// + /// Interface définissant les opérations de gestion de la Content Security Policy. + /// + public interface IContentSecurityPolicy + { + /// + /// Gets a cryptographically secure nonce value for the CSP policy. + /// + string Nonce { get; } + + /// + /// Gets the default source contributor. + /// + SourceCspContributor DefaultSource { get; } + + /// + /// Gets the script source contributor. + /// + SourceCspContributor ScriptSource { get; } + + /// + /// Gets the style source contributor. + /// + SourceCspContributor StyleSource { get; } + + /// + /// Gets the image source contributor. + /// + SourceCspContributor ImgSource { get; } + + /// + /// Gets the connect source contributor. + /// + SourceCspContributor ConnectSource { get; } + + /// + /// Gets the font source contributor. + /// + SourceCspContributor FontSource { get; } + + /// + /// Gets the object source contributor. + /// + SourceCspContributor ObjectSource { get; } + + /// + /// Gets the media source contributor. + /// + SourceCspContributor MediaSource { get; } + + /// + /// Gets the frame source contributor. + /// + SourceCspContributor FrameSource { get; } + + /// + /// Gets the base URI source contributor. + /// + SourceCspContributor BaseUriSource { get; } + + /// + /// Supprimer une source de script à la politique. + /// + /// Le type de source CSP à supprimer. + void RemoveScriptSources(CspSourceType cspSourceType); + + /// + /// Ajoute des types de plugins à la politique. + /// + /// Le type de plugin à autoriser. + void AddPluginTypes(string value); + + /// + /// Ajoute une directive sandbox à la politique. + /// + /// Les options de la directive sandbox. + void AddSandboxDirective(string value); + + /// + /// Ajoute une action de formulaire à la politique. + /// + /// Le type de source CSP à ajouter. + /// L'URL autorisée pour la soumission du formulaire. + void AddFormAction(CspSourceType sourceType, string value); + + /// + /// Ajoute des ancêtres de frame à la politique. + /// + /// Le type de source CSP à ajouter. + /// L'URL autorisée comme ancêtre de frame. + void AddFrameAncestors(CspSourceType sourceType, string value); + + /// + /// Ajoute une URI de rapport à la politique. + /// + /// L'URI où envoyer les rapports de violation. + void AddReportUri(string value); + + /// + /// Ajoute une destination de rapport à la politique. + /// + /// L'endpoint où envoyer les rapports. + void AddReportTo(string value); + + /// + /// Génère la politique de sécurité complète. + /// + /// La politique de sécurité complète sous forme de chaîne. + string GeneratePolicy(); + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs new file mode 100644 index 00000000000..1a6eabaeefc --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs @@ -0,0 +1,90 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + /// + /// Contributor for reporting directives. + /// + public class ReportingCspContributor : BaseCspContributor + { + /// + /// Initializes a new instance of the class. + /// + /// Le type de directive de rapport (ReportUri ou ReportTo). + public ReportingCspContributor(CspDirectiveType directiveType) + { + if (directiveType != CspDirectiveType.ReportUri && directiveType != CspDirectiveType.ReportTo) + { + throw new ArgumentException("Invalid reporting directive type"); + } + + this.DirectiveType = directiveType; + } + + /// + /// Gets collection of reporting endpoints. + /// + private List ReportingEndpoints { get; } = new List(); + + /// + /// Adds a reporting endpoint. + /// + /// L'URL de l'endpoint où envoyer les rapports. + public void AddReportingEndpoint(string endpoint) + { + this.ValidateReportingEndpoint(endpoint); + if (!this.ReportingEndpoints.Contains(endpoint)) + { + this.ReportingEndpoints.Add(endpoint); + } + } + + /// + /// Removes a reporting endpoint. + /// + /// The endpoint to remove. + public void RemoveReportingEndpoint(string endpoint) + { + this.ReportingEndpoints.Remove(endpoint); + } + + /// + /// Generates the directive string. + /// + /// The directive string. + public override string GenerateDirective() + { + if (!this.ReportingEndpoints.Any()) + { + return string.Empty; + } + + return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)} {string.Join(" ", this.ReportingEndpoints)}"; + } + + /// + /// Validates reporting endpoint. + /// + private void ValidateReportingEndpoint(string endpoint) + { + if (string.IsNullOrWhiteSpace(endpoint)) + { + throw new ArgumentException("Reporting endpoint cannot be empty"); + } + + // URL validation regex + var urlRegex = new Regex(@"^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$"); + if (!urlRegex.IsMatch(endpoint)) + { + throw new ArgumentException($"Invalid reporting endpoint: {endpoint}"); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/SourceCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/SourceCspContributor.cs new file mode 100644 index 00000000000..b18f84e8028 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/SourceCspContributor.cs @@ -0,0 +1,175 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + + /// + /// Contributor for fetch directives (sources-based directives). + /// + public class SourceCspContributor : BaseCspContributor + { + /// + /// Initializes a new instance of the class. + /// + /// The directive type to create the contributor for. + public SourceCspContributor(CspDirectiveType directiveType) + { + this.DirectiveType = directiveType; + } + + /// + /// Gets or sets a value indicating whether the inline source is used for backward compatibility. + /// + public bool InlineForBackwardCompatibility { get; set; } + + /// + /// Gets collection of allowed sources. + /// + private List Sources { get; } = new List(); + + /// + /// Adds a source with inline type to the contributor. + /// + /// The current instance for method chaining. + public SourceCspContributor AddInline() + { + return this.AddSource(new CspSource(CspSourceType.Inline)); + } + + /// + /// Ajoute une source 'self' qui autorise les ressources de la même origine. + /// + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddSelf() + { + return this.AddSource(new CspSource(CspSourceType.Self)); + } + + /// + /// Ajoute une source 'unsafe-eval' qui autorise l'utilisation de eval(). + /// + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddSourceEval() + { + return this.AddSource(new CspSource(CspSourceType.Eval)); + } + + /// + /// Ajoute un hôte spécifique comme source autorisée. + /// + /// L'hôte à autoriser (ex: example.com). + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddHost(string host) + { + return this.AddSource(new CspSource(CspSourceType.Host, host)); + } + + /// + /// Ajoute un schéma comme source autorisée. + /// + /// Le schéma à autoriser (ex: https:, data:). + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddScheme(string scheme) + { + return this.AddSource(new CspSource(CspSourceType.Scheme, scheme)); + } + + /// + /// Ajoute un nonce cryptographique comme source autorisée. + /// + /// La valeur du nonce à utiliser. + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddNonce(string nonce) + { + return this.AddSource(new CspSource(CspSourceType.Nonce, nonce)); + } + + /// + /// Ajoute un hash cryptographique comme source autorisée. + /// + /// La valeur du hash à utiliser. + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddHash(string hash) + { + return this.AddSource(new CspSource(CspSourceType.Hash, hash)); + } + + /// + /// Ajoute une source 'none' qui bloque toutes les sources. + /// + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddNone() + { + return this.AddSource(new CspSource(CspSourceType.None)); + } + + /// + /// Ajoute une source 'strict-dynamic' qui active le chargement dynamique strict des scripts. + /// + /// L'instance courante pour chaîner les méthodes. + public SourceCspContributor AddStrictDynamic() + { + return this.AddSource(new CspSource(CspSourceType.StrictDynamic)); + } + + /// + /// Adds a source to the contributor. + /// + /// The source to add. + /// The current instance for method chaining. + public SourceCspContributor AddSource(CspSource source) + { + if (!this.Sources.Any(s => s.Type == source.Type && s.Value == source.Value)) + { + this.Sources.Add(source); + } + + return this; + } + + /// + /// Removes a source from the contributor. + /// + /// The type of the source to remove. + public void RemoveSources(CspSourceType sourceType) + { + this.Sources.RemoveAll(s => s.Type == sourceType); + } + + /// + /// Generates the directive string. + /// + /// The directive string. + public override string GenerateDirective() + { + if (!this.Sources.Any()) + { + return string.Empty; + } + + if (this.Sources.Any(s => s.Type == CspSourceType.Inline) && !this.InlineForBackwardCompatibility) + { + this.RemoveSources(CspSourceType.Nonce); + this.RemoveSources(CspSourceType.StrictDynamic); + } + + return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)} {string.Join(" ", this.Sources.Select(s => s.ToString()))}"; + } + + /// + /// Gets sources by type. + /// + /// The type of sources to get. + /// The sources of the specified type. + public IEnumerable GetSourcesByType(CspSourceType type) + { + return this.Sources.Where(s => s.Type == type); + } + } +} diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/dnn.json b/DNN Platform/DotNetNuke.ContentSecurityPolicy/dnn.json new file mode 100644 index 00000000000..c4d47cd6957 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/dnn.json @@ -0,0 +1,16 @@ +{ + "projectType": "library", + "name": "Dnn_DotNetNukeContentSecurityPolicy", + "friendlyName": "Dnn DotNetNukeContentSecurityPolicy", + "description": "Dnn DotNetNukeContentSecurityPolicy Library", + "packageName": "Dnn_DotNetNukeContentSecurityPolicy", + "folder": "Dnn/DotNetNukeContentSecurityPolicy", + "library": {}, + "pathsAndFiles": { + "pathToAssemblies": "./bin", + "pathToScripts": "./Server/SqlScripts", + "assemblies": ["DotNetNuke.ContentSecurityPolicy.dll"], + "releaseFiles": [], + "zipName": "Dnn.DotNetNukeContentSecurityPolicy" + } +} diff --git a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj index 92c34a55883..4b244bb11e0 100644 --- a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj +++ b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj @@ -63,6 +63,9 @@ ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + @@ -75,6 +78,24 @@ + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.Helpers.dll + + + ..\..\packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + + + ..\..\packages\Microsoft.AspNet.Razor.3.3.0\lib\net45\System.Web.Razor.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.Razor.dll + @@ -96,6 +117,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs new file mode 100644 index 00000000000..5da3c5d726c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs @@ -0,0 +1,583 @@ +// 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 + +// ReSharper disable once CheckNamespace +namespace DotNetNuke.Web.Client.ClientResourceManagement +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Threading; + using System.Web; + using System.Web.Hosting; + using System.Web.Mvc; + using System.Xml; + + using ClientDependency.Core; + using ClientDependency.Core.CompositeFiles.Providers; + using ClientDependency.Core.Config; + using ClientDependency.Core.Mvc; + using DotNetNuke.Instrumentation; + using DotNetNuke.Internal.SourceGenerators; + + /// Provides the ability to request that client resources (JavaScript and CSS) be loaded on the client browser. + public partial class MvcClientResourceManager + { + /// The default css provider. + internal const string DefaultCssProvider = "DnnPageHeaderProvider"; + + /// The default javascript provider. + internal const string DefaultJsProvider = "DnnBodyProvider"; + + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(ClientResourceManager)); + private static readonly Dictionary FileExistsCache = new Dictionary(); + private static readonly ReaderWriterLockSlim LockFileExistsCache = new ReaderWriterLockSlim(); + + /// Adds the necessary configuration to website root web.config to use the Client Dependency component. + public static void AddConfiguration() + { + var configPath = HostingEnvironment.MapPath("~/web.config"); + if (string.IsNullOrEmpty(configPath)) + { + return; + } + + var xmlDoc = new XmlDocument { XmlResolver = null }; + xmlDoc.Load(configPath); + XmlDocumentFragment xmlFrag; + + // Config Sections + var sectionsConfig = xmlDoc.DocumentElement?.SelectSingleNode("configSections"); + if (sectionsConfig != null) + { + var clientDependencySectionConfig = sectionsConfig.SelectSingleNode("section[@name='clientDependency']"); + if (clientDependencySectionConfig == null) + { + xmlFrag = xmlDoc.CreateDocumentFragment(); + xmlFrag.InnerXml = "
"; + xmlDoc.DocumentElement.SelectSingleNode("configSections")?.AppendChild(xmlFrag); + } + } + + // Module Config + var systemWebServerModulesConfig = xmlDoc.DocumentElement?.SelectSingleNode("system.webServer/modules"); + if (systemWebServerModulesConfig != null) + { + var moduleConfig = systemWebServerModulesConfig.SelectSingleNode("add[@name=\"ClientDependencyModule\"]"); + if (moduleConfig == null) + { + xmlFrag = xmlDoc.CreateDocumentFragment(); + xmlFrag.InnerXml = ""; + xmlDoc.DocumentElement.SelectSingleNode("system.webServer/modules")?.AppendChild(xmlFrag); + } + } + + // Handler Config + var systemWebServerHandlersConfig = xmlDoc.DocumentElement?.SelectSingleNode("system.webServer/handlers"); + if (systemWebServerHandlersConfig != null) + { + var handlerConfig = systemWebServerHandlersConfig.SelectSingleNode("add[@name=\"ClientDependencyHandler\"]"); + if (handlerConfig == null) + { + xmlFrag = xmlDoc.CreateDocumentFragment(); + xmlFrag.InnerXml = ""; + xmlDoc.DocumentElement.SelectSingleNode("system.webServer/handlers")?.AppendChild(xmlFrag); + } + } + + // ClientDependency Config + var clientDependencyConfig = xmlDoc.DocumentElement?.SelectSingleNode("clientDependency"); + if (clientDependencyConfig == null) + { + xmlFrag = xmlDoc.CreateDocumentFragment(); + xmlFrag.InnerXml = @" + + + + + + + + + + + + + + + + + "; + + xmlDoc.DocumentElement?.AppendChild(xmlFrag); + } + + // Save Config + xmlDoc.Save(configPath); + } + + /// Checks if ClientDependency is installed. + /// A value indicating whether the ClientDependency provider is installed. + public static bool IsInstalled() + { + var configPath = HostingEnvironment.MapPath("~/web.config"); + if (string.IsNullOrEmpty(configPath)) + { + return false; + } + + var xmlDoc = new XmlDocument { XmlResolver = null }; + xmlDoc.Load(configPath); + + return xmlDoc.DocumentElement?.SelectSingleNode("configSections")?.SelectSingleNode("section[@name='clientDependency']") != null; + } + + /// Registers a stylesheet that has an admin level priority. + /// The page on which to register the style. + /// The path to the CSS stylesheet. + public static void RegisterAdminStylesheet(ControllerContext page, string filePath) + { + RegisterStyleSheet(page, filePath, FileOrder.Css.AdminCss); + } + + /// Registers the default.css stylesheet. + /// The page on which to register the style. + /// The path to the CSS stylesheet. + public static void RegisterDefaultStylesheet(ControllerContext page, string filePath) + { + RegisterStyleSheet(page, filePath, (int)FileOrder.Css.DefaultCss, DefaultCssProvider, "dnndefault", "7.0.0"); + } + + /// Registers a stylesheet for a specific feature. + /// The page on which to register the style. + /// The path to the CSS stylesheet. + public static void RegisterFeatureStylesheet(ControllerContext page, string filePath) + { + RegisterStyleSheet(page, filePath, FileOrder.Css.FeatureCss); + } + + /// Registers a stylesheet specific for Internet Explorer. + /// The page on which to register the style. + /// The path to the CSS stylesheet. + public static void RegisterIEStylesheet(ControllerContext page, string filePath) + { + var browser = HttpContext.Current.Request.Browser; + if (browser.Browser == "Internet Explorer" || browser.Browser == "IE") + { + RegisterStyleSheet(page, filePath, FileOrder.Css.IeCss); + } + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + public static void RegisterScript(ControllerContext page, string filePath) + { + RegisterScript(page, filePath, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript(ControllerContext page, string filePath, IDictionary htmlAttributes) + { + RegisterScript(page, filePath, FileOrder.Js.DefaultPriority, htmlAttributes); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + public static void RegisterScript(ControllerContext page, string filePath, int priority) + { + RegisterScript(page, filePath, priority, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript(ControllerContext page, string filePath, int priority, IDictionary htmlAttributes) + { + RegisterScript(page, filePath, priority, DefaultJsProvider, htmlAttributes); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + public static void RegisterScript(ControllerContext page, string filePath, FileOrder.Js priority) + { + RegisterScript(page, filePath, priority, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript(ControllerContext page, string filePath, FileOrder.Js priority, IDictionary htmlAttributes) + { + RegisterScript(page, filePath, (int)priority, DefaultJsProvider, htmlAttributes); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + public static void RegisterScript(ControllerContext page, string filePath, FileOrder.Js priority, string provider) + { + RegisterScript(page, filePath, priority, provider, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + /// /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript(ControllerContext page, string filePath, FileOrder.Js priority, string provider, IDictionary htmlAttributes) + { + RegisterScript(page, filePath, (int)priority, provider, htmlAttributes); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + public static void RegisterScript(ControllerContext page, string filePath, int priority, string provider) + { + RegisterScript(page, filePath, priority, provider, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript(ControllerContext page, string filePath, int priority, string provider, IDictionary htmlAttributes) + { + RegisterScript(page, filePath, priority, provider, string.Empty, string.Empty, htmlAttributes); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + /// Name of framework like Bootstrap, Angular, etc. + /// Version number of framework. + public static void RegisterScript(ControllerContext page, string filePath, int priority, string provider, string name, string version) + { + RegisterScript(page, filePath, priority, provider, name, version, htmlAttributes: null); + } + + /// Requests that a JavaScript file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the JavaScript resource. + /// The relative priority in which the file should be loaded. + /// The name of the provider responsible for rendering the script output. + /// Name of framework like Bootstrap, Angular, etc. + /// Version number of framework. + /// A dictionary of HTML attributes to use for the script tag. The key being the attribute name and the value its value. + public static void RegisterScript( + ControllerContext page, + string filePath, + int priority, + string provider, + string name, + string version, + IDictionary htmlAttributes) + { + // var include = new DnnJsInclude { ForceProvider = provider, Priority = priority, FilePath = filePath, Name = name, Version = version, }; + var include = new BasicFile(ClientDependencyType.Javascript) + { + FilePath = filePath, + Priority = priority, + + // ForceProvider = provider, + Group = Constants.DefaultGroup, + Name = name, + Version = version, + }; + if (htmlAttributes != null) + { + foreach (var attribute in htmlAttributes) + { + include.HtmlAttributes[attribute.Key] = attribute.Value; + } + } + + // include.HtmlAttributes["defer"] = "defer"; + include.HtmlAttributes["nonce"] = HttpContext.Current.Items["CSP-NONCE"].ToString(); + var loader = page.GetLoader(); + loader.RegisterDependency(include, include.HtmlAttributes); + + // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); + } + + /// Requests that a CSS file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + public static void RegisterStyleSheet(ControllerContext page, string filePath) + { + RegisterStyleSheet(page, filePath, Constants.DefaultPriority, DefaultCssProvider, htmlAttributes: null); + } + + /// Requests that a CSS file be registered on the client browser. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// A dictionary of HTML attributes to use for the link tag. The key being the attribute name and the value its value. + public static void RegisterStyleSheet(ControllerContext page, string filePath, IDictionary htmlAttributes) + { + RegisterStyleSheet(page, filePath, Constants.DefaultPriority, DefaultCssProvider, htmlAttributes); + } + + /// Requests that a CSS file be registered on the client browser. Defaults to rendering in the page header. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority) + { + RegisterStyleSheet(page, filePath, priority, DefaultCssProvider, htmlAttributes: null); + } + + /// Requests that a CSS file be registered on the client browser. Defaults to rendering in the page header. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// A dictionary of HTML attributes to use for the link tag. The key being the attribute name and the value its value. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority, IDictionary htmlAttributes) + { + RegisterStyleSheet(page, filePath, priority, DefaultCssProvider, htmlAttributes); + } + + /// Requests that a CSS file be registered on the client browser. Defaults to rendering in the page header. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + public static void RegisterStyleSheet(ControllerContext page, string filePath, FileOrder.Css priority) + { + RegisterStyleSheet(page, filePath, (int)priority, DefaultCssProvider, htmlAttributes: null); + } + + /// Requests that a CSS file be registered on the client browser. Defaults to rendering in the page header. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// A dictionary of HTML attributes to use for the link tag. The key being the attribute name and the value its value. + public static void RegisterStyleSheet(ControllerContext page, string filePath, FileOrder.Css priority, IDictionary htmlAttributes) + { + RegisterStyleSheet(page, filePath, (int)priority, DefaultCssProvider, htmlAttributes); + } + + /// Requests that a CSS file be registered on the client browser. Allows for overriding the default provider. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// The provider name to be used to render the css file on the page. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority, string provider) + { + RegisterStyleSheet(page, filePath, priority, provider, string.Empty, string.Empty, htmlAttributes: null); + } + + /// Requests that a CSS file be registered on the client browser. Allows for overriding the default provider. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// The provider name to be used to render the css file on the page. + /// A dictionary of HTML attributes to use for the link tag. The key being the attribute name and the value its value. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority, string provider, IDictionary htmlAttributes) + { + RegisterStyleSheet(page, filePath, priority, provider, string.Empty, string.Empty, htmlAttributes); + } + + /// Requests that a CSS file be registered on the client browser. Allows for overriding the default provider. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// The provider name to be used to render the css file on the page. + /// Name of framework like Bootstrap, Angular, etc. + /// Version number of framework. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority, string provider, string name, string version) + { + RegisterStyleSheet(page, filePath, priority, provider, name, version, htmlAttributes: null); + } + + /// Requests that a CSS file be registered on the client browser. Allows for overriding the default provider. + /// The current page. Used to get a reference to the client resource loader. + /// The relative file path to the CSS resource. + /// The relative priority in which the file should be loaded. + /// The provider name to be used to render the css file on the page. + /// Name of framework like Bootstrap, Angular, etc. + /// Version number of framework. + /// A dictionary of HTML attributes to use for the link tag. The key being the attribute name and the value its value. + public static void RegisterStyleSheet(ControllerContext page, string filePath, int priority, string provider, string name, string version, IDictionary htmlAttributes) + { + var fileExists = false; + + // Some "legacy URLs" could be using their own query string versioning scheme (and we've forced them to use the new API through re-routing ControllerContextBase.RegisterStyleSheet + // Ensure that physical CSS files with query strings have their query strings removed + // Ignore absolute urls, they will not exist locally + if (!Uri.TryCreate(filePath, UriKind.Absolute, out _) && filePath.Contains(".css?")) + { + var filePathSansQueryString = RemoveQueryString(filePath); + if (File.Exists(HttpContext.Current.Server.MapPath(filePathSansQueryString))) + { + fileExists = true; + filePath = filePathSansQueryString; + } + } + else if (filePath.Contains("WebResource.axd")) + { + fileExists = true; + } + + if (!fileExists && !FileExists(page, filePath)) + { + return; + } + + // var include = new DnnCssInclude { ForceProvider = provider, Priority = priority, FilePath = filePath, Name = name, Version = version }; + var include = new BasicFile(ClientDependencyType.Css) + { + FilePath = filePath, + Priority = priority, + + // ForceProvider = provider, + Group = Constants.DefaultGroup, + Name = name, + Version = version, + }; + if (htmlAttributes != null) + { + foreach (var attribute in htmlAttributes) + { + include.HtmlAttributes[attribute.Key] = attribute.Value; + } + } + + var loader = page.GetLoader(); + loader.RegisterDependency(include); + + // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); + } + + /// This is a utility method that can be called to update the version of the composite files. + [DnnDeprecated(8, 0, 1, "This method is not required anymore. The CRM version is now managed in host settings and site settings", RemovalVersion = 10)] + public static partial void UpdateVersion() + { + } + + /// Clear the default composite files so that it can be generated next time. + public static void ClearCache() + { + var provider = ClientDependencySettings.Instance.DefaultCompositeFileProcessingProvider; + if (!(provider is CompositeFileProcessingProvider)) + { + return; + } + + try + { + var folder = provider.CompositeFilePath; + if (!folder.Exists) + { + return; + } + + var files = folder.GetFiles("*.cd?"); + foreach (var file in files) + { + file.Delete(); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + + /// Clears the cache used for file existence. + /// The path for the file. + public static void ClearFileExistsCache(string path) + { + LockFileExistsCache.EnterWriteLock(); + try + { + if (string.IsNullOrEmpty(path)) + { + FileExistsCache.Clear(); + } + else + { + FileExistsCache.Remove(path.ToLowerInvariant()); + } + } + finally + { + LockFileExistsCache.ExitWriteLock(); + } + } + + /// Enables the async postback handler. + public static void EnableAsyncPostBackHandler() + { + if (HttpContext.Current != null && !HttpContext.Current.Items.Contains("AsyncPostBackHandlerEnabled")) + { + HttpContext.Current.Items.Add("AsyncPostBackHandlerEnabled", true); + } + } + + private static bool FileExists(ControllerContext page, string filePath) + { + // remove query string for the file exists check, won't impact the absoluteness, so just do it either way. + filePath = RemoveQueryString(filePath); + var cacheKey = filePath.ToLowerInvariant(); + + // cache css file paths + if (!FileExistsCache.ContainsKey(cacheKey)) + { + // apply lock after IF, locking is more expensive than worst case scenario (check disk twice) + LockFileExistsCache.EnterWriteLock(); + try + { + FileExistsCache[cacheKey] = IsAbsoluteUrl(filePath) || File.Exists(HttpContext.Current.Server.MapPath(filePath)); + } + finally + { + LockFileExistsCache.ExitWriteLock(); + } + } + + // return if file exists from cache + LockFileExistsCache.EnterReadLock(); + try + { + return FileExistsCache[cacheKey]; + } + finally + { + LockFileExistsCache.ExitReadLock(); + } + } + + private static bool IsAbsoluteUrl(string url) + { + return Uri.TryCreate(url, UriKind.Absolute, out _); + } + + private static string RemoveQueryString(string filePath) + { + var queryStringPosition = filePath.IndexOf("?", StringComparison.Ordinal); + return queryStringPosition != -1 ? filePath.Substring(0, queryStringPosition) : filePath; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.Client/packages.config b/DNN Platform/DotNetNuke.Web.Client/packages.config index af9e451e0c6..80df64893f9 100644 --- a/DNN Platform/DotNetNuke.Web.Client/packages.config +++ b/DNN Platform/DotNetNuke.Web.Client/packages.config @@ -1,11 +1,15 @@  + + + + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs new file mode 100644 index 00000000000..76bc5556ae6 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs @@ -0,0 +1,152 @@ +// 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.Web.MvcPipeline.Containers +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Content(this HtmlHelper htmlHelper, PortalSettings portalSettings) + { + var model = htmlHelper.ViewData.Model; + if (model == null) + { + throw new InvalidOperationException("The model need to be present."); + } + + var moduleContentPaneDiv = new TagBuilder("div"); + if (!string.IsNullOrEmpty(model.ContentPaneCssClass)) + { + moduleContentPaneDiv.AddCssClass(model.ContentPaneCssClass); + } + + if (!ModuleHostModel.IsViewMode(model.ModuleConfiguration, portalSettings) && htmlHelper.ViewContext.HttpContext.Request.QueryString["dnnprintmode"] != "true") + { + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); + if (model.EditMode && model.ModuleConfiguration.ModuleID > 0) + { + moduleContentPaneDiv.InnerHtml += htmlHelper.Control("ModuleActions", model.ModuleConfiguration); + } + + // register admin.css + MvcClientResourceManager.RegisterAdminStylesheet(htmlHelper.ViewContext, Globals.HostPath + "admin.css"); + } + + if (!string.IsNullOrEmpty(model.ContentPaneStyle)) + { + moduleContentPaneDiv.Attributes["style"] = model.ContentPaneStyle; + } + + if (!string.IsNullOrEmpty(model.Header)) + { + moduleContentPaneDiv.InnerHtml += model.Header; + } + + var moduleDiv = new TagBuilder("div"); + moduleDiv.AddCssClass(model.ModuleHost.CssClass); + + /* + if (model.ModuleConfiguration.ModuleControl.ControlSrc.StartsWith("DesktopModules/RazorModules")) + { + var controlFolder = Path.GetDirectoryName(model.ModuleConfiguration.ModuleControl.ControlSrc); + var controlFileNameWithoutExtension = Path.GetFileNameWithoutExtension(model.ModuleConfiguration.ModuleControl.ControlSrc); + var srcPhysicalPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, controlFolder, "_" + controlFileNameWithoutExtension + ".cshtml"); + var scriptFile = Path.Combine("~/" + controlFolder, "Views/", "_" + controlFileNameWithoutExtension + ".cshtml"); + if (File.Exists(srcPhysicalPath)) + { + try + { + moduleDiv.InnerHtml += htmlHelper.Partial(scriptFile, model.ModuleConfiguration); + } + catch (Exception ex2) + { + throw new Exception($"Error : {ex2.Message} ( razor : {scriptFile}, module : {model.ModuleConfiguration.ModuleID})", ex2); + } + } + else + { + throw new Exception($"Error : Razor file dous not exist ( razor : {scriptFile}, module : {model.ModuleConfiguration.ModuleID})"); + + // moduleDiv.InnerHtml += $"Error : {ex.Message} (Controller : {model.ControllerName}, Action : {model.ActionName}, module : {model.ModuleConfiguration.ModuleTitle}) {ex.StackTrace}"; + } + } + */ + + var controlFolder = Path.GetDirectoryName(model.ModuleConfiguration.ModuleControl.ControlSrc); + var controlFileNameWithoutExtension = Path.GetFileNameWithoutExtension(model.ModuleConfiguration.ModuleControl.ControlSrc); + var srcPhysicalPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, controlFolder, "Partials", controlFileNameWithoutExtension + ".cshtml"); + if (File.Exists(srcPhysicalPath)) + { + var scriptFile = Path.Combine("~/" + controlFolder, "Partials", controlFileNameWithoutExtension + ".cshtml").Replace("\\", "/"); + try + { + moduleDiv.InnerHtml += htmlHelper.Partial(scriptFile, model.ModuleConfiguration); + } + catch (Exception ex2) + { + throw new Exception($"Error : {ex2.Message} ( razor : {scriptFile}, module : {model.ModuleConfiguration.ModuleID})", ex2); + } + } + else + { + moduleDiv.InnerHtml += htmlHelper.Control(model.ModuleConfiguration); + } + + /* + try + { + // module + moduleDiv.InnerHtml += htmlHelper.Control(model.ModuleConfiguration); + } + catch (HttpException ex) + { + var scriptFolder = Path.GetDirectoryName(model.ModuleConfiguration.ModuleControl.ControlSrc); + var fileRoot = Path.GetFileNameWithoutExtension(model.ModuleConfiguration.ModuleControl.ControlSrc); + var srcPhysicalPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, scriptFolder, "_" + fileRoot + ".cshtml"); + var scriptFile = Path.Combine("~/" + scriptFolder, "Views/", "_" + fileRoot + ".cshtml"); + if (File.Exists(srcPhysicalPath)) + { + try + { + moduleDiv.InnerHtml += htmlHelper.Partial(scriptFile, model.ModuleConfiguration); + } + catch (Exception ex2) + { + throw new Exception($"Error : {ex2.Message} ( razor : {scriptFile}, module : {model.ModuleConfiguration.ModuleID})", ex2); + } + } + else + { + // moduleDiv.InnerHtml += $"Error : {ex.Message} (Controller : {model.ControllerName}, Action : {model.ActionName}, module : {model.ModuleConfiguration.ModuleTitle}) {ex.StackTrace}"; + throw new Exception($"Error : {ex.Message} (Controller : {model.ControllerName}, Action : {model.ActionName}, module : {model.ModuleConfiguration.ModuleID})", ex); + } + } + catch (Exception ex) + { + // moduleDiv.InnerHtml += $"Error : {ex.Message} (Controller : {model.ControllerName}, Action : {model.ActionName}, module : {model.ModuleConfiguration.ModuleTitle}) {ex.StackTrace}"; + throw new Exception($"Error : {ex.Message} (Controller : {model.ControllerName}, Action : {model.ActionName}, module : {model.ModuleConfiguration.ModuleID})", ex); + } + */ + + moduleContentPaneDiv.InnerHtml += moduleDiv.ToString(); + if (!string.IsNullOrEmpty(model.Footer)) + { + moduleContentPaneDiv.InnerHtml += model.Footer; + } + + return MvcHtmlString.Create(moduleContentPaneDiv.InnerHtml); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Title.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Title.cs new file mode 100644 index 00000000000..d2bb426ad94 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Title.cs @@ -0,0 +1,37 @@ +// 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.Web.MvcPipeline.Containers +{ + using System; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Title(this HtmlHelper htmlHelper, string cssClass = "") + { + var model = htmlHelper.ViewData.Model; + if (model == null) + { + throw new InvalidOperationException("The model need to be present."); + } + + var labelDiv = new TagBuilder("div"); + labelDiv.InnerHtml = model.ModuleConfiguration.ModuleTitle; + if (!string.IsNullOrEmpty(cssClass)) + { + labelDiv.AddCssClass(cssClass); + } + + return MvcHtmlString.Create(labelDiv.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReport.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReport.cs new file mode 100644 index 00000000000..0df82e57a99 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReport.cs @@ -0,0 +1,21 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Host; + using DotNetNuke.Instrumentation; + using Newtonsoft.Json; + + public class CSPReport + { + [JsonProperty("csp-report")] + public CSPReportDetail CspReport { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReportDetail.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReportDetail.cs new file mode 100644 index 00000000000..491daa2e00f --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CSPReportDetail.cs @@ -0,0 +1,27 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Host; + using DotNetNuke.Instrumentation; + using Newtonsoft.Json; + + public class CSPReportDetail + { + [JsonProperty("document-uri")] + public string DocumentUri { get; set; } + + [JsonProperty("violated-directive")] + public string ViolatedDirective { get; set; } + + [JsonProperty("blocked-uri")] + public string BlockedUri { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CspController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CspController.cs new file mode 100644 index 00000000000..8013bc238d5 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/CspController.cs @@ -0,0 +1,95 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions.Logging; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Instrumentation; + using DotNetNuke.Services.Log.EventLog; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + public class CspController : Controller, IMvcController + { + private readonly ILog logger = LoggerSource.Instance.GetLogger(typeof(CspController)); + private readonly IEventLogger eventLogger; + + public CspController(IEventLogger eventLogger) + { + this.eventLogger = eventLogger; + } + + [HttpPost] + public ActionResult Report() + { + var cspFileLogging = true; + try + { + // Lire le rapport JSON du corps de la requête + string jsonContent; + using (var reader = new StreamReader(this.Request.InputStream)) + { + jsonContent = reader.ReadToEnd(); + } + + var cspReport = JsonConvert.DeserializeObject(jsonContent); + + // Écrire le log + var logEntry = "CSP Report : " + + $"Document URI: {cspReport?.CspReport?.DocumentUri}, " + + $"Violated Directive: {cspReport?.CspReport?.ViolatedDirective}, " + + $"Blocked URI: {cspReport?.CspReport?.BlockedUri}, " + + $"User Agent: {this.Request.UserAgent}"; + + if (cspFileLogging) + { + // Logger.Error("CspReport : " + JsonConvert.SerializeObject(request, Formatting.Indented)); + this.logger.Error(logEntry); + } + + ILogInfo log = new LogInfo { LogTypeKey = EventLogType.ADMIN_ALERT.ToString() }; + log.LogPortalId = PortalSettings.Current.PortalId; + log.LogPortalName = PortalSettings.Current.PortalName; + log.LogUserId = -1; + log.LogProperties.Add(new LogDetailInfo("Content Security Policy", "Report")); + log.LogProperties.Add(new LogDetailInfo("Document URI", cspReport?.CspReport?.DocumentUri)); + log.LogProperties.Add(new LogDetailInfo("Document URI", cspReport?.CspReport?.DocumentUri)); + log.LogProperties.Add(new LogDetailInfo("Violated Directive", cspReport?.CspReport?.ViolatedDirective)); + log.LogProperties.Add(new LogDetailInfo("Blocked URI", cspReport?.CspReport?.BlockedUri)); + log.LogProperties.Add(new LogDetailInfo("User Agent", this.Request.UserAgent)); + + this.eventLogger.AddLog(log); + + /* + if (cspEmailNotification) + { + var cspEmail = Host.HostEmail; + if (this.Request != null) + { + // SendMail(Host.HostEmail, cspEmail, "", "", "", "Csp Report", JsonConvert.SerializeObject(request, Formatting.Indented)); + SendMail(Host.HostEmail, cspEmail, string.Empty, string.Empty, string.Empty, "Csp Report", logEntry); + } + else + { + SendMail(Host.HostEmail, cspEmail, string.Empty, string.Empty, string.Empty, "Csp Report", "no report"); + } + } + */ + return new HttpStatusCodeResult(201, "Report recorded"); + } + catch (Exception ex) + { + this.logger.Error("CSP Report : " + ex.Message, ex); + return new HttpStatusCodeResult(500, "Error processing report"); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs new file mode 100644 index 00000000000..b76dba27e38 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs @@ -0,0 +1,81 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.Text; + using System.Web.Mvc; + using System.Web.Routing; + using System.Web.UI; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Modules; + + /* + using DotNetNuke.Web.Mvc.Framework.ActionResults; + using DotNetNuke.Web.Mvc.Framework.Modules; + using DotNetNuke.Web.Mvc.Helpers; + */ + + public abstract class DnnPageController : Controller, IMvcController + { + /// Initializes a new instance of the class. + protected DnnPageController() + { + // this.ActionInvoker = new ResultCapturingActionInvoker(); + } + + public TabInfo ActivePage + { + get { return (this.PortalSettings == null) ? null : this.PortalSettings.ActiveTab; } + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /* + public ActionResult ResultOfLastExecute + { + get + { + var actionInvoker = this.ActionInvoker as ResultCapturingActionInvoker; + return (actionInvoker != null) ? actionInvoker.ResultOfLastInvoke : null; + } + } + */ + /* + public new UserInfo User + { + get { return (this.PortalSettings == null) ? null : this.PortalSettings.UserInfo; } + } + + public new DnnUrlHelper Url { get; set; } + */ + public string LocalResourceFile { get; set; } + + public string LocalizeString(string key) + { + return Localization.GetString(key, this.LocalResourceFile); + } + + /// + protected override void Initialize(RequestContext requestContext) + { + base.Initialize(requestContext); + + // this.Url = new DnnUrlHelper(requestContext, this); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/IMvcController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/IMvcController.cs new file mode 100644 index 00000000000..6a9d9db23cb --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/IMvcController.cs @@ -0,0 +1,12 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System.Web.Mvc; + + public interface IMvcController : IController + { + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs new file mode 100644 index 00000000000..3e46b6676ac --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs @@ -0,0 +1,378 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Instrumentation; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Modules; + + using DotNetNuke.Web.MvcPipeline.Routing; + + public class ModuleControllerBase : Controller, IMvcController + { + private readonly Lazy activeModule; + /* + private readonly ILog tracelLogger = LoggerSource.Instance.GetLogger("DNN.Trace"); + private readonly Lazy serviceScopeContainer = new Lazy(ServiceScopeContainer.GetRequestOrCreateScope); + private string localResourceFile; + private ModuleInstanceContext moduleContext; + private DesktopModuleInfo desktopModule; + */ + + public ModuleControllerBase() + { + this.activeModule = new Lazy(this.InitModuleInfo); + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /// Gets userInfo for the current user. + public UserInfo UserInfo + { + get { return this.PortalSettings.UserInfo; } + } + + public ModuleInfo ActiveModule + { + get { return this.activeModule.Value; } + } + + protected ActionResult View(ModuleInfo module, object model) + { + return this.View(MvcUtils.GetControlViewName(module), model); + } + + protected ActionResult PartialView(ModuleInfo module, string viewName, object model) + { + return this.View(MvcUtils.GetControlViewName(module, viewName), model); + } + + private ModuleInfo InitModuleInfo() + { + return this.HttpContext.Request.FindModuleInfo(); + } + + /* + public bool IsHostMenu + { + get + { + return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + } + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /// + /// Gets a value indicating whether the EditMode property is used to determine whether the user is in the + /// Administrator role + /// Cache. + /// + public bool EditMode + { + get + { + return this.ModuleContext.EditMode; + } + } + + public bool IsEditable + { + get + { + return this.ModuleContext.IsEditable; + } + } + + public int PortalId + { + get + { + return this.ModuleContext.PortalId; + } + } + + public int TabId + { + get + { + return this.ModuleContext.TabId; + } + } + + public UserInfo UserInfo + { + get + { + return this.PortalSettings.UserInfo; + } + } + + public int UserId + { + get + { + return this.PortalSettings.UserId; + } + } + + public PortalAliasInfo PortalAlias + { + get + { + return this.PortalSettings.PortalAlias; + } + } + + public Hashtable Settings + { + get + { + return this.ModuleContext.Settings; + } + } + + /// Gets the Path for this control (used primarily for UserControls). + /// A String. + public string ControlPath + { + get + { + return "/" + Path.GetDirectoryName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } + } + + /// Gets the Name for this control. + /// A String. + public string ControlName + { + get + { + return this.GetType().Name.Replace("_", "."); + } + } + + /// Gets the Module Context for this control. + /// A ModuleInstanceContext. + public ModuleInstanceContext ModuleContext + { + get + { + if (this.moduleContext == null) + { + this.moduleContext = new ModuleInstanceContext() + { + Configuration = this.ActiveModule, + }; + } + + return this.moduleContext; + } + } + + public DesktopModuleInfo DesktopModule + { + get + { + if (this.desktopModule == null) + { + this.desktopModule = DesktopModuleControllerAdapter.Instance.GetDesktopModule(this.ActiveModule.DesktopModuleID, this.ActiveModule.PortalID); + } + + return this.desktopModule; + } + } + + public ModuleActionCollection Actions + { + get + { + return this.ModuleContext.Actions; + } + + set + { + this.ModuleContext.Actions = value; + } + } + + public string HelpURL + { + get + { + return this.ModuleContext.HelpURL; + } + + set + { + this.ModuleContext.HelpURL = value; + } + } + + public ModuleInfo ModuleConfiguration + { + get + { + return this.ModuleContext.Configuration; + } + + set + { + this.ModuleContext.Configuration = value; + } + } + + public int TabModuleId + { + get + { + return this.ModuleContext.TabModuleId; + } + + set + { + this.ModuleContext.TabModuleId = value; + } + } + + public int ModuleId + { + get + { + return this.ModuleContext.ModuleId; + } + + set + { + this.ModuleContext.ModuleId = value; + } + } + + public string ID + { + get + { + return Path.GetFileName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } + } + + /// Gets or sets the local resource file for this control. + /// A String. + public string LocalResourceFile + { + get + { + string fileRoot; + if (string.IsNullOrEmpty(this.localResourceFile)) + { + fileRoot = Path.Combine(this.ControlPath, Localization.LocalResourceDirectory + "/" + this.ID); + } + else + { + fileRoot = this.localResourceFile; + } + + return fileRoot; + } + + set + { + this.localResourceFile = value; + } + } + + /// + /// Gets the Dependency Provider to resolve registered + /// services with the container. + /// + /// + /// The Dependency Service. + /// + protected IServiceProvider DependencyProvider => this.serviceScopeContainer.Value.ServiceScope.ServiceProvider; + + public string EditUrl() + { + return this.ModuleContext.EditUrl(); + } + + public string EditUrl(string controlKey) + { + return this.ModuleContext.EditUrl(controlKey); + } + + public string EditUrl(string keyName, string keyValue) + { + return this.ModuleContext.EditUrl(keyName, keyValue); + } + + public string EditUrl(string keyName, string keyValue, string controlKey) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey); + } + + public string EditUrl(string keyName, string keyValue, string controlKey, params string[] additionalParameters) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey, additionalParameters); + } + + public string EditUrl(int tabID, string controlKey, bool pageRedirect, params string[] additionalParameters) + { + return this.ModuleContext.NavigateUrl(tabID, controlKey, pageRedirect, additionalParameters); + } + + public int GetNextActionID() + { + return this.ModuleContext.GetNextActionID(); + } + + /// + public override void Dispose() + { + base.Dispose(); + if (this.serviceScopeContainer.IsValueCreated) + { + this.serviceScopeContainer.Value.Dispose(); + } + } + + protected string LocalizeString(string key) + { + return Localization.GetString(key, this.LocalResourceFile); + } + + protected string LocalizeSafeJsString(string key) + { + return Localization.GetSafeJSString(key, this.LocalResourceFile); + } + */ + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsController.cs new file mode 100644 index 00000000000..d47782639b6 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsController.cs @@ -0,0 +1,386 @@ +// 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.Website.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Definitions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Skins; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Models; + using Microsoft.Extensions.DependencyInjection; + + public class ModuleSettingsController : ModuleControllerBase + { + private readonly ModuleController moduleController; + + private int moduleId = -1; + private ModuleInfo module; + + public ModuleSettingsController(INavigationManager navigationManager) + { + this.NavigationManager = navigationManager; + + // Globals.DependencyProvider.GetRequiredService(); + this.moduleController = new ModuleController(); + } + + public int TabId + { + get + { + return this.PortalSettings.ActiveTab.TabID; + } + } + + public int TabModuleId { get; private set; } + + protected INavigationManager NavigationManager { get; private set; } + + private PortalInfo Portal + { + get { return this.PortalSettings.PortalId == Null.NullInteger ? null : PortalController.Instance.GetPortal(this.PortalSettings.PortalId); } + } + + private ModuleInfo Module + { + get { return this.module ?? (this.module = ModuleController.Instance.GetModule(this.moduleId, this.TabId, false)); } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult UpdateDefaultSettings(ModuleSettingsModel model) + { + if (!this.ModelState.IsValid) + { + return this.View("Index", model); + } + + var module = this.moduleController.GetModule(model.ModuleId, model.TabId, false); + if (module == null) + { + return this.HttpNotFound(); + } + + this.module = module; + + this.CheckPermissions(); + + module.ModuleTitle = model.ModuleTitle; + module.IconFile = model.IconFile; + module.AllTabs = model.AllTabs; + module.Visibility = model.Visibility; + module.CacheTime = model.CacheTime; + module.CacheMethod = model.CacheProvider; + module.Alignment = model.Alignment; + module.Color = model.Color; + module.Border = model.Border; + module.Header = model.Header; + module.Footer = model.Footer; + module.StartDate = model.StartDate ?? Null.NullDate; + module.EndDate = model.EndDate ?? Null.NullDate; + module.ContainerSrc = model.ContainerSrc; + + // module.ModulePermissions.Clear(); + // module.ModulePermissions.AddRange(model.ModulePermissions); + module.DisplayTitle = model.DisplayTitle; + module.DisplayPrint = model.DisplayPrint; + module.DisplaySyndicate = model.DisplaySyndicate; + /* + module.IsWebSlice = model.IsWebSlice; + module.WebSliceTitle = model.WebSliceTitle; + module.WebSliceExpiryDate = model.WebSliceExpiryDate ?? Null.NullDate; + module.WebSliceTTL = model.WebSliceTTL ?? Null.NullInteger; + */ + + module.IsDefaultModule = model.IsDefaultModule; + module.AllModules = model.AllModules; + + this.moduleController.UpdateModule(module); + + this.moduleController.UpdateTabModuleSetting(module.TabModuleID, "AllowIndex", model.AllowIndex.ToString()); + this.moduleController.UpdateTabModuleSetting(module.TabModuleID, "Moniker", model.Moniker); + this.moduleController.UpdateTabModuleSetting(module.TabModuleID, "hideadminborder", model.HideAdminBorder.ToString()); + + return new EmptyResult(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult Delete(ModuleSettingsModel model) + { + var module = this.moduleController.GetModule(model.ModuleId, model.TabId, false); + if (module == null) + { + return this.HttpNotFound(); + } + + this.module = module; + + this.CheckPermissions(); + + ModuleController.Instance.DeleteTabModule(model.TabId, model.ModuleId, true); + + return new EmptyResult(); + } + + protected override void OnActionExecuting(ActionExecutingContext filterContext) + { + base.OnActionExecuting(filterContext); + } + + private void Initialize() + { + try + { + // get ModuleId + if (this.Request.QueryString["ModuleId"] != null) + { + this.moduleId = int.Parse(this.Request.QueryString["ModuleId"]); + } + + if (this.Module.ContentItemId == Null.NullInteger && this.Module.ModuleID != Null.NullInteger) + { + // This tab does not have a valid ContentItem + ModuleController.Instance.CreateContentItem(this.Module); + ModuleController.Instance.UpdateModule(this.Module); + } + + this.CheckPermissions(); + + if (this.Module != null) + { + // get module + this.TabModuleId = this.Module.TabModuleID; + + // get Settings Control + ModuleControlInfo moduleControlInfo = ModuleControlController.GetModuleControlByControlKey("Settings", this.Module.ModuleDefID); + + if (moduleControlInfo != null) + { + /* + this.control = ModuleControlFactory.LoadSettingsControl(this.Page, this.Module, moduleControlInfo.ControlSrc); + + var settingsControl = this.control as ISettingsControl; + if (settingsControl != null) + { + this.hlSpecificSettings.Text = Localization.GetString( + "ControlTitle_settings", + settingsControl.LocalResourceFile); + if (string.IsNullOrEmpty(this.hlSpecificSettings.Text)) + { + this.hlSpecificSettings.Text = + string.Format( + Localization.GetString("ControlTitle_settings", this.LocalResourceFile), + this.Module.DesktopModule.FriendlyName); + } + + this.pnlSpecific.Controls.Add(this.control); + } + */ + } + } + } + catch (Exception exc) + { + // Log the exception + DotNetNuke.Services.Exceptions.Exceptions.LogException(exc); + throw; + } + } + + private void CheckPermissions() + { + // Verify that the current user has access to edit this module + if (!ModulePermissionController.HasModuleAccess(SecurityAccessLevel.Edit, "MANAGE", this.Module)) + { + if (!(this.IsSharedViewOnly() && TabPermissionController.CanAddContentToPage())) + { + throw new UnauthorizedAccessException("Access denied"); + } + } + } + + private bool IsSharedViewOnly() + { + return this.Module.IsShared && this.Module.IsShareableViewOnly; + } + + private void BindData(ModuleInfo module, ModuleSettingsModel model) + { + var desktopModule = DesktopModuleController.GetDesktopModule(module.DesktopModuleID, this.PortalSettings.PortalId); + + if (!module.IsShared) + { + model.InheritViewPermissions = module.InheritViewPermissions; + } + + model.ModuleId = module.ModuleID; + model.FriendlyName = module.DesktopModule.FriendlyName; + model.ModuleTitle = module.ModuleTitle; + model.IconFile = module.IconFile; + + model.AllTabs = module.AllTabs; + model.AllowIndex = this.GetBooleanSetting(module, "AllowIndex", true); + model.Moniker = this.GetStringSetting(module, "Moniker"); + + model.Visibility = module.Visibility; + model.HideAdminBorder = this.GetBooleanSetting(module, "hideadminborder", false); + + var objModuleDef = ModuleDefinitionController.GetModuleDefinitionByID(module.ModuleDefID); + if (objModuleDef.DefaultCacheTime == Null.NullInteger) + { + model.CacheWarningVisible = true; + model.CacheTime = module.CacheTime; + } + else + { + model.CacheWarningVisible = false; + model.CacheTime = module.CacheTime; + } + + model.CacheProvider = module.CacheMethod; + model.CacheInheritedVisible = module.CacheMethod != module.GetEffectiveCacheMethod(); + + model.Alignment = module.Alignment; + model.Color = module.Color; + model.Border = module.Border; + + model.Header = module.Header; + model.Footer = module.Footer; + + if (!Null.IsNull(module.StartDate)) + { + model.StartDate = module.StartDate; + } + + if (!Null.IsNull(module.EndDate)) + { + model.EndDate = module.EndDate; + } + + this.BindContainers(model, module); + + model.DisplayTitle = module.DisplayTitle; + model.DisplayPrint = module.DisplayPrint; + model.DisplaySyndicate = module.DisplaySyndicate; + + if (module.ModuleID == this.PortalSettings.DefaultModuleId && module.TabID == this.PortalSettings.DefaultTabId) + { + model.IsDefaultModule = true; + } + + if (!module.IsShared && module.DesktopModule.Shareable != ModuleSharing.Unsupported) + { + model.IsShareable = module.IsShareable; + model.IsShareableViewOnly = module.IsShareableViewOnly; + model.IsShareableVisible = true; + } + + model.AvailableTabs = new List(); + var tabsByModule = TabController.Instance.GetTabsByModuleID(this.moduleId); + tabsByModule.Remove(this.TabId); + model.InstalledOnTabs = tabsByModule.Select(t => new TabModel() + { + Id = t.Value.TabID, + Name = t.Value.TabName, + InstalledOnLink = this.GetInstalledOnLink(t.Value), + InstalledOnSite = this.GetInstalledOnSite(t.Value), + }); + } + + private string GetInstalledOnLink(TabInfo tab) + { + var returnValue = new StringBuilder(); + if (tab != null) + { + var index = 0; + TabController.Instance.PopulateBreadCrumbs(ref tab); + var defaultAlias = PortalAliasController.Instance.GetPortalAliasesByPortalId(tab.IsSuperTab ? Host.HostPortalID : tab.PortalID) + .OrderByDescending(a => a.IsPrimary) + .FirstOrDefault(); + var portalSettings = new PortalSettings(tab.PortalID) + { + PortalAlias = defaultAlias, + }; + + var tabUrl = this.NavigationManager.NavigateURL(tab.TabID, portalSettings, string.Empty); + + foreach (TabInfo t in tab.BreadCrumbs) + { + if (index > 0) + { + returnValue.Append(" > "); + } + + if (tab.BreadCrumbs.Count - 1 == index) + { + returnValue.AppendFormat("{1}", tabUrl, t.LocalizedTabName); + } + else + { + returnValue.AppendFormat("{0}", t.LocalizedTabName); + } + + index = index + 1; + } + } + + return returnValue.ToString(); + } + + private string GetInstalledOnSite(TabInfo tab) + { + string returnValue = string.Empty; + if (tab != null) + { + var portal = PortalController.Instance.GetPortal(tab.PortalID); + if (portal != null) + { + returnValue = portal.PortalName; + } + } + + return returnValue; + } + + private void BindContainers(ModuleSettingsModel model, ModuleInfo module) + { + model.ContainerSrc = module.ContainerSrc; + model.ContainerOptions = SkinController.GetSkins(this.Portal, SkinController.RootContainer, SkinScope.All) + .Select(c => new SelectListItem { Text = c.Key, Value = c.Value }); + } + + private bool GetBooleanSetting(ModuleInfo module, string settingName, bool defaultValue) + { + var setting = module.TabModuleSettings[settingName]; + return setting != null ? bool.Parse(setting.ToString()) : defaultValue; + } + + private string GetStringSetting(ModuleInfo module, string settingName) + { + var setting = module.TabModuleSettings[settingName]; + return setting?.ToString() ?? string.Empty; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs new file mode 100644 index 00000000000..ec41f0bb122 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs @@ -0,0 +1,261 @@ +// 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.Website.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Web.Mvc; + + using DotNetNuke.Common.Utilities; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Definitions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Skins; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Models; + + public class ModuleSettingsViewController : ModuleControllerBase + { + private readonly ModuleController moduleController; + private readonly IContentSecurityPolicy contentSecurityPolicy; + private int moduleId = -1; + private ModuleInfo module; + + public ModuleSettingsViewController(IContentSecurityPolicy csp) + { + this.moduleController = new ModuleController(); + this.contentSecurityPolicy = csp; + } + + public int TabId + { + get + { + return this.PortalSettings.ActiveTab.TabID; + } + } + + public int TabModuleId { get; private set; } + + private PortalInfo Portal + { + get { return this.PortalSettings.PortalId == Null.NullInteger ? null : PortalController.Instance.GetPortal(this.PortalSettings.PortalId); } + } + + private ModuleInfo Module + { + get { return this.module ?? (this.module = ModuleController.Instance.GetModule(this.moduleId, this.TabId, false)); } + } + + [HttpGet] + public ActionResult Invoke(int moduleId) + { + this.Initialize(); + var model = new ModuleSettingsModel(); + model.TabId = this.TabId; + model.ReturnUrl = this.Request.QueryString["ReturnURL"]; + + ModuleControlInfo moduleControlInfo = ModuleControlController.GetModuleControlByControlKey("Settings", this.Module.ModuleDefID); + if (moduleControlInfo != null) + { + model.ModuleControlSrc = moduleControlInfo.ControlSrc; + model.ModuleControllerName = this.Module.DesktopModule.ModuleName; + model.ModuleActionName = "LoadSettings"; + model.ModuleLocalResourceFile = Path.Combine(Path.GetDirectoryName(moduleControlInfo.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(moduleControlInfo.ControlSrc)); + } + + this.BindData(this.Module, model); + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/Resources/Shared/scripts/jquery/jquery.form.min.js"); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/admin/Modules/module.js"); + this.contentSecurityPolicy.ImgSource.AddScheme("data:"); + this.contentSecurityPolicy.StyleSource.AddInline(); + return this.View(model); + } + + protected override void OnActionExecuting(ActionExecutingContext filterContext) + { + base.OnActionExecuting(filterContext); + } + + private void Initialize() + { + try + { + // get ModuleId + if (this.Request.QueryString["ModuleId"] != null) + { + this.moduleId = int.Parse(this.Request.QueryString["ModuleId"]); + } + + if (this.Module.ContentItemId == Null.NullInteger && this.Module.ModuleID != Null.NullInteger) + { + // This tab does not have a valid ContentItem + ModuleController.Instance.CreateContentItem(this.Module); + ModuleController.Instance.UpdateModule(this.Module); + } + + this.CheckPermissions(); + + if (this.Module != null) + { + // get module + this.TabModuleId = this.Module.TabModuleID; + + // get Settings Control + ModuleControlInfo moduleControlInfo = ModuleControlController.GetModuleControlByControlKey("Settings", this.Module.ModuleDefID); + + if (moduleControlInfo != null) + { + /* + this.control = ModuleControlFactory.LoadSettingsControl(this.Page, this.Module, moduleControlInfo.ControlSrc); + + var settingsControl = this.control as ISettingsControl; + if (settingsControl != null) + { + this.hlSpecificSettings.Text = Localization.GetString( + "ControlTitle_settings", + settingsControl.LocalResourceFile); + if (string.IsNullOrEmpty(this.hlSpecificSettings.Text)) + { + this.hlSpecificSettings.Text = + string.Format( + Localization.GetString("ControlTitle_settings", this.LocalResourceFile), + this.Module.DesktopModule.FriendlyName); + } + + this.pnlSpecific.Controls.Add(this.control); + } + */ + } + } + } + catch (Exception exc) + { + // Log the exception + DotNetNuke.Services.Exceptions.Exceptions.LogException(exc); + throw; + } + } + + private void CheckPermissions() + { + // Verify that the current user has access to edit this module + if (!ModulePermissionController.HasModuleAccess(SecurityAccessLevel.Edit, "MANAGE", this.Module)) + { + if (!(this.IsSharedViewOnly() && TabPermissionController.CanAddContentToPage())) + { + throw new UnauthorizedAccessException("Access denied"); + } + } + } + + private bool IsSharedViewOnly() + { + return this.Module.IsShared && this.Module.IsShareableViewOnly; + } + + private void BindData(ModuleInfo module, ModuleSettingsModel model) + { + var desktopModule = DesktopModuleController.GetDesktopModule(module.DesktopModuleID, this.PortalSettings.PortalId); + + if (!module.IsShared) + { + model.InheritViewPermissions = module.InheritViewPermissions; + } + + model.ModuleId = module.ModuleID; + model.FriendlyName = module.DesktopModule.FriendlyName; + model.ModuleTitle = module.ModuleTitle; + model.IconFile = module.IconFile; + + model.AllTabs = module.AllTabs; + model.AllowIndex = this.GetBooleanSetting(module, "AllowIndex", true); + model.Moniker = this.GetStringSetting(module, "Moniker"); + + model.Visibility = module.Visibility; + model.HideAdminBorder = this.GetBooleanSetting(module, "hideadminborder", false); + + var objModuleDef = ModuleDefinitionController.GetModuleDefinitionByID(module.ModuleDefID); + if (objModuleDef.DefaultCacheTime == Null.NullInteger) + { + model.CacheWarningVisible = true; + model.CacheTime = module.CacheTime; + } + else + { + model.CacheWarningVisible = false; + model.CacheTime = module.CacheTime; + } + + model.CacheProvider = module.CacheMethod; + model.CacheInheritedVisible = module.CacheMethod != module.GetEffectiveCacheMethod(); + + model.Alignment = module.Alignment; + model.Color = module.Color; + model.Border = module.Border; + + model.Header = module.Header; + model.Footer = module.Footer; + + if (!Null.IsNull(module.StartDate)) + { + model.StartDate = module.StartDate; + } + + if (!Null.IsNull(module.EndDate)) + { + model.EndDate = module.EndDate; + } + + this.BindContainers(model, module); + + model.DisplayTitle = module.DisplayTitle; + model.DisplayPrint = module.DisplayPrint; + model.DisplaySyndicate = module.DisplaySyndicate; + + if (module.ModuleID == this.PortalSettings.DefaultModuleId && module.TabID == this.PortalSettings.DefaultTabId) + { + model.IsDefaultModule = true; + } + + if (!module.IsShared && module.DesktopModule.Shareable != ModuleSharing.Unsupported) + { + model.IsShareable = module.IsShareable; + model.IsShareableViewOnly = module.IsShareableViewOnly; + model.IsShareableVisible = true; + } + + model.AvailableTabs = new List(); + } + + private void BindContainers(ModuleSettingsModel model, ModuleInfo module) + { + model.ContainerSrc = module.ContainerSrc; + model.ContainerOptions = SkinController.GetSkins(this.Portal, SkinController.RootContainer, SkinScope.All) + .Select(c => new SelectListItem { Text = c.Key, Value = c.Value }); + } + + private bool GetBooleanSetting(ModuleInfo module, string settingName, bool defaultValue) + { + var setting = module.TabModuleSettings[settingName]; + return setting != null ? bool.Parse(setting.ToString()) : defaultValue; + } + + private string GetStringSetting(ModuleInfo module, string settingName) + { + var setting = module.TabModuleSettings[settingName]; + return setting?.ToString() ?? string.Empty; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs new file mode 100644 index 00000000000..1b3989f5f29 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs @@ -0,0 +1,392 @@ +// 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.Web.MvcPipeline.Controllers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Instrumentation; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.MvcPipeline.Models; + using DotNetNuke.Web.MvcPipeline.Routing; + + public abstract class ModuleViewControllerBase : Controller, IMvcController + { + private ModuleInfo activeModule; + /* + private readonly ILog tracelLogger = LoggerSource.Instance.GetLogger("DNN.Trace"); + private readonly Lazy serviceScopeContainer = new Lazy(ServiceScopeContainer.GetRequestOrCreateScope); + private string localResourceFile; + private ModuleInstanceContext moduleContext; + private DesktopModuleInfo desktopModule; + */ + + public ModuleViewControllerBase() + { + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /// Gets userInfo for the current user. + public UserInfo UserInfo + { + get { return this.PortalSettings.UserInfo; } + } + + public ModuleInfo ActiveModule + { + get { return this.activeModule; } + } + + [ChildActionOnly] + public virtual ActionResult Invoke(ControlViewModel input) + { + this.activeModule = ModuleController.Instance.GetModule(input.ModuleId, input.TabId, false); + if (this.activeModule.ModuleControlId != input.ModuleControlId) + { + this.activeModule = this.activeModule.Clone(); + this.activeModule.ContainerPath = input.ContainerPath; + this.activeModule.ContainerSrc = input.ContainerSrc; + this.activeModule.ModuleControlId = input.ModuleControlId; + this.activeModule.PaneName = input.PanaName; + this.activeModule.IconFile = input.IconFile; + } + + var model = this.ViewModel(this.activeModule); + return this.PartialView(this.activeModule, model); + } + + protected abstract object ViewModel(ModuleInfo module); + + protected ActionResult PartialView(ModuleInfo module, object model) + { + return this.View(MvcUtils.GetControlViewName(module), model); + } + + protected ActionResult PartialView(ModuleInfo module, string viewName, object model) + { + return this.View(MvcUtils.GetControlViewName(module, viewName), model); + } + + /* + public bool IsHostMenu + { + get + { + return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + } + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /// + /// Gets a value indicating whether the EditMode property is used to determine whether the user is in the + /// Administrator role + /// Cache. + /// + public bool EditMode + { + get + { + return this.ModuleContext.EditMode; + } + } + + public bool IsEditable + { + get + { + return this.ModuleContext.IsEditable; + } + } + + public int PortalId + { + get + { + return this.ModuleContext.PortalId; + } + } + + public int TabId + { + get + { + return this.ModuleContext.TabId; + } + } + + public UserInfo UserInfo + { + get + { + return this.PortalSettings.UserInfo; + } + } + + public int UserId + { + get + { + return this.PortalSettings.UserId; + } + } + + public PortalAliasInfo PortalAlias + { + get + { + return this.PortalSettings.PortalAlias; + } + } + + public Hashtable Settings + { + get + { + return this.ModuleContext.Settings; + } + } + + /// Gets the Path for this control (used primarily for UserControls). + /// A String. + public string ControlPath + { + get + { + return "/" + Path.GetDirectoryName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } + } + + /// Gets the Name for this control. + /// A String. + public string ControlName + { + get + { + return this.GetType().Name.Replace("_", "."); + } + } + + /// Gets the Module Context for this control. + /// A ModuleInstanceContext. + public ModuleInstanceContext ModuleContext + { + get + { + if (this.moduleContext == null) + { + this.moduleContext = new ModuleInstanceContext() + { + Configuration = this.ActiveModule, + }; + } + + return this.moduleContext; + } + } + + public DesktopModuleInfo DesktopModule + { + get + { + if (this.desktopModule == null) + { + this.desktopModule = DesktopModuleControllerAdapter.Instance.GetDesktopModule(this.ActiveModule.DesktopModuleID, this.ActiveModule.PortalID); + } + + return this.desktopModule; + } + } + + public ModuleActionCollection Actions + { + get + { + return this.ModuleContext.Actions; + } + + set + { + this.ModuleContext.Actions = value; + } + } + + public string HelpURL + { + get + { + return this.ModuleContext.HelpURL; + } + + set + { + this.ModuleContext.HelpURL = value; + } + } + + public ModuleInfo ModuleConfiguration + { + get + { + return this.ModuleContext.Configuration; + } + + set + { + this.ModuleContext.Configuration = value; + } + } + + public int TabModuleId + { + get + { + return this.ModuleContext.TabModuleId; + } + + set + { + this.ModuleContext.TabModuleId = value; + } + } + + public int ModuleId + { + get + { + return this.ModuleContext.ModuleId; + } + + set + { + this.ModuleContext.ModuleId = value; + } + } + + public string ID + { + get + { + return Path.GetFileName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } + } + + /// Gets or sets the local resource file for this control. + /// A String. + public string LocalResourceFile + { + get + { + string fileRoot; + if (string.IsNullOrEmpty(this.localResourceFile)) + { + fileRoot = Path.Combine(this.ControlPath, Localization.LocalResourceDirectory + "/" + this.ID); + } + else + { + fileRoot = this.localResourceFile; + } + + return fileRoot; + } + + set + { + this.localResourceFile = value; + } + } + + /// + /// Gets the Dependency Provider to resolve registered + /// services with the container. + /// + /// + /// The Dependency Service. + /// + protected IServiceProvider DependencyProvider => this.serviceScopeContainer.Value.ServiceScope.ServiceProvider; + + public string EditUrl() + { + return this.ModuleContext.EditUrl(); + } + + public string EditUrl(string controlKey) + { + return this.ModuleContext.EditUrl(controlKey); + } + + public string EditUrl(string keyName, string keyValue) + { + return this.ModuleContext.EditUrl(keyName, keyValue); + } + + public string EditUrl(string keyName, string keyValue, string controlKey) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey); + } + + public string EditUrl(string keyName, string keyValue, string controlKey, params string[] additionalParameters) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey, additionalParameters); + } + + public string EditUrl(int tabID, string controlKey, bool pageRedirect, params string[] additionalParameters) + { + return this.ModuleContext.NavigateUrl(tabID, controlKey, pageRedirect, additionalParameters); + } + + public int GetNextActionID() + { + return this.ModuleContext.GetNextActionID(); + } + + /// + public override void Dispose() + { + base.Dispose(); + if (this.serviceScopeContainer.IsValueCreated) + { + this.serviceScopeContainer.Value.Dispose(); + } + } + + protected string LocalizeString(string key) + { + return Localization.GetString(key, this.LocalResourceFile); + } + + protected string LocalizeSafeJsString(string key) + { + return Localization.GetSafeJSString(key, this.LocalResourceFile); + } + */ + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DnnMvcPipelineDependencyResolver.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/DnnMvcPipelineDependencyResolver.cs new file mode 100644 index 00000000000..2992b9321e6 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DnnMvcPipelineDependencyResolver.cs @@ -0,0 +1,66 @@ +// 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.Web.MvcPipeline +{ + using System; + using System.Collections.Generic; + using System.Web.Mvc; + + using DotNetNuke.Services.DependencyInjection; + using Microsoft.Extensions.DependencyInjection; + + /// + /// The implementation used in the + /// MVC Modules of DNN. + /// + internal class DnnMvcPipelineDependencyResolver : IDependencyResolver + { + private readonly IServiceProvider serviceProvider; + + /// Initializes a new instance of the class. + /// The service provider. + public DnnMvcPipelineDependencyResolver(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + /// Returns the specified service from the scope. + /// + /// The service to be retrieved. + /// + /// + /// The retrieved service. + /// + public object GetService(Type serviceType) + { + var accessor = this.serviceProvider.GetRequiredService(); + var scope = accessor.GetScope(); + if (scope != null) + { + return scope.ServiceProvider.GetService(serviceType); + } + + throw new InvalidOperationException("IServiceScope not provided"); + } + + /// Returns the specified services from the scope. + /// + /// The service to be retrieved. + /// + /// + /// The retrieved service. + /// + public IEnumerable GetServices(Type serviceType) + { + var accessor = this.serviceProvider.GetRequiredService(); + var scope = accessor.GetScope(); + if (scope != null) + { + return scope.ServiceProvider.GetServices(serviceType); + } + + throw new InvalidOperationException("IServiceScope not provided"); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj new file mode 100644 index 00000000000..593d0ee29ab --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -0,0 +1,44 @@ + + + DotNetNuke.Web.MvcPipeline + net48 + bin + false + false + Sacha Trauwaen + Dnn + MvcPipeline + 2025 + MvcPipeline + 0.0.1.0 + 0.0.1.0 + DNN MVC-pipeline project + en-US + + Library + + Portable + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/AccesDeniedException.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/AccesDeniedException.cs new file mode 100644 index 00000000000..bd97ffa7c0f --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/AccesDeniedException.cs @@ -0,0 +1,32 @@ +// 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.Web.MvcPipeline.Exceptions +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class AccesDeniedException : MvcPageException + { + public AccesDeniedException() + { + } + + public AccesDeniedException(string message, string redirectUrl) + : base(message, redirectUrl) + { + } + + public AccesDeniedException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected AccesDeniedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/DisabledPageException.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/DisabledPageException.cs new file mode 100644 index 00000000000..cf9a19d9825 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/DisabledPageException.cs @@ -0,0 +1,37 @@ +// 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.Web.MvcPipeline.Exceptions +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class DisabledPageException : MvcPageException + { + public DisabledPageException() + { + } + + public DisabledPageException(string message) + : base(message) + { + } + + public DisabledPageException(string message, string redirectUrl) + : base(message, redirectUrl) + { + } + + public DisabledPageException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected DisabledPageException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/MvcPageException.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/MvcPageException.cs new file mode 100644 index 00000000000..3362d937139 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/MvcPageException.cs @@ -0,0 +1,39 @@ +// 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.Web.MvcPipeline.Exceptions +{ + using System; + using System.Runtime.Serialization; + + public class MvcPageException : Exception + { + public MvcPageException() + { + } + + public MvcPageException(string message) + : base(message) + { + } + + public MvcPageException(string message, string redirectUrl) + : base(message) + { + this.RedirectUrl = redirectUrl; + } + + public MvcPageException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected MvcPageException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public string RedirectUrl { get; private set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/NotFoundException.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/NotFoundException.cs new file mode 100644 index 00000000000..74156548b42 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Exceptions/NotFoundException.cs @@ -0,0 +1,37 @@ +// 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.Web.MvcPipeline.Exceptions +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class NotFoundException : MvcPageException + { + public NotFoundException() + { + } + + public NotFoundException(string message) + : base(message) + { + } + + public NotFoundException(string message, string redirectUrl) + : base(message, redirectUrl) + { + } + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected NotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Extensions/StartupExtensions.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Extensions/StartupExtensions.cs new file mode 100644 index 00000000000..27fe374d61a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Extensions/StartupExtensions.cs @@ -0,0 +1,36 @@ +// 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.Web.Mvc.Extensions +{ + using System.Linq; + + using DotNetNuke.DependencyInjection.Extensions; + using DotNetNuke.Instrumentation; + using DotNetNuke.Web.MvcPipeline.Controllers; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + + /// Adds DNN MVC Controller Specific startup extensions to simplify the Class. + public static class StartupExtensions + { + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(StartupExtensions)); + + /// Configures all of the 's to be used with the Service Collection for Dependency Injection. + /// Service Collection used to registering services in the container. + public static void AddMvcControllers(this IServiceCollection services) + { + var allTypes = TypeExtensions.SafeGetTypes(); + allTypes.LogOtherExceptions(Logger); + + var mvcControllerTypes = allTypes.Types + .Where( + type => typeof(IMvcController).IsAssignableFrom(type) && + type is { IsClass: true, IsAbstract: false }); + foreach (var controller in mvcControllerTypes) + { + services.TryAddTransient(controller); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs new file mode 100644 index 00000000000..693f7699c34 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs @@ -0,0 +1,264 @@ +// 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.Web.MvcPipeline.Framework +{ + using System; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.MvcPipeline.Models; + + public class ContainerModelFactory : IContainerModelFactory + { + public ContainerModelFactory() + { + } + + public ContainerModel CreateContainerModel(ModuleInfo configuration, PortalSettings portalSettings) + { + var container = new ContainerModel(configuration); + container = this.ProcessModule(container, portalSettings); + return container; + } + + private ContainerModel ProcessModule(ContainerModel container, PortalSettings portalSettings) + { + /* + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"Container.ProcessModule Start (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleID: {this.ModuleConfiguration.ModuleDefinition.DesktopModuleID}): Module FriendlyName: '{this.ModuleConfiguration.ModuleDefinition.FriendlyName}')"); + } + */ + // Process Content Pane Attributes + container = this.ProcessContentPane(container, portalSettings); + + // always add the actions menu as the first item in the content pane. + /* + if (this.InjectActionMenu && !ModuleHost.IsViewMode(this.ModuleConfiguration, this.PortalSettings) && this.Request.QueryString["dnnprintmode"] != "true") + { + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); + this.ContentPane.Controls.Add(this.LoadControl(this.PortalSettings.DefaultModuleActionMenu)); + + // register admin.css + MvcClientResourceManager.RegisterAdminStylesheet(this.Page, Globals.HostPath + "admin.css"); + } + */ + + // Process Module Header + container = this.ProcessHeader(container); + + // Try to load the module control + // container.moduleHost = new ModuleHostModel(this.ModuleConfiguration, this.ParentSkin, this); + // container.moduleHost.OnPreRender(); + + /* + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"Container.ProcessModule Info (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleID: {this.ModuleConfiguration.ModuleDefinition.DesktopModuleID}): ControlPane.Controls.Add(ModuleHost:{this.moduleHost.ID})"); + } + + this.ContentPane.Controls.Add(this.ModuleHost); + */ + + // Process Module Footer + container = this.ProcessFooter(container); + + /* + // Process the Action Controls + if (this.ModuleHost != null && this.ModuleControl != null) + { + this.ProcessChildControls(this); + } + */ + + // Add Module Stylesheets + container = this.ProcessStylesheets(container, container.ModuleHost != null); + + /* + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"Container.ProcessModule End (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleID: {this.ModuleConfiguration.ModuleDefinition.DesktopModuleID}): Module FriendlyName: '{this.ModuleConfiguration.ModuleDefinition.FriendlyName}')"); + } + */ + + return container; + } + + private ContainerModel ProcessContentPane(ContainerModel container, PortalSettings portalSettings) + { + container = this.SetAlignment(container); + + container = this.SetBackground(container); + + container = this.SetBorder(container); + + // display visual indicator if module is only visible to administrators + string viewRoles = container.ModuleConfiguration.InheritViewPermissions + ? TabPermissionController.GetTabPermissions(container.ModuleConfiguration.TabID, container.ModuleConfiguration.PortalID).ToString("VIEW") + : container.ModuleConfiguration.ModulePermissions.ToString("VIEW"); + + string pageEditRoles = TabPermissionController.GetTabPermissions(container.ModuleConfiguration.TabID, container.ModuleConfiguration.PortalID).ToString("EDIT"); + string moduleEditRoles = container.ModuleConfiguration.ModulePermissions.ToString("EDIT"); + + viewRoles = viewRoles.Replace(";", string.Empty).Trim().ToLowerInvariant(); + pageEditRoles = pageEditRoles.Replace(";", string.Empty).Trim().ToLowerInvariant(); + moduleEditRoles = moduleEditRoles.Replace(";", string.Empty).Trim().ToLowerInvariant(); + + var showMessage = false; + var adminMessage = Null.NullString; + if (viewRoles.Equals(portalSettings.AdministratorRoleName, StringComparison.InvariantCultureIgnoreCase) + && (moduleEditRoles.Equals(portalSettings.AdministratorRoleName, StringComparison.InvariantCultureIgnoreCase) + || string.IsNullOrEmpty(moduleEditRoles)) + && pageEditRoles.Equals(portalSettings.AdministratorRoleName, StringComparison.InvariantCultureIgnoreCase)) + { + adminMessage = Localization.GetString("ModuleVisibleAdministrator.Text"); + showMessage = !container.ModuleConfiguration.HideAdminBorder && !Globals.IsAdminControl(); + } + + if (container.ModuleConfiguration.StartDate >= DateTime.Now) + { + adminMessage = string.Format(Localization.GetString("ModuleEffective.Text"), container.ModuleConfiguration.StartDate); + showMessage = !Globals.IsAdminControl(); + } + + if (container.ModuleConfiguration.EndDate <= DateTime.Now) + { + adminMessage = string.Format(Localization.GetString("ModuleExpired.Text"), container.ModuleConfiguration.EndDate); + showMessage = !Globals.IsAdminControl(); + } + + if (showMessage) + { + container = this.AddAdministratorOnlyHighlighting(container, adminMessage); + } + + return container; + } + + /// ProcessFooter adds an optional footer (and an End_Module comment).. + private ContainerModel ProcessFooter(ContainerModel container) + { + // inject the footer + if (!string.IsNullOrEmpty(container.ModuleConfiguration.Footer)) + { + container.Footer = container.ModuleConfiguration.Footer; + } + + // inject an end comment around the module content + if (!Globals.IsAdminControl()) + { + // this.ContentPane.Controls.Add(new LiteralControl("")); + } + + return container; + } + + /// ProcessHeader adds an optional header (and a Start_Module_ comment).. + private ContainerModel ProcessHeader(ContainerModel container) + { + if (!Globals.IsAdminControl()) + { + // inject a start comment around the module content + // this.ContentPane.Controls.Add(new LiteralControl("")); + } + + // inject the header + if (!string.IsNullOrEmpty(container.ModuleConfiguration.Header)) + { + container.Header = container.ModuleConfiguration.Header; + } + + return container; + } + + /// + /// ProcessStylesheets processes the Module and Container stylesheets and adds + /// them to the Page. + /// + private ContainerModel ProcessStylesheets(ContainerModel container, bool includeModuleCss) + { + // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, container.ContainerPath + "container.css", FileOrder.Css.ContainerCss); + container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = container.ContainerPath + "container.css", FileOrder = FileOrder.Css.ContainerCss }); + + // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, container.ContainerSrc.Replace(".ascx", ".css"), FileOrder.Css.SpecificContainerCss); + container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = container.ContainerSrc.Replace(".ascx", ".css"), FileOrder = FileOrder.Css.SpecificContainerCss }); + + // process the base class module properties + if (includeModuleCss) + { + string controlSrc = container.ModuleConfiguration.ModuleControl.ControlSrc; + string folderName = container.ModuleConfiguration.DesktopModule.FolderName; + + string stylesheet = string.Empty; + if (string.IsNullOrEmpty(folderName) == false) + { + if (controlSrc.EndsWith(".mvc")) + { + stylesheet = Globals.ApplicationPath + "/DesktopModules/MVC/" + folderName.Replace("\\", "/") + "/module.css"; + } + else + { + stylesheet = Globals.ApplicationPath + "/DesktopModules/" + folderName.Replace("\\", "/") + "/module.css"; + } + + // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, stylesheet, FileOrder.Css.ModuleCss); + container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = stylesheet, FileOrder = FileOrder.Css.ModuleCss }); + } + + var ix = controlSrc.LastIndexOf("/", StringComparison.Ordinal); + if (ix >= 0) + { + stylesheet = Globals.ApplicationPath + "/" + controlSrc.Substring(0, ix + 1) + "module.css"; + + // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, stylesheet, FileOrder.Css.ModuleCss); + container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = stylesheet, FileOrder = FileOrder.Css.ModuleCss }); + } + } + + return container; + } + + private ContainerModel SetAlignment(ContainerModel container) + { + if (!string.IsNullOrEmpty(container.ModuleConfiguration.Alignment)) + { + container.ContentPaneCssClass += " DNNAlign" + container.ModuleConfiguration.Alignment.ToLowerInvariant(); + } + + return container; + } + + private ContainerModel SetBackground(ContainerModel container) + { + if (!string.IsNullOrEmpty(container.ModuleConfiguration.Color)) + { + container.ContentPaneStyle += "background-color:" + container.ModuleConfiguration.Color + ";"; + } + + return container; + } + + private ContainerModel SetBorder(ContainerModel container) + { + if (!string.IsNullOrEmpty(container.ModuleConfiguration.Border)) + { + container.ContentPaneStyle += "border:" + string.Format("{0}px #000000 solid", container.ModuleConfiguration.Border) + ";"; + } + + return container; + } + + private ContainerModel AddAdministratorOnlyHighlighting(ContainerModel container, string message) + { + // this.ContentPane.Controls.Add(new LiteralControl(string.Format("
{0}
", message))); + return container; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IContainerModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IContainerModelFactory.cs new file mode 100644 index 00000000000..59e155483d1 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IContainerModelFactory.cs @@ -0,0 +1,15 @@ +// 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.Web.MvcPipeline.Framework +{ + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Web.MvcPipeline.Models; + + public interface IContainerModelFactory + { + ContainerModel CreateContainerModel(ModuleInfo configuration, PortalSettings portalSettings); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPageModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPageModelFactory.cs new file mode 100644 index 00000000000..2b764cd76c7 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPageModelFactory.cs @@ -0,0 +1,14 @@ +// 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.Web.MvcPipeline.Framework +{ + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Models; + + public interface IPageModelFactory + { + PageModel CreatePageModel(DnnPageController page); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPaneModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPaneModelFactory.cs new file mode 100644 index 00000000000..c2f3dbe9a70 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IPaneModelFactory.cs @@ -0,0 +1,19 @@ +// 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.Web.MvcPipeline.Framework +{ + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Web.MvcPipeline.Models; + + public interface IPaneModelFactory + { + PaneModel CreatePane(string name); + + PaneModel InjectModule(PaneModel pane, ModuleInfo module, PortalSettings portalSettings); + + PaneModel ProcessPane(PaneModel pane); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ISkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ISkinModelFactory.cs new file mode 100644 index 00000000000..be29eaf77ca --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ISkinModelFactory.cs @@ -0,0 +1,14 @@ +// 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.Web.MvcPipeline.Framework +{ + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Models; + + public interface ISkinModelFactory + { + SkinModel CreateSkinModel(DnnPageController page); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs new file mode 100644 index 00000000000..9c09b6d9c09 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs @@ -0,0 +1,248 @@ +// 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.Web.MvcPipeline.Framework +{ + using System; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Web.Helpers; + + using DotNetNuke.Abstractions; + using DotNetNuke.Abstractions.Application; + using DotNetNuke.Application; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Services.FileSystem; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI.Internals; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Models; + + public class PageModelFactory : IPageModelFactory + { + private static readonly Regex HeaderTextRegex = new Regex( + "])+name=('|\")robots('|\")", + RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); + + private readonly IContentSecurityPolicy contentSecurityPolicy; + private readonly INavigationManager navigationManager; + private readonly IPortalController portalController; + private readonly IModuleControlPipeline moduleControlPipeline; + private readonly IApplicationInfo applicationInfo; + private readonly ISkinModelFactory skinModelFactory; + + public PageModelFactory( + IContentSecurityPolicy contentSecurityPolicy, + INavigationManager navigationManager, + IPortalController portalController, + IModuleControlPipeline moduleControlPipeline, + IApplicationInfo applicationInfo, + ISkinModelFactory skinModelFactory) + { + this.contentSecurityPolicy = contentSecurityPolicy; + this.navigationManager = navigationManager; + this.portalController = portalController; + this.moduleControlPipeline = moduleControlPipeline; + this.applicationInfo = applicationInfo; + this.skinModelFactory = skinModelFactory; + } + + public PageModel CreatePageModel(DnnPageController page) + { + var ctl = page.Request.QueryString["ctl"] != null ? page.Request.QueryString["ctl"] : string.Empty; + var pageModel = new PageModel + { + IsEditMode = Globals.IsEditMode(), + AntiForgery = AntiForgery.GetHtml().ToHtmlString(), + PortalId = page.PortalSettings.PortalId, + TabId = page.PortalSettings.ActiveTab.TabID, + Language = Thread.CurrentThread.CurrentCulture.Name, + ContentSecurityPolicy = this.contentSecurityPolicy, + NavigationManager = this.navigationManager, + FavIconLink = FavIcon.GetHeaderLink(page.PortalSettings.PortalId), + }; + if (page.PortalSettings.ActiveTab.PageHeadText != Null.NullString && !Globals.IsAdminControl()) + { + pageModel.PageHeadText = page.PortalSettings.ActiveTab.PageHeadText; + } + + if (!string.IsNullOrEmpty(page.PortalSettings.PageHeadText)) + { + pageModel.PortalHeadText = page.PortalSettings.PageHeadText; + } + + // set page title + if (UrlUtils.InPopUp()) + { + var strTitle = new StringBuilder(page.PortalSettings.PortalName); + var slaveModule = UI.UIUtilities.GetSlaveModule(page.PortalSettings.ActiveTab.TabID); + + // Skip is popup is just a tab (no slave module) + if (slaveModule.DesktopModuleID != Null.NullInteger) + { + var control = this.moduleControlPipeline.CreateModuleControl(slaveModule) as IModuleControl; + string extension = Path.GetExtension(slaveModule.ModuleControl.ControlSrc.ToLowerInvariant()); + switch (extension) + { + case ".mvc": + var segments = slaveModule.ModuleControl.ControlSrc.Replace(".mvc", string.Empty).Split('/'); + + control.LocalResourceFile = string.Format( + "~/DesktopModules/MVC/{0}/{1}/{2}.resx", + slaveModule.DesktopModule.FolderName, + Localization.LocalResourceDirectory, + segments[0]); + break; + default: + control.LocalResourceFile = string.Concat( + slaveModule.ModuleControl.ControlSrc.Replace( + Path.GetFileName(slaveModule.ModuleControl.ControlSrc), + string.Empty), + Localization.LocalResourceDirectory, + "/", + Path.GetFileName(slaveModule.ModuleControl.ControlSrc)); + break; + } + + var title = Localization.LocalizeControlTitle(control); + + strTitle.Append(string.Concat(" > ", page.PortalSettings.ActiveTab.LocalizedTabName)); + strTitle.Append(string.Concat(" > ", title)); + } + else + { + strTitle.Append(string.Concat(" > ", page.PortalSettings.ActiveTab.LocalizedTabName)); + } + + // Set to page + pageModel.Title = strTitle.ToString(); + } + else + { + // If tab is named, use that title, otherwise build it out via breadcrumbs + if (!string.IsNullOrEmpty(page.PortalSettings.ActiveTab.Title)) + { + pageModel.Title = page.PortalSettings.ActiveTab.Title; + } + else + { + // Elected for SB over true concatenation here due to potential for long nesting depth + var strTitle = new StringBuilder(page.PortalSettings.PortalName); + foreach (TabInfo tab in page.PortalSettings.ActiveTab.BreadCrumbs) + { + strTitle.Append(string.Concat(" > ", tab.TabName)); + } + + pageModel.Title = strTitle.ToString(); + } + } + + // set the background image if there is one selected + if (!UrlUtils.InPopUp()) + { + if (!string.IsNullOrEmpty(page.PortalSettings.BackgroundFile)) + { + var fileInfo = this.GetBackgroundFileInfo(page.PortalSettings); + pageModel.BackgroundUrl = FileManager.Instance.GetUrl(fileInfo); + + // ((HtmlGenericControl)this.FindControl("Body")).Attributes["style"] = string.Concat("background-image: url('", url, "')"); + } + } + + // META Refresh + // Only autorefresh the page if we are in VIEW-mode and if we aren't displaying some module's subcontrol. + if (page.PortalSettings.ActiveTab.RefreshInterval > 0 && Personalization.GetUserMode() == PortalSettings.Mode.View && string.IsNullOrEmpty(ctl)) + { + pageModel.MetaRefresh = page.PortalSettings.ActiveTab.RefreshInterval.ToString(); + } + + // META description + if (!string.IsNullOrEmpty(page.PortalSettings.ActiveTab.Description)) + { + pageModel.Description = page.PortalSettings.ActiveTab.Description; + } + else + { + pageModel.Description = page.PortalSettings.Description; + } + + // META keywords + if (!string.IsNullOrEmpty(page.PortalSettings.ActiveTab.KeyWords)) + { + pageModel.KeyWords = page.PortalSettings.ActiveTab.KeyWords; + } + else + { + pageModel.KeyWords = page.PortalSettings.KeyWords; + } + + // META copyright + if (!string.IsNullOrEmpty(page.PortalSettings.FooterText)) + { + pageModel.Copyright = page.PortalSettings.FooterText.Replace("[year]", DateTime.Now.Year.ToString()); + } + else + { + pageModel.Copyright = string.Concat("Copyright (c) ", DateTime.Now.Year, " by ", page.PortalSettings.PortalName); + } + + // META generator + pageModel.Generator = string.Empty; + + // META Robots - hide it inside popups and if PageHeadText of current tab already contains a robots meta tag + if (!UrlUtils.InPopUp() && + !(HeaderTextRegex.IsMatch(page.PortalSettings.ActiveTab.PageHeadText) || + HeaderTextRegex.IsMatch(page.PortalSettings.PageHeadText))) + { + var allowIndex = true; + if ((page.PortalSettings.ActiveTab.TabSettings.ContainsKey("AllowIndex") && + bool.TryParse(page.PortalSettings.ActiveTab.TabSettings["AllowIndex"].ToString(), out allowIndex) && + !allowIndex) || ctl == "Login" || ctl == "Register") + { + pageModel.MetaRobots = "NOINDEX, NOFOLLOW"; + } + else + { + pageModel.MetaRobots = "INDEX, FOLLOW"; + } + } + + // NonProduction Label Injection + if (this.applicationInfo.Status != Abstractions.Application.ReleaseMode.Stable && Host.DisplayBetaNotice && !UrlUtils.InPopUp()) + { + string versionString = + $" ({this.applicationInfo.Status} Version: {this.applicationInfo.Version})"; + pageModel.Title += versionString; + } + + pageModel.Skin = this.skinModelFactory.CreateSkinModel(page); + + return pageModel; + } + + private IFileInfo GetBackgroundFileInfo(PortalSettings portalSettings) + { + string cacheKey = string.Format(Common.Utilities.DataCache.PortalCacheKey, portalSettings.PortalId, "BackgroundFile"); + var file = CBO.GetCachedObject( + new CacheItemArgs(cacheKey, Common.Utilities.DataCache.PortalCacheTimeOut, Common.Utilities.DataCache.PortalCachePriority, portalSettings.PortalId, portalSettings.BackgroundFile), + this.GetBackgroundFileInfoCallBack); + + return file; + } + + private IFileInfo GetBackgroundFileInfoCallBack(CacheItemArgs itemArgs) + { + return FileManager.Instance.GetFile((int)itemArgs.Params[0], (string)itemArgs.Params[1]); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PaneModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PaneModelFactory.cs new file mode 100644 index 00000000000..aa3b9ab7496 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PaneModelFactory.cs @@ -0,0 +1,421 @@ +// 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.Web.MvcPipeline.Framework +{ + using System; + using System.IO; + using System.Threading; + using System.Web; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI.Skins; + using DotNetNuke.Web.MvcPipeline.Models; + + public class PaneModelFactory : IPaneModelFactory + { + private readonly IContainerModelFactory containerModelFactory; + + public PaneModelFactory(IContainerModelFactory containerModelFactory) + { + this.containerModelFactory = containerModelFactory; + } + + public PaneModel CreatePane(string name) + { + var pane = new PaneModel(name); + return pane; + } + + public PaneModel InjectModule(PaneModel pane, ModuleInfo module, PortalSettings portalSettings) + { + // this.containerWrapperControl = new HtmlGenericControl("div"); + // this.PaneControl.Controls.Add(this.containerWrapperControl); + + // inject module classes + string classFormatString = "DnnModule DnnModule-{0} DnnModule-{1}"; + string sanitizedModuleName = Null.NullString; + + if (!string.IsNullOrEmpty(module.DesktopModule.ModuleName)) + { + sanitizedModuleName = Globals.CreateValidClass(module.DesktopModule.ModuleName, false); + } + + if (this.IsVesionableModule(module)) + { + classFormatString += " DnnVersionableControl"; + } + + // this.containerWrapperControl.Attributes["class"] = string.Format(classFormatString, sanitizedModuleName, module.ModuleID); + try + { + if (!Globals.IsAdminControl() && (portalSettings.InjectModuleHyperLink || Personalization.GetUserMode() != PortalSettings.Mode.View)) + { + // this.containerWrapperControl.Controls.Add(new LiteralControl("")); + } + + // Load container control + ContainerModel container = this.LoadModuleContainer(module, portalSettings); + + // Add Container to Dictionary + pane.Containers.Add(container.ID, container); + + // hide anything of type ActionsMenu - as we're injecting our own menu now. + /* + container.InjectActionMenu = container.Controls.OfType().Count() == 0; + if (!container.InjectActionMenu) + { + foreach (var actionControl in container.Controls.OfType()) + { + if (actionControl is ActionsMenu) + { + Control control = actionControl as Control; + if (control != null) + { + control.Visible = false; + container.InjectActionMenu = true; + } + } + } + } + + if (Globals.IsLayoutMode() && Globals.IsAdminControl() == false) + { + // provide Drag-N-Drop capabilities + var dragDropContainer = new Panel(); + Control title = container.FindControl("dnnTitle"); + + // Assume that the title control is named dnnTitle. If this becomes an issue we could loop through the controls looking for the title type of skin object + dragDropContainer.ID = container.ID + "_DD"; + this.containerWrapperControl.Controls.Add(dragDropContainer); + + // inject the container into the page pane - this triggers the Pre_Init() event for the user control + dragDropContainer.Controls.Add(container); + + if (title != null) + { + if (title.Controls.Count > 0) + { + title = title.Controls[0]; + } + } + + // enable drag and drop + if (title != null) + { + // The title ID is actually the first child so we need to make sure at least one child exists + DNNClientAPI.EnableContainerDragAndDrop(title, dragDropContainer, module.ModuleID); + ClientAPI.RegisterPostBackEventHandler(this.PaneControl, "MoveToPane", this.ModuleMoveToPanePostBack, false); + } + } + else + { + this.containerWrapperControl.Controls.Add(container); + if (Globals.IsAdminControl()) + { + this.containerWrapperControl.Attributes["class"] += " DnnModule-Admin"; + } + } + */ + + // Attach Module to Container + // container.SetModuleConfiguration(module); + + // display collapsible page panes + /* + if (this.PaneControl.Visible == false) + { + this.PaneControl.Visible = true; + } + */ + } + catch (ThreadAbortException) + { + // Response.Redirect may called in module control's OnInit method, so it will cause ThreadAbortException, no need any action here. + } + catch (Exception exc) + { + var lex = new ModuleLoadException(string.Format(Skin.MODULEADD_ERROR, pane.Name), exc); + /* + if (TabPermissionController.CanAdminPage()) + { + // only display the error to administrators + this.containerWrapperControl.Controls.Add(new ErrorContainer(this.PortalSettings, Skin.MODULELOAD_ERROR, lex).Container); + } + + Exceptions.LogException(exc); + */ + throw lex; + } + + return pane; + } + + public PaneModel ProcessPane(PaneModel pane) + { + /* + // remove excess skin non-validating attributes + this.PaneControl.Attributes.Remove("ContainerType"); + this.PaneControl.Attributes.Remove("ContainerName"); + this.PaneControl.Attributes.Remove("ContainerSrc"); + */ + if (Globals.IsLayoutMode()) + { + /* + this.PaneControl.Visible = true; + + // display pane border + string cssclass = this.PaneControl.Attributes["class"]; + if (string.IsNullOrEmpty(cssclass)) + { + this.PaneControl.Attributes["class"] = CPaneOutline; + } + else + { + this.PaneControl.Attributes["class"] = cssclass.Replace(CPaneOutline, string.Empty).Trim().Replace(" ", " ") + " " + CPaneOutline; + } + + // display pane name + var ctlLabel = new Label { Text = "
" + this.Name + "

", CssClass = "SubHead" }; + this.PaneControl.Controls.AddAt(0, ctlLabel); + */ + } + else + { + /* + if (this.PaneControl.Visible == false && TabPermissionController.CanAddContentToPage()) + { + this.PaneControl.Visible = true; + }*/ + if (this.CanCollapsePane(pane)) + { + // This pane has no controls so set the width to 0 + /* + if (this.PaneControl.Attributes["style"] != null) + { + this.PaneControl.Attributes.Remove("style"); + } + */ + pane.CssClass += " DNNEmptyPane"; + } + + // Add support for drag and drop + if (Globals.IsEditMode()) + { + pane.CssClass += " dnnSortable"; + + // this call also checks for permission + } + } + + return pane; + } + + private bool CanCollapsePane(PaneModel pane) + { + // This section sets the width to "0" on panes that have no modules. + // This preserves the integrity of the HTML syntax so we don't have to set + // the visiblity of a pane to false. Setting the visibility of a pane to + // false where there are colspans and rowspans can render the skin incorrectly. + bool canCollapsePane = true; + if (pane.Containers.Count > 0) + { + canCollapsePane = false; + } + + /* + else if (this.PaneControl.Controls.Count == 1) + { + // Pane contains 1 control + canCollapsePane = false; + var literal = this.PaneControl.Controls[0] as LiteralControl; + if (literal != null) + { + // Check if the literal control is just whitespace - if so we can collapse panes + if (string.IsNullOrEmpty(HtmlUtils.StripWhiteSpace(literal.Text, false))) + { + canCollapsePane = true; + } + } + } + else if (this.PaneControl.Controls.Count > 1) + { + // Pane contains more than 1 control + canCollapsePane = false; + } + */ + return canCollapsePane; + } + + private bool IsVesionableModule(ModuleInfo moduleInfo) + { + if (string.IsNullOrEmpty(moduleInfo.DesktopModule.BusinessControllerClass)) + { + return false; + } + + object controller = DotNetNuke.Framework.Reflection.CreateObject(moduleInfo.DesktopModule.BusinessControllerClass, string.Empty); + return controller is IVersionable; + } + + private ContainerModel LoadContainerFromCookie(HttpRequest request, PortalSettings portalSettings) + { + ContainerModel container = null; + HttpCookie cookie = request.Cookies["_ContainerSrc" + portalSettings.PortalId]; + if (cookie != null) + { + if (!string.IsNullOrEmpty(cookie.Value)) + { + // container = this.LoadContainerByPath(SkinController.FormatSkinSrc(cookie.Value + ".ascx", this.PortalSettings)); + } + } + + return container; + } + + private ContainerModel LoadModuleContainer(ModuleInfo module, PortalSettings portalSettings) + { + var containerSrc = Null.NullString; + + // var request = this.PaneControl.Page.Request; + ContainerModel container = null; + + if (portalSettings.EnablePopUps && UrlUtils.InPopUp()) + { + containerSrc = module.ContainerPath + "popUpContainer.ascx"; + + // Check Skin for a popup Container + if (module.ContainerSrc == portalSettings.ActiveTab.ContainerSrc) + { + if (File.Exists(HttpContext.Current.Server.MapPath(containerSrc))) + { + container = this.LoadContainerByPath(containerSrc, module, portalSettings); + } + } + + // error loading container - load default popup container + if (container == null) + { + containerSrc = Globals.HostPath + "Containers/_default/popUpContainer.ascx"; + container = this.LoadContainerByPath(containerSrc, module, portalSettings); + } + } + else + { + /* + container = (this.LoadContainerFromQueryString(module, request) ?? this.LoadContainerFromCookie(request)) ?? this.LoadNoContainer(module); + if (container == null) + { + // Check Skin for Container + var masterModules = this.PortalSettings.ActiveTab.ChildModules; + if (masterModules.ContainsKey(module.ModuleID) && string.IsNullOrEmpty(masterModules[module.ModuleID].ContainerSrc)) + { + // look for a container specification in the skin pane + if (this.PaneControl != null) + { + if (this.PaneControl.Attributes["ContainerSrc"] != null) + { + container = this.LoadContainerFromPane(); + } + } + } + } + */ + // else load assigned container + if (container == null) + { + containerSrc = module.ContainerSrc; + if (!string.IsNullOrEmpty(containerSrc)) + { + containerSrc = SkinController.FormatSkinSrc(containerSrc, portalSettings); + container = this.LoadContainerByPath(containerSrc, module, portalSettings); + } + } + + // error loading container - load from tab + if (container == null) + { + containerSrc = portalSettings.ActiveTab.ContainerSrc; + if (!string.IsNullOrEmpty(containerSrc)) + { + containerSrc = SkinController.FormatSkinSrc(containerSrc, portalSettings); + container = this.LoadContainerByPath(containerSrc, module, portalSettings); + } + } + + // error loading container - load default + if (container == null) + { + containerSrc = SkinController.FormatSkinSrc(SkinController.GetDefaultPortalContainer(), portalSettings); + container = this.LoadContainerByPath(containerSrc, module, portalSettings); + } + } + + // Set container path + module.ContainerPath = SkinController.FormatSkinPath(containerSrc); + + // set container id to an explicit short name to reduce page payload + container.ID = "ctr"; + + // make the container id unique for the page + if (module.ModuleID > -1) + { + container.ID += module.ModuleID.ToString(); + } + + container.EditMode = Personalization.GetUserMode() == PortalSettings.Mode.Edit; + + return container; + } + + private ContainerModel LoadContainerByPath(string containerPath, ModuleInfo module, PortalSettings portalSettings) + { + if (containerPath.IndexOf("/skins/", StringComparison.InvariantCultureIgnoreCase) != -1 || containerPath.IndexOf("/skins\\", StringComparison.InvariantCultureIgnoreCase) != -1 || containerPath.IndexOf("\\skins\\", StringComparison.InvariantCultureIgnoreCase) != -1 || + containerPath.IndexOf("\\skins/", StringComparison.InvariantCultureIgnoreCase) != -1) + { + throw new Exception(); + } + + ContainerModel container = null; + + try + { + string containerSrc = containerPath; + if (containerPath.IndexOf(Globals.ApplicationPath, StringComparison.InvariantCultureIgnoreCase) != -1) + { + containerPath = containerPath.Remove(0, Globals.ApplicationPath.Length); + } + + // container = ControlUtilities.LoadControl(this.PaneControl.Page, containerPath); + container = this.containerModelFactory.CreateContainerModel(module, portalSettings); + container.ContainerSrc = containerSrc; + + // call databind so that any server logic in the container is executed + // container.DataBind(); + } + catch (Exception exc) + { + // could not load user control + var lex = new ModuleLoadException(Skin.MODULELOAD_ERROR, exc); + if (TabPermissionController.CanAdminPage()) + { + // only display the error to administrators + /* + this.containerWrapperControl.Controls.Add(new ErrorContainer(this.PortalSettings, string.Format(Skin.CONTAINERLOAD_ERROR, containerPath), lex).Container); + */ + } + + Exceptions.LogException(lex); + } + + return container; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs new file mode 100644 index 00000000000..e08607bb535 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs @@ -0,0 +1,713 @@ +// 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.Web.MvcPipeline.Framework +{ + using System; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Web; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Tabs.TabVersions; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.FileSystem; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI; + using DotNetNuke.UI.ControlPanels; + using DotNetNuke.UI.Modules; + using DotNetNuke.UI.Skins; + using DotNetNuke.UI.Skins.Controls; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Exceptions; + using DotNetNuke.Web.MvcPipeline.Models; + using Microsoft.Extensions.DependencyInjection; + + public class SkinModelFactory : ISkinModelFactory + { + private readonly INavigationManager navigationManager; + private readonly IPaneModelFactory paneModelFactory; + + public SkinModelFactory(INavigationManager navigationManager, IPaneModelFactory paneModelFactory) + { + this.navigationManager = navigationManager; + this.paneModelFactory = paneModelFactory; + } + + public SkinModel CreateSkinModel(DnnPageController page) + { + SkinModel skin = null; + string skinSource = Null.NullString; + + if (page.PortalSettings.EnablePopUps && UrlUtils.InPopUp()) + { + // attempt to find and load a popup skin from the assigned skinned source + skinSource = Globals.IsAdminSkin() ? SkinController.FormatSkinSrc(page.PortalSettings.DefaultAdminSkin, page.PortalSettings) : page.PortalSettings.ActiveTab.SkinSrc; + if (!string.IsNullOrEmpty(skinSource)) + { + skinSource = SkinController.FormatSkinSrc(SkinController.FormatSkinPath(skinSource) + "popUpSkin.ascx", page.PortalSettings); + + if (File.Exists(HttpContext.Current.Server.MapPath(SkinController.FormatSkinSrc(skinSource, page.PortalSettings)))) + { + skin = this.LoadSkin(page, skinSource); + } + } + + // error loading popup skin - load default popup skin + if (skin == null) + { + skinSource = Globals.HostPath + "Skins/_default/popUpSkin.ascx"; + skin = this.LoadSkin(page, skinSource); + } + + // set skin path + page.PortalSettings.ActiveTab.SkinPath = SkinController.FormatSkinPath(skinSource); + + // set skin id to an explicit short name to reduce page payload and make it standards compliant + /* + skin.ID = "dnn"; + */ + } + else + { + // skin preview + if (page.Request.QueryString["SkinSrc"] != null) + { + skinSource = SkinController.FormatSkinSrc(Globals.QueryStringDecode(page.Request.QueryString["SkinSrc"]) + ".ascx", page.PortalSettings); + skin = this.LoadSkin(page, skinSource); + } + + // load user skin ( based on cookie ) + if (skin == null) + { + HttpCookie skinCookie = page.Request.Cookies["_SkinSrc" + page.PortalSettings.PortalId]; + if (skinCookie != null) + { + if (!string.IsNullOrEmpty(skinCookie.Value)) + { + skinSource = SkinController.FormatSkinSrc(skinCookie.Value + ".ascx", page.PortalSettings); + skin = this.LoadSkin(page, skinSource); + } + } + } + + // load assigned skin + if (skin == null) + { + // DNN-6170 ensure skin value is culture specific + // skinSource = Globals.IsAdminSkin() ? SkinController.FormatSkinSrc(page.page.PortalSettings.DefaultAdminSkin, page.page.PortalSettings) : page.page.PortalSettings.ActiveTab.SkinSrc; + skinSource = Globals.IsAdminSkin() ? PortalController.GetPortalSetting("DefaultAdminSkin", page.PortalSettings.PortalId, Host.DefaultPortalSkin, page.PortalSettings.CultureCode) : page.PortalSettings.ActiveTab.SkinSrc; + if (!string.IsNullOrEmpty(skinSource)) + { + skinSource = SkinController.FormatSkinSrc(skinSource, page.PortalSettings); + skin = this.LoadSkin(page, skinSource); + } + } + + // error loading skin - load default + if (skin == null) + { + skinSource = SkinController.FormatSkinSrc(SkinController.GetDefaultPortalSkin(), page.PortalSettings); + skin = this.LoadSkin(page, skinSource); + } + + // set skin path + page.PortalSettings.ActiveTab.SkinPath = SkinController.FormatSkinPath(skinSource); + + // set skin id to an explicit short name to reduce page payload and make it standards compliant + /* + skin.ID = "dnn"; + */ + } + + if (page.PortalSettings.ActiveTab.DisableLink) + { + if (TabPermissionController.CanAdminPage()) + { + var heading = Localization.GetString("PageDisabled.Header"); + var message = Localization.GetString("PageDisabled.Text"); + + this.AddPageMessage( + skin, + heading, + message, + ModuleMessage.ModuleMessageType.YellowWarning); + } + } + + // register the custom stylesheet of current page + if (page.PortalSettings.ActiveTab.TabSettings.ContainsKey("CustomStylesheet") && !string.IsNullOrEmpty(page.PortalSettings.ActiveTab.TabSettings["CustomStylesheet"].ToString())) + { + var styleSheet = page.PortalSettings.ActiveTab.TabSettings["CustomStylesheet"].ToString(); + + // Try and go through the FolderProvider first + var stylesheetFile = this.GetPageStylesheetFileInfo(styleSheet, page.PortalSettings.PortalId); + if (stylesheetFile != null) + { + skin.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = FileManager.Instance.GetUrl(stylesheetFile), FileOrder = FileOrder.Css.DefaultCss }); + } + else + { + skin.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = styleSheet, FileOrder = FileOrder.Css.DefaultCss }); + } + } + + return skin; + } + + private SkinModel LoadSkin(DnnPageController page, string skinPath) + { + SkinModel ctlSkin = null; + try + { + string skinSrc = skinPath; + if (skinPath.IndexOf(Globals.ApplicationPath, StringComparison.OrdinalIgnoreCase) != -1) + { + skinPath = skinPath.Remove(0, Globals.ApplicationPath.Length); + } + + /* + ctlSkin = ControlUtilities.LoadControl(page, skinPath); + */ + ctlSkin = new SkinModel(); + + ctlSkin.SkinSrc = skinSrc; + + // call databind so that any server logic in the skin is executed + /* + ctlSkin.DataBind(); + */ + + // Load the Panes + this.LoadPanes(page.PortalSettings); + + // Load the Module Control(s) + bool success = Globals.IsAdminControl() ? this.ProcessSlaveModule(page.PortalSettings, ctlSkin) : this.ProcessMasterModules(page.PortalSettings, ctlSkin); + /* + this.ProcessMasterModules(); + */ + + // Load the Control Panel + this.InjectControlPanel(ctlSkin, page.Request); + + /* + // Register any error messages on the Skin + if (this.Request.QueryString["error"] != null && Host.ShowCriticalErrors) + { + AddPageMessage(this, Localization.GetString("CriticalError.Error"), " ", ModuleMessage.ModuleMessageType.RedError); + + if (UserController.Instance.GetCurrentUserInfo().IsSuperUser) + { + ServicesFramework.Instance.RequestAjaxScriptSupport(); + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + + JavaScript.RequestRegistration(CommonJs.jQueryUI); + JavaScript.RegisterClientReference(this.Page, ClientAPI.ClientNamespaceReferences.dnn_dom); + ClientResourceManager.RegisterScript(this.Page, "~/resources/shared/scripts/dnn.logViewer.js"); + } + } + */ + if (!TabPermissionController.CanAdminPage() && !success) + { + // only display the warning to non-administrators (administrators will see the errors) + this.AddPageMessage(ctlSkin, Localization.GetString("ModuleLoadWarning.Error"), string.Format(Localization.GetString("ModuleLoadWarning.Text"), page.PortalSettings.Email), ModuleMessage.ModuleMessageType.YellowWarning); + } + + /* + this.InvokeSkinEvents(SkinEventType.OnSkinInit); + + if (HttpContext.Current != null && HttpContext.Current.Items.Contains(OnInitMessage)) + { + var messageType = ModuleMessage.ModuleMessageType.YellowWarning; + if (HttpContext.Current.Items.Contains(OnInitMessageType)) + { + messageType = (ModuleMessage.ModuleMessageType)Enum.Parse(typeof(ModuleMessage.ModuleMessageType), HttpContext.Current.Items[OnInitMessageType].ToString(), true); + } + + AddPageMessage(this, string.Empty, HttpContext.Current.Items[OnInitMessage].ToString(), messageType); + + JavaScript.RequestRegistration(CommonJs.DnnPlugins); + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + } + */ + + // Process the Panes attributes + foreach (var key in ctlSkin.Panes.Keys) + { + ctlSkin.Panes[key] = this.paneModelFactory.ProcessPane(ctlSkin.Panes[key]); + } + + // this.InvokeSkinEvents(SkinEventType.OnSkinPreRender); + var isSpecialPageMode = UrlUtils.InPopUp() || page.Request.QueryString["dnnprintmode"] == "true"; + if (TabPermissionController.CanAddContentToPage() && Globals.IsEditMode() && !isSpecialPageMode) + { + // Register Drag and Drop plugin + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); + + // MvcClientResourceManager.RegisterStyleSheet(page.ControllerContext, "~/resources/shared/stylesheets/dnn.dragDrop.css", FileOrder.Css.FeatureCss); + ctlSkin.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = "~/resources/shared/stylesheets/dnn.dragDrop.css", FileOrder = FileOrder.Css.FeatureCss }); + + // MvcClientResourceManager.RegisterScript(page.ControllerContext, "~/resources/shared/scripts/dnn.dragDrop.js"); + ctlSkin.RegisteredScripts.Add("~/resources/shared/scripts/dnn.dragDrop.js"); + + // Register Client Script + var sb = new StringBuilder(); + sb.AppendLine(" (function ($) {"); + sb.AppendLine(" $(document).ready(function () {"); + sb.AppendLine(" $('.dnnSortable').dnnModuleDragDrop({"); + sb.AppendLine(" tabId: " + page.PortalSettings.ActiveTab.TabID + ","); + sb.AppendLine(" draggingHintText: '" + Localization.GetSafeJSString("DraggingHintText", Localization.GlobalResourceFile) + "',"); + sb.AppendLine(" dragHintText: '" + Localization.GetSafeJSString("DragModuleHint", Localization.GlobalResourceFile) + "',"); + sb.AppendLine(" dropHintText: '" + Localization.GetSafeJSString("DropModuleHint", Localization.GlobalResourceFile) + "',"); + sb.AppendLine(" dropTargetText: '" + Localization.GetSafeJSString("DropModuleTarget", Localization.GlobalResourceFile) + "'"); + sb.AppendLine(" });"); + sb.AppendLine(" });"); + sb.AppendLine(" } (jQuery));"); + + var script = sb.ToString(); + /* + if (ScriptManager.GetCurrent(this.Page) != null) + { + // respect MS AJAX + ScriptManager.RegisterStartupScript(this.Page, this.GetType(), "DragAndDrop", script, true); + } + else + { + this.Page.ClientScript.RegisterStartupScript(this.GetType(), "DragAndDrop", script, true); + } + */ + MvcClientAPI.RegisterStartupScript("DragAndDrop", script); + } + } + catch (MvcPageException mvcExc) + { + throw mvcExc; + } + catch (Exception exc) + { + // could not load user control + var lex = new PageLoadException("Unhandled error loading page.", exc); + if (TabPermissionController.CanAdminPage()) + { + // only display the error to administrators + /* + var skinError = (Label)page.FindControl("SkinError"); + skinError.Text = string.Format(Localization.GetString("SkinLoadError", Localization.GlobalResourceFile), skinPath, page.Server.HtmlEncode(exc.Message)); + skinError.Visible = true; + */ + ctlSkin.SkinError = string.Format(Localization.GetString("SkinLoadError", Localization.GlobalResourceFile), skinPath, page.Server.HtmlEncode(exc.Message)); + } + + Exceptions.LogException(lex); + } + + return ctlSkin; + } + + private ModuleMessageModel GetModuleMessage(string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType, string iconSrc) + { + return new ModuleMessageModel() + { + Heading = heading, + IconImage = iconSrc, + Text = message, + IconType = moduleMessageType, + }; + } + + private void LoadPanes(PortalSettings portalSettings) + { + portalSettings.ActiveTab.Panes.Add("HeaderPane"); + portalSettings.ActiveTab.Panes.Add("ContentPane"); + portalSettings.ActiveTab.Panes.Add("ContentPaneLower"); + /* + // iterate page controls + foreach (Control ctlControl in this.Controls) + { + var objPaneControl = ctlControl as HtmlContainerControl; + + // Panes must be runat=server controls so they have to have an ID + if (objPaneControl != null && !string.IsNullOrEmpty(objPaneControl.ID)) + { + // load the skin panes + switch (objPaneControl.TagName.ToLowerInvariant()) + { + case "td": + case "div": + case "span": + case "p": + case "section": + case "header": + case "footer": + case "main": + case "article": + case "aside": + // content pane + if (!objPaneControl.ID.Equals("controlpanel", StringComparison.InvariantCultureIgnoreCase)) + { + // Add to the PortalSettings (for use in the Control Panel) + portalSettings.ActiveTab.Panes.Add(objPaneControl.ID); + + // Add to the Panes collection + this.Panes.Add(objPaneControl.ID.ToLowerInvariant(), new Pane(objPaneControl)); + } + else + { + // Control Panel pane + this.controlPanel = objPaneControl; + } + + break; + } + } + } + */ + } + + private bool ProcessMasterModules(PortalSettings portalSettings, SkinModel skin) + { + bool success = true; + if (TabPermissionController.CanViewPage()) + { + // We need to ensure that Content Item exists since in old versions Content Items are not needed for tabs + this.EnsureContentItemForTab(portalSettings.ActiveTab); + + // Versioning checks. + if (!TabController.CurrentPage.HasAVisibleVersion) + { + this.HandleAccesDenied(true); + } + + int urlVersion; + if (TabVersionUtils.TryGetUrlVersion(out urlVersion)) + { + if (!TabVersionUtils.CanSeeVersionedPages()) + { + this.HandleAccesDenied(false); + return true; + } + + if (TabVersionController.Instance.GetTabVersions(TabController.CurrentPage.TabID).All(tabVersion => tabVersion.Version != urlVersion)) + { + throw new NotFoundException("ErrorPage404", this.navigationManager.NavigateURL(portalSettings.ErrorPage404, string.Empty, "status=404")); + /* + this.Response.Redirect(this.NavigationManager.NavigateURL(portalSettings.ErrorPage404, string.Empty, "status=404")); + */ + } + } + + // check portal expiry date + if (!this.CheckExpired(portalSettings)) + { + if ((portalSettings.ActiveTab.StartDate < DateTime.Now && portalSettings.ActiveTab.EndDate > DateTime.Now) || TabPermissionController.CanAdminPage() || Globals.IsLayoutMode()) + { + foreach (var objModule in PortalSettingsController.Instance().GetTabModules(portalSettings)) + { + success = this.ProcessModule(portalSettings, skin, objModule); + } + } + else + { + this.HandleAccesDenied(false); + } + } + else + { + /* + AddPageMessage( + this, + string.Empty, + string.Format(Localization.GetString("ContractExpired.Error"), portalSettings.PortalName, Globals.GetMediumDate(portalSettings.ExpiryDate.ToString(CultureInfo.InvariantCulture)), portalSettings.Email), + ModuleMessage.ModuleMessageType.RedError); + */ + } + } + else + { + // If request localized page which haven't complete translate yet, redirect to default language version. + var redirectUrl = Globals.AccessDeniedURL(Localization.GetString("TabAccess.Error")); + + // Current locale will use default if did'nt find any + Locale currentLocale = LocaleController.Instance.GetCurrentLocale(portalSettings.PortalId); + if (portalSettings.ContentLocalizationEnabled && + TabController.CurrentPage.CultureCode != currentLocale.Code) + { + redirectUrl = new LanguageTokenReplace { Language = currentLocale.Code }.ReplaceEnvironmentTokens("[URL]"); + } + + throw new AccesDeniedException("TabAccess.Error", redirectUrl); + /* + this.Response.Redirect(redirectUrl, true); + */ + } + + return success; + } + + private bool ProcessSlaveModule(PortalSettings portalSettings, SkinModel skin) + { + var success = true; + var key = UIUtilities.GetControlKey(); + var moduleId = UIUtilities.GetModuleId(key); + var slaveModule = UIUtilities.GetSlaveModule(moduleId, key, portalSettings.ActiveTab.TabID); + + PaneModel pane; + skin.Panes.TryGetValue(Globals.glbDefaultPane.ToLowerInvariant(), out pane); + if (pane == null) + { + skin.Panes.Add(Globals.glbDefaultPane.ToLowerInvariant(), this.paneModelFactory.CreatePane(Globals.glbDefaultPane.ToLowerInvariant())); + skin.Panes.TryGetValue(Globals.glbDefaultPane.ToLowerInvariant(), out pane); + } + + slaveModule.PaneName = Globals.glbDefaultPane; + slaveModule.ContainerSrc = portalSettings.ActiveTab.ContainerSrc; + if (string.IsNullOrEmpty(slaveModule.ContainerSrc)) + { + slaveModule.ContainerSrc = portalSettings.DefaultPortalContainer; + } + + slaveModule.ContainerSrc = SkinController.FormatSkinSrc(slaveModule.ContainerSrc, portalSettings); + slaveModule.ContainerPath = SkinController.FormatSkinPath(slaveModule.ContainerSrc); + + var moduleControl = ModuleControlController.GetModuleControlByControlKey(key, slaveModule.ModuleDefID); + if (moduleControl != null) + { + slaveModule.ModuleControlId = moduleControl.ModuleControlID; + slaveModule.IconFile = moduleControl.IconFile; + + string permissionKey; + switch (slaveModule.ModuleControl.ControlSrc) + { + case "Admin/Modules/ModuleSettings.ascx": + permissionKey = "MANAGE"; + break; + case "Admin/Modules/Import.ascx": + permissionKey = "IMPORT"; + break; + case "Admin/Modules/Export.ascx": + permissionKey = "EXPORT"; + break; + default: + permissionKey = "CONTENT"; + break; + } + + if (ModulePermissionController.HasModuleAccess(slaveModule.ModuleControl.ControlType, permissionKey, slaveModule)) + { + success = this.InjectModule(portalSettings, pane, slaveModule); + } + else + { + throw new AccesDeniedException("AccesDenied", Globals.AccessDeniedURL(Localization.GetString("ModuleAccess.Error"))); + /* + this.Response.Redirect(Globals.AccessDeniedURL(Localization.GetString("ModuleAccess.Error")), true); + */ + } + } + + return success; + } + + private bool ProcessModule(PortalSettings portalSettings, SkinModel skin, ModuleInfo module) + { + var success = true; + var x = Globals.GetCurrentServiceProvider().GetService(); + if (x.CanInjectModule(module, portalSettings)) + { + // We need to ensure that Content Item exists since in old versions Content Items are not needed for modules + this.EnsureContentItemForModule(module); + + PaneModel pane = this.GetPane(skin, module); + + if (pane != null) + { + success = this.InjectModule(portalSettings, pane, module); + } + else + { + var lex = new ModuleLoadException(Localization.GetString("PaneNotFound.Error")); + + // this.Controls.Add(new ErrorContainer(portalSettings, MODULELOAD_ERROR, lex).Container); + Exceptions.LogException(lex); + } + } + + return success; + } + + private PaneModel GetPane(SkinModel skin, ModuleInfo module) + { + PaneModel pane; + bool found = skin.Panes.TryGetValue(module.PaneName.ToLowerInvariant(), out pane); + + if (!found) + { + // this.Panes.TryGetValue(Globals.glbDefaultPane.ToLowerInvariant(), out pane); + skin.Panes.Add(module.PaneName.ToLowerInvariant(), this.paneModelFactory.CreatePane(module.PaneName.ToLowerInvariant())); + found = skin.Panes.TryGetValue(module.PaneName.ToLowerInvariant(), out pane); + } + + return pane; + } + + private void HandleAccesDenied(bool v) + { + throw new NotImplementedException(); + } + + private bool CheckExpired(PortalSettings portalSettings) + { + bool blnExpired = false; + if (portalSettings.ExpiryDate != Null.NullDate) + { + if (Convert.ToDateTime(portalSettings.ExpiryDate) < DateTime.Now && !Globals.IsHostTab(portalSettings.ActiveTab.TabID)) + { + blnExpired = true; + } + } + + return blnExpired; + } + + private void EnsureContentItemForTab(Entities.Tabs.TabInfo tabInfo) + { + // If tab exists but ContentItem not, then we create it + if (tabInfo.ContentItemId == Null.NullInteger && tabInfo.TabID != Null.NullInteger) + { + TabController.Instance.CreateContentItem(tabInfo); + TabController.Instance.UpdateTab(tabInfo); + } + } + + private void EnsureContentItemForModule(ModuleInfo module) + { + // If module exists but ContentItem not, then we create it + if (module.ContentItemId == Null.NullInteger && module.ModuleID != Null.NullInteger) + { + ModuleController.Instance.CreateContentItem(module); + ModuleController.Instance.UpdateModule(module); + } + } + + private void InjectControlPanel(SkinModel skin, HttpRequestBase request) + { + // if querystring dnnprintmode=true, controlpanel will not be shown + if (request.QueryString["dnnprintmode"] != "true" && !UrlUtils.InPopUp() && request.QueryString["hidecommandbar"] != "true") + { + // if (Host.AllowControlPanelToDetermineVisibility || (ControlPanelBase.IsPageAdminInternal() || ControlPanelBase.IsModuleAdminInternal())) + if (ControlPanelBase.IsPageAdminInternal() || ControlPanelBase.IsModuleAdminInternal()) + { + // ControlPanel processing + skin.ControlPanelRazor = Path.GetFileNameWithoutExtension(Host.ControlPanel); + + /* + var controlPanel = ControlUtilities.LoadControl(this, Host.ControlPanel); + var form = (HtmlForm)this.Parent.FindControl("Form"); + + if (controlPanel.IncludeInControlHierarchy) + { + // inject ControlPanel control into skin + if (this.ControlPanel == null || HostController.Instance.GetBoolean("IgnoreControlPanelWrapper", false)) + { + if (form != null) + { + form.Controls.AddAt(0, controlPanel); + } + else + { + this.Page.Controls.AddAt(0, controlPanel); + } + } + else + { + this.ControlPanel.Controls.Add(controlPanel); + } + + // register admin.css + ClientResourceManager.RegisterAdminStylesheet(this.Page, Globals.HostPath + "admin.css"); + } + */ + } + } + } + + private void AddPageMessage(SkinModel skin, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType) + { + this.AddPageMessage(skin, heading, message, moduleMessageType, Null.NullString); + } + + private void AddPageMessage(SkinModel skin, string heading, string message, ModuleMessage.ModuleMessageType moduleMessageType, string iconSrc) + { + if (!string.IsNullOrEmpty(message)) + { + ModuleMessageModel moduleMessage = this.GetModuleMessage(heading, message, moduleMessageType, iconSrc); + skin.ModuleMessages.Insert(0, moduleMessage); + } + } + + private bool InjectModule(PortalSettings portalSettings, PaneModel pane, ModuleInfo module) + { + bool bSuccess = true; + + // try to inject the module into the pane + try + { + if (portalSettings.ActiveTab.TabID == portalSettings.UserTabId || portalSettings.ActiveTab.ParentId == portalSettings.UserTabId) + { + /* + var profileModule = this.ModuleControlPipeline.LoadModuleControl(this.Page, module) as IProfileModule; + if (profileModule == null || profileModule.DisplayModule) + { + pane.InjectModule(module); + } + */ + } + else + { + this.paneModelFactory.InjectModule(pane, module, portalSettings); + } + } + catch (ThreadAbortException) + { + // Response.Redirect may called in module control's OnInit method, so it will cause ThreadAbortException, no need any action here. + } + catch (Exception ex) + { + Exceptions.LogException(ex); + bSuccess = false; + } + + return bSuccess; + } + + private IFileInfo GetPageStylesheetFileInfo(string styleSheet, int portalId) + { + string cacheKey = string.Format(Common.Utilities.DataCache.PortalCacheKey, portalId, "PageStylesheet" + styleSheet); + var file = CBO.GetCachedObject( + new CacheItemArgs(cacheKey, Common.Utilities.DataCache.PortalCacheTimeOut, Common.Utilities.DataCache.PortalCachePriority, styleSheet, portalId), + this.GetPageStylesheetInfoCallBack); + + return file; + } + + private IFileInfo GetPageStylesheetInfoCallBack(CacheItemArgs itemArgs) + { + var styleSheet = itemArgs.Params[0].ToString(); + return FileManager.Instance.GetFile((int)itemArgs.Params[1], styleSheet); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs new file mode 100644 index 00000000000..6180b98f980 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs @@ -0,0 +1,123 @@ +// 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.Web.MvcPipeline +{ + using System; + using System.IO; + using System.Web; + using System.Web.Helpers; + using System.Web.Mvc; + using System.Web.Mvc.Html; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + + // using DotNetNuke.Framework.Models; + using DotNetNuke.Mvc; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class HtmlHelpers + { + public static IHtmlString ViewComponent(this HtmlHelper htmlHelper, string controllerName, object model) + { + return htmlHelper.Action("Invoke", controllerName, model); + } + + public static IHtmlString Control(this HtmlHelper htmlHelper, string controlSrc, object model) + { + try + { + return htmlHelper.Action( + "Invoke", + MvcUtils.GetControlControllerName(controlSrc), + model); + } + catch (Exception ex) + { + throw new Exception($"{ex.Message} - {MvcUtils.GetControlControllerName(controlSrc)} - Invoke", ex); + } + } + + public static IHtmlString Control(this HtmlHelper htmlHelper, string controlSrc, ModuleInfo module) + { + try + { + return htmlHelper.Action( + "Invoke", + MvcUtils.GetControlControllerName(controlSrc), + new ControlViewModel() + { + ModuleId = module.ModuleID, + TabId = module.TabID, + ModuleControlId = module.ModuleControlId, + PanaName = module.PaneName, + ContainerSrc = module.ContainerSrc, + ContainerPath = module.ContainerPath, + IconFile = module.IconFile, + }); + } + catch (Exception ex) + { + throw new Exception($"{ex.Message} - {MvcUtils.GetControlControllerName(controlSrc)} - Invoke", ex); + } + } + + public static IHtmlString Control(this HtmlHelper htmlHelper, ModuleInfo module) + { + try + { + return htmlHelper.Action( + "Invoke", + MvcUtils.GetControlControllerName(module.ModuleControl.ControlSrc), + new ControlViewModel() + { + ModuleId = module.ModuleID, + TabId = module.TabID, + ModuleControlId = module.ModuleControlId, + PanaName = module.PaneName, + ContainerSrc = module.ContainerSrc, + ContainerPath = module.ContainerPath, + IconFile = module.IconFile, + }); + } + catch (Exception ex) + { + throw new Exception($"{ex.Message} - {MvcUtils.GetControlControllerName(module.ModuleControl.ControlSrc)} - Invoke", ex); + } + } + + public static IHtmlString CspNonce(this HtmlHelper htmlHelper) + { + return new MvcHtmlString(htmlHelper.ViewContext.HttpContext.Items["CSP-NONCE"].ToString()); + } + + public static IHtmlString RegisterAjaxScriptIfRequired(this HtmlHelper htmlHelper) + { + if (ServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) + { + ServicesFrameworkInternal.Instance.RegisterAjaxScript(htmlHelper.ViewContext.Controller.ControllerContext); + } + + return new MvcHtmlString(string.Empty); + } + + public static IHtmlString AntiForgeryIfRequired(this HtmlHelper htmlHelper) + { + // ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); // add also jquery + if (ServicesFrameworkInternal.Instance.IsAjaxAntiForgerySupportRequired) + { + // var antiForgery = AntiForgery.GetHtml().ToHtmlString(); + return AntiForgery.GetHtml(); + } + + return new MvcHtmlString(string.Empty); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs new file mode 100644 index 00000000000..d39fcabb9c1 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs @@ -0,0 +1,139 @@ +// 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.Web.MvcPipeline.Models +{ + using System.Collections.Generic; + using System.IO; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.UI.Modules; + + public class ContainerModel + { + private ModuleInfo moduleConfiguration; + private ModuleHostModel moduleHost; + + public ContainerModel(ModuleInfo moduleConfiguration) + { + this.moduleConfiguration = moduleConfiguration; + this.moduleHost = new ModuleHostModel(moduleConfiguration); + } + + public ModuleHostModel ModuleHost + { + get + { + return this.moduleHost; + } + } + + public IModuleControl ModuleControl + { + get + { + IModuleControl moduleControl = null; + if (this.ModuleHost != null) + { + moduleControl = this.ModuleHost.ModuleControl; + } + + return moduleControl; + } + } + + public string ID { get; internal set; } + + public string ContainerPath + { + get + { + return Path.GetDirectoryName(this.ContainerSrc) + "/"; + } + } + + public string ContainerSrc { get; internal set; } + + public string ActionName + { + get + { + if (this.moduleConfiguration.ModuleControl.ControlKey == "Module") + { + return "LoadDefaultSettings"; + } + else + { + return string.IsNullOrEmpty(this.ModuleName) ? "Index" : this.FileNameWithoutExtension; + } + } + } + + public string ControllerName + { + get + { + if (this.moduleConfiguration.ModuleControl.ControlKey == "Module") + { + return "ModuleSettings"; + } + else + { + return string.IsNullOrEmpty(this.ModuleName) ? this.FileNameWithoutExtension : this.ModuleName; + } + } + } + + public string RazorFile + { + get + { + return this.moduleConfiguration.ModuleControl.ControlSrc.Replace(".ascx", string.Empty); + } + } + + public string ContainerRazorFile + { + get + { + return "~" + Path.GetDirectoryName(this.ContainerSrc) + "/Views/" + Path.GetFileName(this.ContainerSrc).Replace(".ascx", ".cshtml"); + } + } + + public ModuleInfo ModuleConfiguration + { + get + { + return this.moduleConfiguration; + } + } + + public bool EditMode { get; internal set; } + + public string Footer { get; internal set; } + + public string Header { get; internal set; } + + public string ContentPaneCssClass { get; internal set; } + + public string ContentPaneStyle { get; internal set; } + + public List RegisteredStylesheets { get; set; } = new List(); + + private string ModuleName + { + get + { + return this.moduleConfiguration.DesktopModule.ModuleName; + } + } + + private string FileNameWithoutExtension + { + get + { + return Path.GetFileNameWithoutExtension(this.moduleConfiguration.ModuleControl.ControlSrc); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ControlViewModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ControlViewModel.cs new file mode 100644 index 00000000000..471571b6589 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ControlViewModel.cs @@ -0,0 +1,22 @@ +// 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.Web.MvcPipeline.Models +{ + public class ControlViewModel + { + public int ModuleId { get; set; } + + public int TabId { get; set; } + + public int ModuleControlId { get; set; } + + public string PanaName { get; set; } + + public string ContainerSrc { get; set; } + + public string ContainerPath { get; set; } + + public string IconFile { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleHostModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleHostModel.cs new file mode 100644 index 00000000000..5f2245dcbe2 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleHostModel.cs @@ -0,0 +1,85 @@ +// 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.Web.MvcPipeline.Models +{ + using System.Text.RegularExpressions; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Instrumentation; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI.Modules; + + /// Project : DotNetNuke + /// Namespace: DotNetNuke.UI.Modules + /// Class : ModuleHost + /// ModuleHost hosts a Module Control (or its cached Content). + public sealed class ModuleHostModel + { + private const string DefaultCssProvider = "DnnPageHeaderProvider"; + private const string DefaultJsProvider = "DnnBodyProvider"; + + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(ModuleHostModel)); + + private static readonly Regex CdfMatchRegex = new Regex( + @"<\!--CDF\((?JAVASCRIPT|CSS|JS-LIBRARY)\|(?.+?)(\|(?.+?)\|(?\d+?))?\)-->", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private readonly ModuleInfo moduleConfiguration; + + private IModuleControl control = null; + + public ModuleHostModel(ModuleInfo moduleConfiguration) + { + this.moduleConfiguration = moduleConfiguration; + if (Host.EnableCustomModuleCssClass) + { + string moduleName = this.moduleConfiguration.DesktopModule.ModuleName; + if (moduleName != null) + { + moduleName = Globals.CleanName(moduleName); + } + + this.CssClass = string.Format("DNNModuleContent Mod{0}C", moduleName); + } + } + + /// Gets the attached ModuleControl. + /// An IModuleControl. + public IModuleControl ModuleControl + { + get + { + // Make sure the Control tree has been created + // this.EnsureChildControls(); + return this.control as IModuleControl; + } + } + + public string CssClass { get; private set; } + + /// Gets a flag that indicates whether the Module is in View Mode. + /// A Boolean. + internal static bool IsViewMode(ModuleInfo moduleInfo, PortalSettings settings) + { + bool viewMode; + + if (ModulePermissionController.HasModuleAccess(SecurityAccessLevel.ViewPermissions, Null.NullString, moduleInfo)) + { + viewMode = false; + } + else + { + viewMode = !ModulePermissionController.HasModuleAccess(SecurityAccessLevel.Edit, Null.NullString, moduleInfo); + } + + return viewMode || Personalization.GetUserMode() == PortalSettings.Mode.View; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleMessageModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleMessageModel.cs new file mode 100644 index 00000000000..dd0214cc2f4 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleMessageModel.cs @@ -0,0 +1,19 @@ +// 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.Web.MvcPipeline.Models +{ + using static DotNetNuke.UI.Skins.Controls.ModuleMessage; + + public class ModuleMessageModel + { + public string Text { get; set; } + + public string Heading { get; set; } + + public ModuleMessageType IconType { get; set; } + + public string IconImage { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleModelBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleModelBase.cs new file mode 100644 index 00000000000..5cb289baa55 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleModelBase.cs @@ -0,0 +1,14 @@ +// 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.Web.MvcPipeline.Models +{ + public class ModuleModelBase + { + public int ModuleId { get; set; } + + public int TabId { get; set; } + + public string LocalResourceFile { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs new file mode 100644 index 00000000000..ef3f5df6dca --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs @@ -0,0 +1,145 @@ +// 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.Web.MvcPipeline.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Web.Mvc; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Mobile; + + public class ModuleSettingsModel + { + [Display(Name = "plModuleId")] + public int ModuleId { get; set; } + + [Required] + [Display(Name = "plTitle")] + public string ModuleTitle { get; set; } + + [Display(Name = "plIcon")] + public string IconFile { get; set; } + + [Display(Name = "plAllTabs")] + public bool AllTabs { get; set; } + + [Display(Name = "AllowIndexLabel")] + public bool AllowIndex { get; set; } + + [Display(Name = "plMoniker")] + public string Moniker { get; set; } + + [Display(Name = "plVisibility")] + public VisibilityState Visibility { get; set; } + + [Display(Name = "plAdminBorder")] + public bool HideAdminBorder { get; set; } + + [Display(Name = "plCacheTime")] + public int CacheTime { get; set; } + + [Display(Name = "CacheProvider")] + public string CacheProvider { get; set; } + + [Display(Name = "plAlign")] + public string Alignment { get; set; } + + [Display(Name = "plColor")] + public string Color { get; set; } + + [Display(Name = "plBorder")] + public string Border { get; set; } + + [Display(Name = "plHeader")] + public string Header { get; set; } + + [Display(Name = "plFooter")] + public string Footer { get; set; } + + [Display(Name = "plStartDate")] + public DateTime? StartDate { get; set; } + + [Display(Name = "plEndDate")] + public DateTime? EndDate { get; set; } + + [Display(Name = "plModuleContainer")] + public string ContainerSrc { get; set; } + + public List ModulePermissions { get; set; } + + [Display(Name = "plDisplayTitle")] + public bool DisplayTitle { get; set; } + + [Display(Name = "plDisplayPrint")] + public bool DisplayPrint { get; set; } + + [Display(Name = "plDisplaySyndicate")] + public bool DisplaySyndicate { get; set; } + + [Display(Name = "plDefault")] + public bool IsDefaultModule { get; set; } + + [Display(Name = "plAllModules")] + public bool AllModules { get; set; } + + [Display(Name = "cultureLabel")] + public string CultureCode { get; set; } + + [Display(Name = "plFriendlyName")] + public string FriendlyName { get; set; } + + [Display(Name = "plTags")] + public string Tags { get; set; } + + [Display(Name = "plNewTabs")] + public bool NewTabs { get; set; } + + [Display(Name = "isShareableLabel")] + public bool IsShareable { get; set; } + + [Display(Name = "isShareableViewOnlyLabel")] + public bool IsShareableViewOnly { get; set; } + + [Display(Name = "plAdminBorder")] + public bool AdminBorder { get; set; } + + [Display(Name = "plTab")] + public int TabId { get; set; } + + public IEnumerable AvailableTabs { get; set; } + + [Display(Name = "InheritPermissions")] + public bool InheritViewPermissions { get; set; } + + public bool CacheWarningVisible { get; set; } + + public bool CacheInheritedVisible { get; set; } + + public bool IsShareableVisible { get; set; } + + public IEnumerable ContainerOptions { get; set; } + + public string ModuleContainerCombo { get; set; } + + public string CmdDelete { get; set; } + + public string IsShareableCheckBox { get; set; } + + public IEnumerable InstalledOnTabs { get; set; } + + public string ReturnUrl { get; internal set; } + + public string ModuleControllerName { get; set; } + + public string ModuleActionName { get; set; } + + public string ModuleLocalResourceFile { get; internal set; } + + public string ModuleControlSrc { get; internal set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsMvcViewModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsMvcViewModel.cs new file mode 100644 index 00000000000..f243261ee81 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsMvcViewModel.cs @@ -0,0 +1,144 @@ +// 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.Web.MvcPipeline.Models +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Linq; + using System.Web.Mvc; + + using DotNetNuke.Entities.Content.Taxonomy; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.ModuleCache; + using DotNetNuke.UI.WebControls; + + public class ModuleSettingsMvcViewModel + { + public ModuleSettingsMvcViewModel() + { + this.AvailableTabs = new List(); + this.Permissions = new List(); + this.Terms = new List(); + this.AvailableCacheProviders = new List(); + } + + public int ModuleId { get; set; } + + public string ModuleTitle { get; set; } + + public string Alignment { get; set; } + + public bool AllTabs { get; set; } + + public bool NewTabs { get; set; } + + public bool AllowIndex { get; set; } + + public bool IsShareable { get; set; } + + public bool IsShareableViewOnly { get; set; } + + public bool AdminBorder { get; set; } + + public string Header { get; set; } + + public string Footer { get; set; } + + public DateTime? StartDate { get; set; } + + public DateTime? EndDate { get; set; } + + public string Moniker { get; set; } + + public string CacheProvider { get; set; } + + public int CacheDuration { get; set; } + + public string Color { get; set; } + + public string Border { get; set; } + + public string IconFile { get; set; } + + public VisibilityState Visibility { get; set; } + + public bool DisplayTitle { get; set; } + + public bool DisplayPrint { get; set; } + + public bool DisplaySyndicate { get; set; } + + public string ContainerSrc { get; set; } + + public bool IsDefaultModule { get; set; } + + public bool AllModules { get; set; } + + public int TabId { get; set; } + + public List AvailableTabs { get; set; } + + public List Permissions { get; set; } + + public List Terms { get; set; } + + public bool InheritViewPermissions { get; set; } + + public bool AllTabsChanged { get; set; } + + public List AvailableCacheProviders { get; set; } + + public void LoadSettings(ModuleInfo module) + { + this.ModuleId = module.ModuleID; + this.ModuleTitle = module.ModuleTitle; + this.Alignment = module.Alignment; + this.AllTabs = module.AllTabs; + this.Color = module.Color; + this.Border = module.Border; + this.IconFile = module.IconFile; + this.CacheDuration = module.CacheTime; + this.CacheProvider = module.CacheMethod; + this.TabId = module.TabID; + this.Visibility = module.Visibility; + this.Header = module.Header; + this.Footer = module.Footer; + this.StartDate = module.StartDate; + this.EndDate = module.EndDate; + this.ContainerSrc = module.ContainerSrc; + this.DisplayTitle = module.DisplayTitle; + this.DisplayPrint = module.DisplayPrint; + this.DisplaySyndicate = module.DisplaySyndicate; + this.IsDefaultModule = module.IsDefaultModule; + this.AllModules = module.AllModules; + + this.AllowIndex = bool.Parse(module.ModuleSettings["AllowIndex"] == null ? "true" : module.ModuleSettings["AllowIndex"].ToString()); + this.AdminBorder = bool.Parse(module.ModuleSettings["hideadminborder"] == null ? "false" : module.ModuleSettings["hideadminborder"].ToString()); + this.Moniker = module.ModuleSettings["Moniker"] as string ?? string.Empty; + + if (!module.IsShared) + { + this.InheritViewPermissions = module.InheritViewPermissions; + this.IsShareable = module.IsShareable; + this.IsShareableViewOnly = module.IsShareableViewOnly; + } + + this.Permissions = new List(module.ModulePermissions.Cast()); + this.Terms = new List(module.Terms); + + // Laad beschikbare cache providers + var cacheProviders = ModuleCachingProvider.GetProviderList(); + this.AvailableCacheProviders = cacheProviders.Select(p => new SelectListItem + { + Text = p.Key.Replace("ModuleCachingProvider", string.Empty), + Value = p.Key, + }).ToList(); + this.AvailableCacheProviders.Insert(0, new SelectListItem { Text = "None Specified", Value = string.Empty }); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PageModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PageModel.cs new file mode 100644 index 00000000000..47720e025c8 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PageModel.cs @@ -0,0 +1,57 @@ +// 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.Web.MvcPipeline.Models +{ + using System.Collections.Generic; + + using DotNetNuke.Abstractions; + using DotNetNuke.ContentSecurityPolicy; + + public class PageModel + { + public int? TabId { get; set; } + + public string Language { get; set; } + + public int? PortalId { get; set; } + + public SkinModel Skin { get; set; } + + public string AntiForgery { get; set; } + + public Dictionary ClientVariables { get; set; } + + public string PageHeadText { get; set; } + + public string PortalHeadText { get; set; } + + public string Title { get; set; } + + public string BackgroundUrl { get; set; } + + public string MetaRefresh { get; set; } + + public string Description { get; set; } + + public string KeyWords { get; set; } + + public string Copyright { get; set; } + + public string Generator { get; set; } + + public string MetaRobots { get; set; } + + public Dictionary StartupScripts { get; set; } + + public bool IsEditMode { get; set; } + + public string FavIconLink { get; set; } + + public string CanonicalLinkUrl { get; set; } + + public IContentSecurityPolicy ContentSecurityPolicy { get; set; } + + public INavigationManager NavigationManager { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PaneModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PaneModel.cs new file mode 100644 index 00000000000..bf163a5c918 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/PaneModel.cs @@ -0,0 +1,30 @@ +// 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.Web.MvcPipeline.Models +{ + using System.Collections.Generic; + + public class PaneModel + { + private Dictionary containers; + + public PaneModel(string name) + { + this.Name = name; + } + + public string CssClass { get; set; } + + public Dictionary Containers + { + get + { + return this.containers ?? (this.containers = new Dictionary()); + } + } + + /// Gets or sets the name (ID) of the Pane. + public string Name { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/RegisteredStylesheet.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/RegisteredStylesheet.cs new file mode 100644 index 00000000000..efe89e9aaaf --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/RegisteredStylesheet.cs @@ -0,0 +1,13 @@ +// 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.Web.MvcPipeline.Models +{ + public class RegisteredStylesheet + { + public string Stylesheet { get; set; } + + public DotNetNuke.Web.Client.FileOrder.Css FileOrder { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs new file mode 100644 index 00000000000..2edff0580bf --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs @@ -0,0 +1,106 @@ +// 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.Web.MvcPipeline.Models +{ + using System.Collections.Generic; + using System.IO; + + using DotNetNuke.Common; + using DotNetNuke.Web.MvcPipeline.Controllers; + + public class SkinModel + { + private Dictionary panes; + /* + private PageModel pageModel; + + public SkinModel(DnnPageController page, PageModel pageModel) + { + this.Page = page; + this.pageModel = pageModel; + this.PageMessages = new List(); + this.ModuleMessages = new List(); + this.NavigationManager = this.pageModel.NavigationManager; + } + */ + /* + public DnnPageController Page { get; private set; } + */ + + public string SkinSrc { get; set; } + + /* + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + */ + + public Dictionary Panes + { + get + { + return this.panes ?? (this.panes = new Dictionary()); + } + } + + public string RazorFile + { + get + { + return Path.GetDirectoryName(this.SkinSrc).Replace("\\", "/") + "/Views/" + Path.GetFileName(this.SkinSrc).Replace(".ascx", ".cshtml"); + } + } + + public string SkinPath + { + get + { + return Path.GetDirectoryName(this.SkinSrc).Replace("\\", "/") + "/"; + } + } + + public string ControlPanelRazor { get; set; } + + public string PaneCssClass + { + get + { + if (Globals.IsEditMode()) + { + return "dnnSortable"; + } + + return string.Empty; + } + } + + public string BodyCssClass + { + get + { + if (Globals.IsEditMode()) + { + return "dnnEditState"; + } + + return string.Empty; + } + } + + public string SkinError { get; set; } + + public List PageMessages { get; private set; } + + public List ModuleMessages { get; private set; } + + public List RegisteredStylesheets { get; set; } = new List(); + + public List RegisteredScripts { get; set; } = new List(); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/TabModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/TabModel.cs new file mode 100644 index 00000000000..34a83c84e5b --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/TabModel.cs @@ -0,0 +1,16 @@ +// 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.Web.MvcPipeline.Models +{ + public class TabModel + { + public string Name { get; set; } + + public int Id { get; set; } + + public string InstalledOnSite { get; set; } + + public string InstalledOnLink { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/ModuleControl/ModuleControlBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/ModuleControl/ModuleControlBase.cs new file mode 100644 index 00000000000..830315f9d63 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/ModuleControl/ModuleControlBase.cs @@ -0,0 +1,440 @@ +// 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.Web.MvcPipeline.ModuleControl +{ + using System; + using System.Collections; + using System.IO; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Instrumentation; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Modules; + + public class ModuleControlBase : IModuleControl, IDisposable + { + /* + protected static readonly Regex FileInfoRegex = new Regex( + @"\.([a-z]{2,3}\-[0-9A-Z]{2,4}(-[A-Z]{2})?)(\.(Host|Portal-\d+))?\.resx$", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + TimeSpan.FromSeconds(1)); + */ + + private readonly ILog tracelLogger = LoggerSource.Instance.GetLogger("DNN.Trace"); + private readonly Lazy serviceScopeContainer = new Lazy(ServiceScopeContainer.GetRequestOrCreateScope); + private string localResourceFile; + private ModuleInstanceContext moduleContext; + + /* + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Control ContainerControl + { + get + { + return Globals.FindControlRecursive(this, "ctr" + this.ModuleId); + } + } + */ + + public bool IsHostMenu + { + get + { + return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + } + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + /// + /// Gets a value indicating whether the EditMode property is used to determine whether the user is in the + /// Administrator role + /// Cache. + /// + public bool EditMode + { + get + { + return this.ModuleContext.EditMode; + } + } + + public bool IsEditable + { + get + { + return this.ModuleContext.IsEditable; + } + } + + public int PortalId + { + get + { + return this.ModuleContext.PortalId; + } + } + + public int TabId + { + get + { + return this.ModuleContext.TabId; + } + } + + public UserInfo UserInfo + { + get + { + return this.PortalSettings.UserInfo; + } + } + + public int UserId + { + get + { + return this.PortalSettings.UserId; + } + } + + public PortalAliasInfo PortalAlias + { + get + { + return this.PortalSettings.PortalAlias; + } + } + + public Hashtable Settings + { + get + { + return this.ModuleContext.Settings; + } + } + + /// Gets the underlying base control for this ModuleControl. + /// A String. + public Control Control + { + get + { + return null; + } + } + + public string ID { get; set; } + + /// Gets or Sets the Path for this control (used primarily for UserControls). + /// A String. + public string ControlPath { get; set; } + + /// Gets the Name for this control. + /// A String. + public string ControlName + { + get + { + return this.GetType().Name.Replace("_", "."); + } + } + + /// Gets the Module Context for this control. + /// A ModuleInstanceContext. + public ModuleInstanceContext ModuleContext + { + get + { + if (this.moduleContext == null) + { + this.moduleContext = new ModuleInstanceContext(this); + } + + return this.moduleContext; + } + } + + /* + // CONVERSION: Remove obsoleted methods (FYI some core modules use these, such as Links) + + /// + /// Gets the CacheDirectory property is used to return the location of the "Cache" + /// Directory for the Module. + /// + [Obsolete("Deprecated in DotNetNuke 7.0.0. Please use ModuleController.CacheDirectory(). Scheduled removal in v11.0.0.")] + public string CacheDirectory + { + get + { + return PortalController.Instance.GetCurrentPortalSettings().HomeDirectoryMapPath + "Cache"; + } + } + + /// + /// Gets the CacheFileName property is used to store the FileName for this Module's + /// Cache. + /// + [Obsolete("Deprecated in DotNetNuke 7.0.0. Please use ModuleController.CacheFileName(TabModuleID). Scheduled removal in v11.0.0.")] + public string CacheFileName + { + get + { + string strCacheKey = "TabModule:"; + strCacheKey += this.TabModuleId + ":"; + strCacheKey += Thread.CurrentThread.CurrentUICulture.ToString(); + return PortalController.Instance.GetCurrentPortalSettings().HomeDirectoryMapPath + "Cache" + "\\" + Globals.CleanFileName(strCacheKey) + ".resources"; + } + } + + [Obsolete("Deprecated in DotNetNuke 7.0.0. Please use ModuleController.CacheKey(TabModuleID). Scheduled removal in v11.0.0.")] + public string CacheKey + { + get + { + string strCacheKey = "TabModule:"; + strCacheKey += this.TabModuleId + ":"; + strCacheKey += Thread.CurrentThread.CurrentUICulture.ToString(); + return strCacheKey; + } + } + */ + + public ModuleActionCollection Actions + { + get + { + return this.ModuleContext.Actions; + } + + set + { + this.ModuleContext.Actions = value; + } + } + + public string HelpURL + { + get + { + return this.ModuleContext.HelpURL; + } + + set + { + this.ModuleContext.HelpURL = value; + } + } + + public ModuleInfo ModuleConfiguration + { + get + { + return this.ModuleContext.Configuration; + } + + set + { + this.ModuleContext.Configuration = value; + } + } + + public int TabModuleId + { + get + { + return this.ModuleContext.TabModuleId; + } + + set + { + this.ModuleContext.TabModuleId = value; + } + } + + public int ModuleId + { + get + { + return this.ModuleContext.ModuleId; + } + + set + { + this.ModuleContext.ModuleId = value; + } + } + + /// Gets or sets the local resource file for this control. + /// A String. + public string LocalResourceFile + { + get + { + string fileRoot; + if (string.IsNullOrEmpty(this.localResourceFile)) + { + fileRoot = Path.Combine(this.ControlPath, Localization.LocalResourceDirectory + "/" + this.ID); + } + else + { + fileRoot = this.localResourceFile; + } + + return fileRoot; + } + + set + { + this.localResourceFile = value; + } + } + + /// + /// Gets the Dependency Provider to resolve registered + /// services with the container. + /// + /// + /// The Dependency Service. + /// + protected IServiceProvider DependencyProvider => this.serviceScopeContainer.Value.ServiceScope.ServiceProvider; + + public string EditUrl() + { + return this.ModuleContext.EditUrl(); + } + + public string EditUrl(string controlKey) + { + return this.ModuleContext.EditUrl(controlKey); + } + + public string EditUrl(string keyName, string keyValue) + { + return this.ModuleContext.EditUrl(keyName, keyValue); + } + + public string EditUrl(string keyName, string keyValue, string controlKey) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey); + } + + public string EditUrl(string keyName, string keyValue, string controlKey, params string[] additionalParameters) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey, additionalParameters); + } + + public string EditUrl(int tabID, string controlKey, bool pageRedirect, params string[] additionalParameters) + { + return this.ModuleContext.NavigateUrl(tabID, controlKey, pageRedirect, additionalParameters); + } + + public int GetNextActionID() + { + return this.ModuleContext.GetNextActionID(); + } + + /// + public void Dispose() + { + // base.Dispose(); + if (this.serviceScopeContainer.IsValueCreated) + { + this.serviceScopeContainer.Value.Dispose(); + } + } + + /* + [DnnDeprecated(7, 0, 0, "Please use ModuleController.CacheFileName(TabModuleID)", RemovalVersion = 11)] + public partial string GetCacheFileName(int tabModuleId) + { + string strCacheKey = "TabModule:"; + strCacheKey += tabModuleId + ":"; + strCacheKey += Thread.CurrentThread.CurrentUICulture.ToString(); + return PortalController.Instance.GetCurrentPortalSettings().HomeDirectoryMapPath + "Cache" + "\\" + Globals.CleanFileName(strCacheKey) + ".resources"; + } + + [DnnDeprecated(7, 0, 0, "Please use ModuleController.CacheKey(TabModuleID)", RemovalVersion = 11)] + public partial string GetCacheKey(int tabModuleId) + { + string strCacheKey = "TabModule:"; + strCacheKey += tabModuleId + ":"; + strCacheKey += Thread.CurrentThread.CurrentUICulture.ToString(); + return strCacheKey; + } + + [DnnDeprecated(7, 0, 0, "Please use ModuleController.SynchronizeModule(ModuleId)", RemovalVersion = 11)] + public partial void SynchronizeModule() + { + ModuleController.SynchronizeModule(this.ModuleId); + } + */ + protected void OnInit() + { + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"PortalModuleBase.OnInit Start (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleId:{this.ModuleId}): {this.GetType()}"); + } + + // base.OnInit(e); + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"PortalModuleBase.OnInit End (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleId:{this.ModuleId}): {this.GetType()}"); + } + } + + protected void OnLoad() + { + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"PortalModuleBase.OnLoad Start (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleId:{this.ModuleId}): {this.GetType()}"); + } + + // base.OnLoad(e); + if (this.tracelLogger.IsDebugEnabled) + { + this.tracelLogger.Debug($"PortalModuleBase.OnLoad End (TabId:{this.PortalSettings.ActiveTab.TabID},ModuleId:{this.ModuleId}): {this.GetType()}"); + } + } + + /// + /// Helper method that can be used to add an ActionEventHandler to the Skin for this + /// Module Control. + /// + protected void AddActionHandler(ActionEventHandler e) + { + /* + UI.Skins.Skin parentSkin = UI.Skins.Skin.GetParentSkin(this); + if (parentSkin != null) + { + parentSkin.RegisterModuleActionEvent(this.ModuleId, e); + } + */ + } + + protected string LocalizeString(string key) + { + return Localization.GetString(key, this.LocalResourceFile); + } + + protected string LocalizeSafeJsString(string key) + { + return Localization.GetSafeJSString(key, this.LocalResourceFile); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs new file mode 100644 index 00000000000..4c887dea842 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs @@ -0,0 +1,66 @@ +// 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.Web.MvcPipeline.Modules +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.Security.Roles; + using DotNetNuke.Services.FileSystem; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client.ClientResourceManagement; + + public static partial class ModuleHelpers + { + public static IHtmlString DnnLabelFor(this HtmlHelper htmlHelper, Expression> expression, string resourceFile) + { + // HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes) + var name = htmlHelper.DisplayNameFor(expression).ToString(); + if (string.IsNullOrEmpty(name)) + { + name = htmlHelper.NameFor(expression).ToString(); + } + + name = Localization.GetString(name, resourceFile); + + var attrs = new Dictionary(); + + var div = new TagBuilder("div"); + div.AddCssClass("dnnLabel"); + div.Attributes["style"] = "position: relative;"; + var aHelp = new TagBuilder("a"); + aHelp.AddCssClass("dnnFormHelp"); + + div.InnerHtml += htmlHelper.LabelFor(expression, name, attrs).ToString(); + div.InnerHtml += aHelp.ToString(); + + /* + < a id = "dnn_ctr385_ModuleSettings_plTitle_cmdHelp" tabindex = "-1" class="dnnFormHelp" aria-label="Help" href="javascript:__doPostBack('dnn$ctr385$ModuleSettings$plTitle$cmdHelp','')"> +
+
+ Saisissez un titre pour ce module.Il apparaitra dans la barre de titre du container utilisé pour ce module, si cette fonction est supportée par le container. + +
+
+ */ + + return MvcHtmlString.Create(div.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs new file mode 100644 index 00000000000..c21dda6b7a6 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs @@ -0,0 +1,342 @@ +// 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.Web.MvcPipeline.Containers +{ + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Linq; + using System.Linq.Expressions; + using System.Text; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + using System.Web.UI; + + // using DNNConnect.CKEditorProvider.Constants; + // using DNNConnect.CKEditorProvider.Utilities; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.Security.Roles; + using DotNetNuke.Services.FileSystem; + using DotNetNuke.Web.Client.ClientResourceManagement; + + public static partial class ModuleHelpers + { + public static IHtmlString TextEditorFor(this HtmlHelper htmlHelper, Expression> expression) + { + // HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes) + var id = htmlHelper.IdFor(expression); + + LoadAllSettings(htmlHelper.ViewContext, id.ToString()); + + var attrs = new Dictionary(); + attrs.Add("id", id); + attrs.Add("data-ckeditor", true); + return htmlHelper.TextAreaFor(expression, attrs); + } + + private static void LoadAllSettings(ViewContext page, string id) + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + /* + var settingsDictionary = EditorController.GetEditorHostSettings(); + var portalRoles = RoleController.Instance.GetRoles(portalSettings.PortalId); + NameValueCollection settings = new NameValueCollection(); + int parentModulId = -1; + string id = "id"; + + // Load Default Settings + var currentEditorSettings = SettingsUtil.GetDefaultSettings( + portalSettings, + portalSettings.HomeDirectoryMapPath, + settings["configFolder"], + portalRoles); + + // Set Current Mode to Default + currentEditorSettings.SettingMode = SettingsMode.Default; + + var hostKey = SettingConstants.HostKey; + var portalKey = SettingConstants.PortalKey(portalSettings.PortalId); + var pageKey = $"DNNCKT#{portalSettings.ActiveTab.TabID}#"; + var moduleKey = $"DNNCKMI#{parentModulId}#INS#{id}#"; + + // Load Host Settings ?! + if (SettingsUtil.CheckSettingsExistByKey(settingsDictionary, hostKey)) + { + var hostPortalRoles = RoleController.Instance.GetRoles(Host.HostPortalID); + currentEditorSettings = SettingsUtil.LoadEditorSettingsByKey( + portalSettings, + currentEditorSettings, + settingsDictionary, + hostKey, + hostPortalRoles); + + // Set Current Mode to Host + currentEditorSettings.SettingMode = SettingsMode.Host; + + // reset the roles to the correct portal + if (portalSettings.PortalId != Host.HostPortalID) + { + foreach (var toolbarRole in currentEditorSettings.ToolBarRoles) + { + var roleName = hostPortalRoles.FirstOrDefault(role => role.RoleID == toolbarRole.RoleId)?.RoleName ?? string.Empty; + var roleId = portalRoles.FirstOrDefault(role => role.RoleName.Equals(roleName))?.RoleID ?? Null.NullInteger; + toolbarRole.RoleId = roleId; + } + + foreach (var uploadRoles in currentEditorSettings.UploadSizeRoles) + { + var roleName = hostPortalRoles.FirstOrDefault(role => role.RoleID == uploadRoles.RoleId)?.RoleName ?? string.Empty; + var roleId = portalRoles.FirstOrDefault(role => role.RoleName.Equals(roleName))?.RoleID ?? Null.NullInteger; + uploadRoles.RoleId = roleId; + } + } + } + + // Load Portal Settings ?! + if (SettingsUtil.CheckSettingsExistByKey(settingsDictionary, portalKey)) + { + currentEditorSettings = SettingsUtil.LoadEditorSettingsByKey( + portalSettings, + currentEditorSettings, + settingsDictionary, + portalKey, + portalRoles); + + // Set Current Mode to Portal + currentEditorSettings.SettingMode = SettingsMode.Portal; + } + + // Load Page Settings ?! + if (SettingsUtil.CheckSettingsExistByKey(settingsDictionary, pageKey)) + { + currentEditorSettings = SettingsUtil.LoadEditorSettingsByKey( + portalSettings, currentEditorSettings, settingsDictionary, pageKey, portalRoles); + + // Set Current Mode to Page + currentEditorSettings.SettingMode = SettingsMode.Page; + } + + // Load Module Settings ?! + if (!SettingsUtil.CheckExistsModuleInstanceSettings(moduleKey, parentModulId)) + { + return; + } + + currentEditorSettings = SettingsUtil.LoadModuleSettings( + portalSettings, currentEditorSettings, moduleKey, parentModulId, portalRoles); + + // Set Current Mode to Module Instance + currentEditorSettings.SettingMode = SettingsMode.ModuleInstance; + */ + if (page.IsChildAction) + { + MvcClientResourceManager.RegisterStyleSheet(page, Globals.ResolveUrl("~/Providers/HtmlEditorProviders/DNNConnect.CKE/css/CKEditorToolBars.css")); + MvcClientResourceManager.RegisterStyleSheet(page, Globals.ResolveUrl("~/Providers/HtmlEditorProviders/DNNConnect.CKE/css/CKEditorOverride.css")); + MvcClientResourceManager.RegisterStyleSheet(page, Globals.ResolveUrl("~/Providers/HtmlEditorProviders/DNNConnect.CKE/js/ckeditor/4.18.0/editor.css")); + + /* + const string CsName = "CKEdScript"; + const string CsFindName = "CKFindScript"; + */ + + JavaScript.RequestRegistration(CommonJs.jQuery); + + MvcClientResourceManager.RegisterScript(page, "~/Providers/HtmlEditorProviders/DNNConnect.CKE/js/ckeditor/4.18.0/ckeditor.js"); + MvcClientResourceManager.RegisterScript(page, Globals.ResolveUrl("~/Providers/HtmlEditorProviders/DNNConnect.CKE/js/editorOverride.js")); + } + + /* + // Load Custom JS File + if (!string.IsNullOrEmpty(currentEditorSettings.CustomJsFile)) + { + MvcClientResourceManager.RegisterScript(page, FormatUrl(portalSettings, currentEditorSettings.CustomJsFile)); + } + + // GenerateEditorLoadScript + var clientID = "cke1"; + var editorVar = string.Format( + "editor{0}", + clientID.Substring(clientID.LastIndexOf("_", StringComparison.Ordinal) + 1).Replace( + "-", string.Empty)); + + var editorFixedId = clientID.Replace("-", string.Empty).Replace(".", string.Empty); + + var postBackScript = string.Format( + @" if (CKEDITOR && CKEDITOR.instances && CKEDITOR.instances.{0}) {{ CKEDITOR.instances.{0}.updateElement(); if (typeof Page_IsValid !== 'undefined' && !Page_IsValid) return false; CKEDITOR.instances.{0}.destroy(); }}", + editorFixedId); + */ + /* + this.RegisterOnSubmitStatement( + this.GetType(), string.Format("CKEditor_OnAjaxSubmit_{0}", editorFixedId), postBackScript); + */ + var editorScript = new StringBuilder(); + /* + editorScript.AppendFormat( + "Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(LoadCKEditorInstance_{0});", editorFixedId); + + editorScript.AppendFormat("function LoadCKEditorInstance_{0}(sender,args) {{", editorFixedId); + + editorScript.AppendFormat( + @"if (jQuery(""[id*='UpdatePanel']"").length == 0 && CKEDITOR && CKEDITOR.instances && CKEDITOR.instances.{0}) {{ CKEDITOR.instances.{0}.updateElement();}}", + editorFixedId); + + editorScript.AppendFormat( + "if (document.getElementById('{0}') == null){{return;}}", + editorFixedId); + + // Render EditorConfig + var editorConfigScript = new StringBuilder(); + editorConfigScript.AppendFormat("var editorConfig{0} = {{", editorVar); + + var keysCount = settings.Keys.Count; + var currentCount = 0; + + // Write options + foreach (string key in settings.Keys) + { + var value = settings[key]; + + currentCount++; + + // Is boolean state or string + if (value.Equals("true", StringComparison.InvariantCultureIgnoreCase) + || value.Equals("false", StringComparison.InvariantCultureIgnoreCase) || value.StartsWith("[") + || value.StartsWith("{") || Utility.IsNumeric(value)) + { + if (value.Equals("True")) + { + value = "true"; + } + else if (value.Equals("False")) + { + value = "false"; + } + + editorConfigScript.AppendFormat("{0}:{1}", key, value); + + editorConfigScript.Append(currentCount == keysCount ? "};" : ","); + } + else + { + if (key == "browser") + { + continue; + } + + editorConfigScript.AppendFormat("{0}:\'{1}\'", key, value); + + editorConfigScript.Append(currentCount == keysCount ? "};" : ","); + } + } + + editorScript.AppendFormat( + "if (CKEDITOR.instances.{0}){{return;}}", + editorFixedId); + + // Check if we can use jQuery or $, and if both fail use ckeditor without the adapter + editorScript.Append("if (jQuery().ckeditor) {"); + + editorScript.AppendFormat("var {0} = jQuery('#{1}').ckeditor(editorConfig{0});", editorVar, editorFixedId); + + editorScript.Append("} else if ($.ckeditor) {"); + + editorScript.AppendFormat("var {0} = $('#{1}').ckeditor(editorConfig{0});", editorVar, editorFixedId); + + editorScript.Append("} else {"); + + editorScript.AppendFormat("var {0} = CKEDITOR.replace( '{1}', editorConfig{0});", editorVar, editorFixedId); + + editorScript.Append("}"); + + // firefox maximize fix + editorScript.Append("CKEDITOR.on('instanceReady', function (ev) {"); + editorScript.Append("ev.editor.on('maximize', function () {"); + editorScript.Append("if (ev.editor.commands.maximize.state == 1) {"); + editorScript.Append("var mainDocument = CKEDITOR.document;"); + editorScript.Append("CKEDITOR.env.gecko && mainDocument.getDocumentElement().setStyle( 'position', 'fixed' );"); + editorScript.Append("}"); + editorScript.Append("});"); + editorScript.Append("});"); + + editorScript.Append("if(CKEDITOR && CKEDITOR.config){"); + editorScript.Append(" CKEDITOR.config.portalId = " + portalSettings.PortalId); + editorScript.Append("};"); + + // End of LoadScript + editorScript.Append("}"); + + MvcClientAPI.RegisterScript(string.Format(@"{0}_CKE_Config", editorFixedId), editorConfigScript.ToString()); + MvcClientAPI.RegisterStartupScript(string.Format(@"{0}_CKE_Startup", editorFixedId), editorScript.ToString()); + */ + /* + editorScript.Append(@" + if(CKEDITOR && CKEDITOR.config){ + CKEDITOR.config.portalId = " + portalSettings.PortalId + @"; + CKEDITOR.config.height = '400px'; + CKEDITOR.config.toolbar = [ + { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates' ] }, + { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] }, + { name: 'editing', items: [ 'Find', 'Replace', '-', 'SelectAll', '-', 'Scayt' ] }, + { name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] }, + '/', + { name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl' ] }, + { name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] }, + { name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule', 'SpecialChar' ] }, + '/', + { name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] }, + { name: 'colors', items: [ 'TextColor', 'BGColor' ] }, + { name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] } + ]; + CKEDITOR.config.removePlugins = 'elementspath,resize'; + CKEDITOR.config.extraPlugins = 'dnnpages'; + CKEDITOR.config.allowedContent = true; + } + jQuery('[data-ckeditor]').each(function() { + CKEDITOR.replace(this.id); + }); + "); + + MvcClientAPI.RegisterStartupScript("CKEditorConfig", editorScript.ToString()); + */ + } + + /* + private static string FormatUrl(PortalSettings portalSettings, string inputUrl) + { + var formattedUrl = string.Empty; + + if (string.IsNullOrEmpty(inputUrl)) + { + return formattedUrl; + } + + if (inputUrl.StartsWith("http://") || inputUrl.StartsWith("https://") || inputUrl.StartsWith("//")) + { + formattedUrl = inputUrl; + } + else if (inputUrl.StartsWith("FileID=")) + { + var fileId = int.Parse(inputUrl.Substring(7)); + + var objFileInfo = FileManager.Instance.GetFile(fileId); + + formattedUrl = portalSettings.HomeDirectory + objFileInfo.Folder + objFileInfo.FileName; + } + else + { + formattedUrl = portalSettings.HomeDirectory + inputUrl; + } + + return formattedUrl; + } + */ + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.cs new file mode 100644 index 00000000000..d1ac82a2320 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.cs @@ -0,0 +1,24 @@ +// 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.Web.Mvc.Page +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Services.Localization; + + public static class ModuleHelpers + { + public static IHtmlString LocalizeString(this HtmlHelper htmlHelper, string key, string localResourceFile) + { + return MvcHtmlString.Create(Localization.GetString(key, localResourceFile)); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs new file mode 100644 index 00000000000..814d9cae7b9 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs @@ -0,0 +1,145 @@ +// 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.MvcPipeline +{ + using System; + using System.Runtime.CompilerServices; + using System.Web.Mvc; + using System.Web.UI; + + using DotNetNuke.Entities.Icons; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Integration.Mvc; + + public static class PermissionTriStateHelper + { + public static MvcHtmlString PermissionTriState( + this HtmlHelper helper, + string name, + string value, + bool isFullControl = false, + bool isView = false, + bool locked = false, + bool supportsDenyMode = true, + string permissionKey = "") + { + const string scriptKey = "initTriState"; + MvcJavaScript.RequestRegistration(CommonJs.jQuery); + MvcClientResourceManager.RegisterScript(helper.ViewContext, "/js/dnn.permissiontristate.js"); + MvcClientAPI.RegisterStartupScript(scriptKey, GetInitScript()); + + var grantImagePath = IconController.IconURL("Grant"); + var denyImagePath = IconController.IconURL("Deny"); + var nullImagePath = IconController.IconURL("Unchecked"); + var lockImagePath = IconController.IconURL("Lock"); + + var grantAltText = Localization.GetString("PermissionTypeGrant"); + var denyAltText = Localization.GetString("PermissionTypeDeny"); + var nullAltText = Localization.GetString("PermissionTypeNull"); + + string imagePath; + string altText; + switch (value) + { + case "True": + imagePath = grantImagePath; + altText = grantAltText; + break; + case "False": + imagePath = denyImagePath; + altText = denyAltText; + break; + default: + imagePath = nullImagePath; + altText = nullAltText; + break; + } + + var cssClass = "tristate"; + if (locked) + { + imagePath = lockImagePath; + cssClass += " lockedPerm"; + } + + if (!supportsDenyMode) + { + cssClass += " noDenyPerm"; + } + + if (isFullControl) + { + cssClass += " fullControl"; + } + + if (isView && !locked) + { + cssClass += " view"; + } + + if (!string.IsNullOrEmpty(permissionKey) && !isView && !isFullControl) + { + cssClass += " " + permissionKey.ToLowerInvariant(); + } + + var img = new TagBuilder("img"); + img.MergeAttribute("src", imagePath); + img.MergeAttribute("alt", altText); + + var hidden = new TagBuilder("input"); + hidden.MergeAttribute("type", "hidden"); + hidden.MergeAttribute("name", name); + hidden.MergeAttribute("value", value); + hidden.MergeAttribute("class", cssClass); + hidden.MergeAttribute("id", name); + + return MvcHtmlString.Create(img.ToString(TagRenderMode.SelfClosing) + hidden.ToString(TagRenderMode.SelfClosing)); + } + + public static string GetInitScript() + { + string grantImagePath, denyImagePath, nullImagePath, lockImagePath, grantAltText, denyAltText, nullAltText; + + LookupScriptValues(out grantImagePath, out denyImagePath, out nullImagePath, out lockImagePath, out grantAltText, out denyAltText, out nullAltText); + + string script = + string.Format( + @"jQuery(document).ready( + function() {{ + var images = {{ 'True': '{0}', 'False': '{1}', 'Null': '{2}' }}; + var toolTips = {{ 'True': '{3}', 'False': '{4}', 'Null': '{5}' }}; + var tsm = dnn.controls.triStateManager(images, toolTips); + jQuery('.tristate').each( function(i, elem) {{ + tsm.initControl( elem ); + }}); + }});", + grantImagePath, + denyImagePath, + nullImagePath, + grantAltText, + denyAltText, + nullAltText); + + return script; + } + + private static void LookupScriptValues(out string grantImagePath, out string denyImagePath, out string nullImagePath, out string lockImagePath, out string grantAltText, out string denyAltText, out string nullAltText) + { + grantImagePath = IconController.IconURL("Grant"); + denyImagePath = IconController.IconURL("Deny"); + nullImagePath = IconController.IconURL("Unchecked"); + lockImagePath = IconController.IconURL("Lock"); + + grantAltText = Localization.GetString("PermissionTypeGrant"); + denyAltText = Localization.GetString("PermissionTypeDeny"); + nullAltText = Localization.GetString("PermissionTypeNull"); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs new file mode 100644 index 00000000000..040d71d3f54 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs @@ -0,0 +1,58 @@ +// 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.Web.MvcPipeline.Routing +{ + using System; + using System.Web; + using System.Web.Mvc; + using System.Web.Routing; + using System.Web.SessionState; + + using DotNetNuke.ComponentModel; + using DotNetNuke.Entities.Portals; + using DotNetNuke.HttpModules.Membership; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.MvcPipeline.Routing; + + public class DnnMvcPageHandler : MvcHandler, IHttpHandler, IRequiresSessionState + { + public DnnMvcPageHandler(RequestContext requestContext) + : base(requestContext) + { + } + + /// + protected override void ProcessRequest(HttpContext httpContext) + { + this.SetThreadCulture(); + MembershipModule.AuthenticateRequest(this.RequestContext.HttpContext, allowUnknownExtensions: true); + base.ProcessRequest(httpContext); + } + + protected override IAsyncResult BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, object state) + { + this.SetThreadCulture(); + MembershipModule.AuthenticateRequest(this.RequestContext.HttpContext, allowUnknownExtensions: true); + return base.BeginProcessRequest(httpContext, callback, state); + } + + private void SetThreadCulture() + { + var portalSettings = PortalController.Instance.GetCurrentSettings(); + if (portalSettings is null) + { + return; + } + + var pageLocale = Localization.GetPageLocale(portalSettings); + if (pageLocale is null) + { + return; + } + + Localization.SetThreadCultures(pageLocale, portalSettings); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageRouteHandler.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageRouteHandler.cs new file mode 100644 index 00000000000..556e29d916e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageRouteHandler.cs @@ -0,0 +1,52 @@ +// 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.Web.MvcPipeline.Routing +{ + using System; + using System.Web; + using System.Web.Mvc; + using System.Web.Routing; + using System.Web.SessionState; + + public class DnnMvcPageRouteHandler : IRouteHandler + { + private readonly IControllerFactory controllerFactory; + + /// Initializes a new instance of the class. + public DnnMvcPageRouteHandler() + { + } + + /// Initializes a new instance of the class. + /// The controller factory. + public DnnMvcPageRouteHandler(IControllerFactory controllerFactory) + { + this.controllerFactory = controllerFactory; + } + + /// + IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) + { + return this.GetHttpHandler(requestContext); + } + + protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) + { + requestContext.HttpContext.SetSessionStateBehavior(this.GetSessionStateBehavior(requestContext)); + return new DnnMvcPageHandler(requestContext); + } + + protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) + { + string controllerName = (string)requestContext.RouteData.Values["controller"]; + if (string.IsNullOrWhiteSpace(controllerName)) + { + throw new InvalidOperationException("No Controller"); + } + + IControllerFactory controllerFactory = this.controllerFactory ?? ControllerBuilder.Current.GetControllerFactory(); + return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpConfigurationExtensions.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpConfigurationExtensions.cs new file mode 100644 index 00000000000..efac1c41d5e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpConfigurationExtensions.cs @@ -0,0 +1,53 @@ +// 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.Web.MvcPipeline.Routing +{ + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Web.Http; + + using DotNetNuke.Common; + + public static class HttpConfigurationExtensions + { + private const string Key = "MvcTabAndModuleInfoProvider"; + + public static void AddTabAndModuleInfoProvider(this HttpConfiguration configuration, ITabAndModuleInfoProvider tabAndModuleInfoProvider) + { + Requires.NotNull("configuration", configuration); + Requires.NotNull("tabAndModuleInfoProvider", tabAndModuleInfoProvider); + + var providers = configuration.Properties.GetOrAdd(Key, InitValue) as ConcurrentQueue; + + if (providers == null) + { + providers = new ConcurrentQueue(); + configuration.Properties[Key] = providers; + } + + providers.Enqueue(tabAndModuleInfoProvider); + } + + public static IEnumerable GetTabAndModuleInfoProviders(this HttpConfiguration configuration) + { + Requires.NotNull("configuration", configuration); + + var providers = configuration.Properties.GetOrAdd(Key, InitValue) as ConcurrentQueue; + + if (providers == null) + { + // shouldn't ever happen outside of unit tests + return new ITabAndModuleInfoProvider[] { }; + } + + return providers.ToArray(); + } + + private static object InitValue(object o) + { + return new ConcurrentQueue(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpRequestExtensions.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpRequestExtensions.cs new file mode 100644 index 00000000000..7d4c8128d41 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/HttpRequestExtensions.cs @@ -0,0 +1,74 @@ +// 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.Web.MvcPipeline.Routing +{ + using System; + using System.Linq; + using System.Net; + using System.Net.NetworkInformation; + using System.Net.Sockets; + using System.Text; + using System.Web; + using System.Web.Http; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.Services.UserRequest; + + internal static class HttpRequestExtensions + { + private delegate bool TryMethod(ITabAndModuleInfoProvider provider, HttpRequestBase request, out T output); + + public static int FindTabId(this HttpRequestBase request) + { + return IterateTabAndModuleInfoProviders(request, TryFindTabId, -1); + } + + public static ModuleInfo FindModuleInfo(this HttpRequestBase request) + { + return IterateTabAndModuleInfoProviders(request, TryFindModuleInfo, null); + } + + public static int FindModuleId(this HttpRequestBase request) + { + return IterateTabAndModuleInfoProviders(request, TryFindModuleId, -1); + } + + public static string GetIPAddress(HttpRequestBase request) + { + return UserRequestIPAddressController.Instance.GetUserRequestIPAddress(request); + } + + private static bool TryFindTabId(ITabAndModuleInfoProvider provider, HttpRequestBase request, out int output) + { + return provider.TryFindTabId(request, out output); + } + + private static bool TryFindModuleInfo(ITabAndModuleInfoProvider provider, HttpRequestBase request, out ModuleInfo output) + { + return provider.TryFindModuleInfo(request, out output); + } + + private static bool TryFindModuleId(ITabAndModuleInfoProvider provider, HttpRequestBase request, out int output) + { + return provider.TryFindModuleId(request, out output); + } + + private static T IterateTabAndModuleInfoProviders(HttpRequestBase request, TryMethod func, T fallback) + { + var providers = GlobalConfiguration.Configuration.GetTabAndModuleInfoProviders(); + + foreach (var provider in providers) + { + T output; + if (func(provider, request, out output)) + { + return output; + } + } + + return fallback; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/ITabAndModuleInfoProvider.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/ITabAndModuleInfoProvider.cs new file mode 100644 index 00000000000..a3ddfd6b140 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/ITabAndModuleInfoProvider.cs @@ -0,0 +1,19 @@ +// 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.Web.MvcPipeline.Routing +{ + using System.Web; + + using DotNetNuke.Entities.Modules; + + public interface ITabAndModuleInfoProvider + { + bool TryFindTabId(HttpRequestBase request, out int tabId); + + bool TryFindModuleId(HttpRequestBase request, out int moduleId); + + bool TryFindModuleInfo(HttpRequestBase request, out ModuleInfo moduleInfo); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs new file mode 100644 index 00000000000..069d7972726 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs @@ -0,0 +1,69 @@ +// 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.Web.MvcPipeline.Routing +{ + using System; + using System.Web.Http; + using System.Web.Mvc; + using System.Web.Routing; + + using DotNetNuke.Common.Internal; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Instrumentation; + + public sealed class MvcRoutingManager: IRoutingManager + { + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(MvcRoutingManager)); + private readonly RouteCollection routes; + + public MvcRoutingManager() + : this(RouteTable.Routes) + { + } + + internal MvcRoutingManager(RouteCollection routes) + { + this.routes = routes; + } + + public void RegisterRoutes() + { + // add standard tab and module id provider + GlobalConfiguration.Configuration.AddTabAndModuleInfoProvider(new StandardTabAndModuleInfoProvider()); + using (this.routes.GetWriteLock()) + { + // routes.Clear(); -- don't use; it will remove original WEP API maps + this.LocateServicesAndMapRoutes(); + } + + Logger.TraceFormat("Registered a total of {0} routes", this.routes.Count); + } + + private static bool IsTracingEnabled() + { + var configValue = Config.GetSetting("EnableServicesFrameworkTracing"); + + return !string.IsNullOrEmpty(configValue) && Convert.ToBoolean(configValue); + } + + private void RegisterSystemRoutes() + { + var route = new Route( + "DesktopModules/{controller}/{action}/{tabid}/{language}", + new RouteValueDictionary(new { action = "Index", tabid = UrlParameter.Optional, language = UrlParameter.Optional }), + new DnnMvcPageRouteHandler()); + + // route.DataTokens = new RouteValueDictionary(); + // ConstraintValidation.Validate(route); + // route.SetNameSpaces(new string[] { "DotNetNuke.Framework.Controllers" }); + // route.SetName("Default"); + this.routes.Add(route); + } + + private void LocateServicesAndMapRoutes() + { + this.RegisterSystemRoutes(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/StandardTabAndModuleInfoProvider.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/StandardTabAndModuleInfoProvider.cs new file mode 100644 index 00000000000..48826680e11 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/StandardTabAndModuleInfoProvider.cs @@ -0,0 +1,171 @@ +// 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.Web.MvcPipeline.Routing +{ + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Web; + + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Instrumentation; + + public sealed class StandardTabAndModuleInfoProvider : ITabAndModuleInfoProvider + { + private const string ModuleIdKey = "ModuleId"; + private const string TabIdKey = "TabId"; + private const string MonikerQueryKey = "Moniker"; + private const string MonikerHeaderKey = "X-DNN-MONIKER"; + private const string MonikerSettingsKey = "Moniker"; + private static readonly ILog Logger = LoggerSource.Instance.GetLogger(typeof(StandardTabAndModuleInfoProvider)); + + /// + public bool TryFindTabId(HttpRequestBase request, out int tabId) + { + return TryFindTabId(request, out tabId, true); + } + + /// + public bool TryFindModuleId(HttpRequestBase request, out int moduleId) + { + return TryFindModuleId(request, out moduleId, true); + } + + /// + public bool TryFindModuleInfo(HttpRequestBase request, out ModuleInfo moduleInfo) + { + int tabId, moduleId; + if (TryFindTabId(request, out tabId, false) && TryFindModuleId(request, out moduleId, false)) + { + moduleInfo = ModuleController.Instance.GetModule(moduleId, tabId, false); + return moduleInfo != null; + } + + return TryFindByMoniker(request, out moduleInfo); + } + + private static bool TryFindTabId(HttpRequestBase request, out int tabId, bool tryMoniker) + { + tabId = FindInt(request, TabIdKey); + if (tabId > Null.NullInteger) + { + return true; + } + + if (tryMoniker) + { + ModuleInfo moduleInfo; + if (TryFindByMoniker(request, out moduleInfo)) + { + tabId = moduleInfo.TabID; + return true; + } + } + + return false; + } + + private static bool TryFindModuleId(HttpRequestBase request, out int moduleId, bool tryMoniker) + { + moduleId = FindInt(request, ModuleIdKey); + if (moduleId > Null.NullInteger) + { + return true; + } + + if (tryMoniker) + { + ModuleInfo moduleInfo; + if (TryFindByMoniker(request, out moduleInfo)) + { + moduleId = moduleInfo.ModuleID; + return true; + } + } + + return false; + } + + private static int FindInt(HttpRequestBase requestBase, string key) + { + string value = null; + if (requestBase.Headers[key] != null) + { + value = requestBase.Headers[key]; + } + + if (requestBase.Form[key] != null) + { + value = requestBase.Form[key]; + } + + if (string.IsNullOrEmpty(value) && requestBase.Url != null) + { + var queryString = HttpUtility.ParseQueryString(requestBase.Url.Query); + value = queryString[key]; + } + + int id; + return int.TryParse(value, out id) ? id : Null.NullInteger; + } + + private static bool TryFindByMoniker(HttpRequestBase requestBase, out ModuleInfo moduleInfo) + { + var id = FindIntInHeader(requestBase, MonikerHeaderKey); + if (id <= Null.NullInteger) + { + id = FindIntInQueryString(requestBase, MonikerQueryKey); + } + + moduleInfo = id > Null.NullInteger ? ModuleController.Instance.GetTabModule(id) : null; + return moduleInfo != null; + } + + private static int FindIntInHeader(HttpRequestBase requestBase, string key) + { + string value = null; + if (requestBase.Headers[key] != null) + { + value = requestBase.Form[key]; + } + + return GetTabModuleInfoFromMoniker(value); + } + + private static int FindIntInQueryString(HttpRequestBase requestBase, string key) + { + string value = null; + if (requestBase.Url != null) + { + var queryString = HttpUtility.ParseQueryString(requestBase.Url.Query); + value = queryString[key]; + } + + return GetTabModuleInfoFromMoniker(value); + } + + private static int GetTabModuleInfoFromMoniker(string monikerValue) + { + monikerValue = (monikerValue ?? string.Empty).Trim(); + if (monikerValue.Length > 0) + { + var ids = TabModulesController.Instance.GetTabModuleIdsBySetting(MonikerSettingsKey, monikerValue); + if (ids != null && ids.Any()) + { + return ids.First(); + } + + if (Logger.IsWarnEnabled) + { + Logger.WarnFormat("The specified moniker ({0}) is not defined in the system", monikerValue); + } + } + + return Null.NullInteger; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs new file mode 100644 index 00000000000..50d9b128a10 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs @@ -0,0 +1,378 @@ +// 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.Web.MvcPipeline.Security.Controllers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Web.Mvc; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Users; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Security.Roles; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Security.Models; + + public class ModulePermissionsGridController : PermissionsGridController + { + private bool inheritViewPermissionsFromTab; + private int moduleId = -1; + private ModulePermissionCollection modulePermissions; + private List permissionsList; + private int viewColumnIndex; + + public ModulePermissionsGridController() + { + this.TabId = -1; + } + + public ModulePermissionCollection ModulePermissions + { + get + { + // First Update Permissions in case they have been changed + // this.UpdateModulePermissions(); + return this.modulePermissions; + } + } + + public bool InheritViewPermissionsFromTab + { + get => this.inheritViewPermissionsFromTab; + set + { + this.inheritViewPermissionsFromTab = value; + this.permissionsList = null; + } + } + + public int ModuleId + { + get => this.moduleId; + set + { + this.moduleId = value; + this.GetModulePermissions(); + } + } + + public int TabId { get; set; } + + protected override List PermissionsList + { + get + { + if (this.permissionsList == null && this.modulePermissions != null) + { + this.permissionsList = this.modulePermissions.ToList(); + } + + return this.permissionsList; + } + } + + public ActionResult Index(int tabId, int moduleId, bool inheritViewPermissionsFromTab) + { + this.inheritViewPermissionsFromTab = inheritViewPermissionsFromTab; + this.TabId = tabId; + this.ModuleId = moduleId; + + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/Resources/Shared/Components/Tokeninput/jquery.tokeninput.js"); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/js/dnn.permissiongrid.js"); + + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Resources/Shared/Components/Tokeninput/Themes/token-input-facebook.css", FileOrder.Css.ResourceCss); + + var script = "var pgm = new dnn.permissionGridManager('ClientID');"; + MvcClientAPI.RegisterStartupScript("ClientID-PermissionGridManager", script); + + this.BindData(); + + var model = new ModulePermissionsGridViewModel + { + Permissions = this.Permissions, + Users = this.GetUsers(), + Roles = this.GetRolesComboBox(), + RoleGroups = this.GetRoleGroups(), + ModuleId = this.ModuleId, + TabId = this.TabId, + InheritViewPermissionsFromTab = this.InheritViewPermissionsFromTab, + RolePermissions = this.RolePermissions, + }; + + return this.View(model); + } + + protected override void AddPermission(List permissions, UserInfo user) + { + bool isMatch = this.modulePermissions.Cast() + .Any(objModulePermission => objModulePermission.UserID == user.UserID); + + if (!isMatch) + { + foreach (PermissionInfo objPermission in permissions) + { + if (objPermission.PermissionKey == "VIEW") + { + this.AddModulePermission( + objPermission, + int.Parse(Globals.glbRoleNothing), + Null.NullString, + user.UserID, + user.DisplayName, + true); + } + } + } + } + + protected override void AddPermission(List permissions, RoleInfo role) + { + if (this.modulePermissions.Cast().Any(p => p.RoleID == role.RoleID)) + { + return; + } + + foreach (PermissionInfo objPermission in permissions) + { + if (objPermission.PermissionKey == "VIEW") + { + this.AddModulePermission( + objPermission, + role.RoleID, + role.RoleName, + Null.NullInteger, + Null.NullString, + true); + } + } + } + + protected override void UpdateRolePermission(PermissionUpdateModel permission) + { + var permissionInfo = this.GetPermissionInfo(permission.PermissionId); + if (this.InheritViewPermissionsFromTab && permissionInfo.PermissionKey == "VIEW") + { + return; + } + + this.RemovePermission(permission.PermissionId, permission.RoleId, Null.NullInteger); + + if (permission.PermissionKey == PermissionTypeGrant) + { + var role = this.GetRole(permission.RoleId); + this.AddModulePermission( + permissionInfo, + permission.RoleId, + role.RoleName, + Null.NullInteger, + Null.NullString, + true); + } + else if (permission.PermissionKey == PermissionTypeDeny) + { + var role = this.GetRole(permission.RoleId); + this.AddModulePermission( + permissionInfo, + permission.RoleId, + role.RoleName, + Null.NullInteger, + Null.NullString, + false); + } + } + + protected override void UpdateUserPermission(PermissionUpdateModel permission) + { + var permissionInfo = this.GetPermissionInfo(permission.PermissionId); + if (this.InheritViewPermissionsFromTab && permissionInfo.PermissionKey == "VIEW") + { + return; + } + + this.RemovePermission(permission.PermissionId, Null.NullInteger, permission.UserId); + + if (permission.PermissionKey == PermissionTypeGrant || permission.PermissionKey == PermissionTypeDeny) + { + var user = UserController.GetUserById(this.PortalId, permission.UserId); + this.AddModulePermission( + permissionInfo, + Null.NullInteger, + Null.NullString, + permission.UserId, + user.DisplayName, + permission.PermissionKey == PermissionTypeGrant); + } + } + + protected void RemovePermission(int permissionID, int roleID, int userID) + { + this.modulePermissions.Remove(permissionID, roleID, userID); + + // Clear Permission List + this.permissionsList = null; + } + + protected override List GetPermissions() + { + var moduleInfo = ModuleController.Instance.GetModule(this.ModuleId, this.TabId, false); + var permissionController = new PermissionController(); + var permissions = permissionController.GetPermissionsByModule(this.ModuleId, this.TabId).Cast().ToList(); + + var permissionList = new List(); + for (int i = 0; i < permissions.Count; i++) + { + var permission = (PermissionInfo)permissions[i]; + if (permission.PermissionKey == "VIEW") + { + this.viewColumnIndex = i + 1; + permissionList.Add(permission); + } + else if (!(moduleInfo.IsShared && moduleInfo.IsShareableViewOnly)) + { + permissionList.Add(permission); + } + } + + return permissionList; + } + + protected override bool SupportsDenyPermissions(PermissionInfo permissionInfo) + { + return true; + } + + /// + protected override bool GetEnabled(PermissionInfo objPerm, RoleInfo role, int column) + { + bool enabled; + if (this.InheritViewPermissionsFromTab && column == this.viewColumnIndex) + { + enabled = false; + } + else + { + enabled = !this.IsImplicitRole(role.PortalID, role.RoleID); + } + + return enabled; + } + + /// + protected override bool GetEnabled(PermissionInfo objPerm, UserInfo user, int column) + { + bool enabled; + if (this.InheritViewPermissionsFromTab && column == this.viewColumnIndex) + { + enabled = false; + } + else + { + enabled = true; + } + + return enabled; + } + + /// + protected override string GetPermission(PermissionInfo objPerm, RoleInfo role, int column, string defaultState) + { + string permission; + if (this.InheritViewPermissionsFromTab && column == this.viewColumnIndex) + { + permission = PermissionTypeNull; + } + else + { + permission = role.RoleID == this.AdministratorRoleId + ? PermissionTypeGrant + : base.GetPermission(objPerm, role, column, defaultState); + } + + return permission; + } + + /// + protected override string GetPermission(PermissionInfo objPerm, UserInfo user, int column, string defaultState) + { + string permission; + if (this.InheritViewPermissionsFromTab && column == this.viewColumnIndex) + { + permission = PermissionTypeNull; + } + else + { + // Call base class method to handle standard permissions + permission = base.GetPermission(objPerm, user, column, defaultState); + } + + return permission; + } + + /// + protected override bool IsFullControl(PermissionInfo permissionInfo) + { + return (permissionInfo.PermissionKey == "EDIT") && PermissionProvider.Instance().SupportsFullControl(); + } + + /// + protected override bool IsViewPermisison(PermissionInfo permissionInfo) + { + return permissionInfo.PermissionKey == "VIEW"; + } + + private void AddModulePermission(PermissionInfo permission, int roleId, string roleName, int userId, string displayName, bool allowAccess) + { + var objPermission = new ModulePermissionInfo(permission) + { + ModuleID = this.ModuleId, + RoleID = roleId, + RoleName = roleName, + AllowAccess = allowAccess, + UserID = userId, + DisplayName = displayName, + }; + this.modulePermissions.Add(objPermission, true); + this.permissionsList = null; + } + + private void GetModulePermissions() + { + this.modulePermissions = new ModulePermissionCollection( + ModulePermissionController.GetModulePermissions(this.ModuleId, this.TabId)); + this.permissionsList = null; + } + + private void UpdateModulePermissions() + { + // Implementation of permission updates to the database + foreach (ModulePermissionInfo permission in this.modulePermissions) + { + // ModulePermissionController.SaveModulePermission(permission); + } + } + + private bool IsImplicitRole(int portalId, int roleId) + { + return ModulePermissionController.ImplicitRoles(portalId) + .Any(r => r.RoleID == roleId); + } + + private PermissionInfo GetPermissionInfo(int permissionId) + { + return this.GetPermissions().Cast() + .FirstOrDefault(p => p.PermissionID == permissionId); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/PermissionsGridController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/PermissionsGridController.cs new file mode 100644 index 00000000000..5991c487c49 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/PermissionsGridController.cs @@ -0,0 +1,645 @@ +// 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.Web.MvcPipeline.Security.Controllers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Web.Mvc; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Security.Roles; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Security.Models; + + public abstract class PermissionsGridController : Controller + { + protected const string PermissionTypeGrant = "True"; + protected const string PermissionTypeDeny = "False"; + protected const string PermissionTypeNull = "Null"; + + private List permissions; + private IList roles; + + protected List UserPermissions { get; set; } + + protected List RolePermissions { get; set; } + + protected virtual List PermissionsList + { + get { return null; } + } + + protected List Permissions + { + get { return this.permissions; } + } + + protected virtual bool RefreshGrid + { + get { return false; } + } + + protected int PortalId + { + get + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + return Globals.IsHostTab(portalSettings.ActiveTab.TabID) + ? Null.NullInteger + : portalSettings.PortalId; + } + } + + protected int UnAuthUsersRoleId => int.Parse(Globals.glbRoleUnauthUser); + + protected int AllUsersRoleId => int.Parse(Globals.glbRoleAllUsers); + + protected int AdministratorRoleId => PortalController.Instance.GetCurrentPortalSettings().AdministratorRoleId; + + protected int RegisteredUsersRoleId => PortalController.Instance.GetCurrentPortalSettings().RegisteredRoleId; + + public void BindData() + { + this.permissions = this.GetPermissions(); + this.BindRolesGrid(); + this.BindUsersGrid(); + } + + [HttpPost] + public virtual ActionResult UpdatePermissions(PermissionsUpdateModel model) + { + if (!this.ModelState.IsValid) + { + return this.Json(new { success = false, message = "Invalid model state" }); + } + + try + { + foreach (var permission in model.Permissions) + { + if (permission.IsRolePermission) + { + this.UpdateRolePermission(permission); + } + else + { + this.UpdateUserPermission(permission); + } + } + + return this.Json(new { success = true }); + } + catch (Exception ex) + { + return this.Json(new { success = false, message = ex.Message }); + } + } + + [HttpPost] + public virtual ActionResult AddRole(int roleId) + { + try + { + var role = this.GetRole(roleId); + if (role == null) + { + return this.Json(new { success = false, message = "Role not found" }); + } + + this.AddPermission(this.permissions, role); + + return this.Json(new { success = true }); + } + catch (Exception ex) + { + return this.Json(new { success = false, message = ex.Message }); + } + } + + [HttpPost] + public virtual ActionResult AddUser(int userId) + { + try + { + var user = UserController.GetUserById(this.PortalId, userId); + if (user == null) + { + return this.Json(new { success = false, message = "User not found" }); + } + + this.AddPermission(this.permissions, user); + + return this.Json(new { success = true }); + } + catch (Exception ex) + { + return this.Json(new { success = false, message = ex.Message }); + } + } + + protected abstract List GetPermissions(); + + protected abstract void AddPermission(List permissions, RoleInfo role); + + protected abstract void AddPermission(List permissions, UserInfo user); + + protected abstract void UpdateRolePermission(PermissionUpdateModel permission); + + protected abstract void UpdateUserPermission(PermissionUpdateModel permission); + + protected virtual IEnumerable GetRoleGroups() + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + var groups = RoleController.GetRoleGroups(portalSettings.PortalId).Cast(); + + // Add default items + var allGroups = new List + { + new RoleGroupInfo { RoleGroupID = -2, RoleGroupName = Localization.GetString("AllRoles") }, + new RoleGroupInfo { RoleGroupID = -1, RoleGroupName = Localization.GetString("GlobalRoles") }, + }; + + allGroups.AddRange(groups); + return allGroups; + } + + protected virtual List GetRolesComboBox(int roleGroupId = -1) + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + var roles = new List(); + + // Get roles based on group filter + if (roleGroupId > -2) + { + roles.AddRange(RoleController.Instance.GetRoles( + portalSettings.PortalId, + r => r.RoleGroupID == roleGroupId && + r.SecurityMode != SecurityMode.SocialGroup && + r.Status == RoleStatus.Approved)); + } + else + { + roles.AddRange(RoleController.Instance.GetRoles( + portalSettings.PortalId, + r => r.SecurityMode != SecurityMode.SocialGroup && + r.Status == RoleStatus.Approved)); + } + + // Add system roles if global roles selected + if (roleGroupId < 0) + { + roles.Add(new RoleInfo + { + RoleID = this.UnAuthUsersRoleId, + RoleName = Globals.glbRoleUnauthUserName, + }); + + roles.Add(new RoleInfo + { + RoleID = this.AllUsersRoleId, + RoleName = Globals.glbRoleAllUsersName, + }); + } + + // Ensure administrator role is always included + this.EnsureRole(roles, portalSettings.AdministratorRoleId); + + // Ensure registered users role is included + this.EnsureRole(roles, portalSettings.RegisteredRoleId); + + return roles; + } + + protected virtual List GetUsers() + { + var users = new List(); + + if (this.PermissionsList == null) + { + return users; + } + + foreach (var permission in this.PermissionsList) + { + if (!Null.IsNull(permission.UserID) && users.Cast().All(u => u.UserID != permission.UserID)) + { + var user = new UserInfo + { + UserID = permission.UserID, + Username = permission.Username, + DisplayName = permission.DisplayName, + }; + users.Add(user); + } + } + + return users; + } + + protected virtual RoleInfo GetRole(int roleId) + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + + if (roleId == this.AllUsersRoleId) + { + return new RoleInfo + { + RoleID = this.AllUsersRoleId, + RoleName = Globals.glbRoleAllUsersName, + PortalID = portalSettings.PortalId, + }; + } + + if (roleId == this.UnAuthUsersRoleId) + { + return new RoleInfo + { + RoleID = this.UnAuthUsersRoleId, + RoleName = Globals.glbRoleUnauthUserName, + PortalID = portalSettings.PortalId, + }; + } + + return RoleController.Instance.GetRoleById(portalSettings.PortalId, roleId); + } + + protected virtual void EnsureRole(List roles, int roleId) + { + if (roles.Cast().All(r => r.RoleID != roleId)) + { + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + var role = RoleController.Instance.GetRoleById(portalSettings.PortalId, roleId); + if (role != null) + { + roles.Add(role); + } + } + } + + protected virtual string BuildPermissionKey( + bool allowAccess, + int permissionId, + int objectPermissionId, + int roleId, + string roleName, + int userId = -1, + string displayName = null) + { + var key = allowAccess ? PermissionTypeGrant : PermissionTypeDeny; + + key += $"|{permissionId}|{(objectPermissionId > -1 ? objectPermissionId.ToString() : string.Empty)}"; + key += $"|{roleName}|{roleId}|{userId}|{displayName}"; + + return key; + } + + protected virtual bool GetEnabled(PermissionInfo permission, RoleInfo role, int column) + { + // Base implementation - override in derived classes for specific logic + return true; + } + + protected virtual bool GetEnabled(PermissionInfo permission, UserInfo user, int column) + { + // Base implementation - override in derived classes for specific logic + return true; + } + + protected virtual bool IsFullControl(PermissionInfo permissionInfo) + { + return false; + } + + protected virtual bool IsViewPermisison(PermissionInfo permissionInfo) + { + return false; + } + + protected virtual string GetPermissionState(PermissionInfo permission, RoleInfo role, string defaultState = null) + { + if (this.PermissionsList == null) + { + return defaultState ?? PermissionTypeNull; + } + + foreach (var existingPermission in this.PermissionsList) + { + if ( + existingPermission.PermissionID == permission.PermissionID && + existingPermission.RoleID == role.RoleID) + { + return existingPermission.AllowAccess ? PermissionTypeGrant : PermissionTypeDeny; + } + } + + return defaultState ?? PermissionTypeNull; + } + + protected virtual string GetPermissionState(PermissionInfo permission, UserInfo user, string defaultState = null) + { + if (this.PermissionsList == null) + { + return defaultState ?? PermissionTypeNull; + } + + foreach (var existingPermission in this.PermissionsList) + { + if ( + existingPermission.PermissionID == permission.PermissionID && + existingPermission.UserID == user.UserID) + { + return existingPermission.AllowAccess ? PermissionTypeGrant : PermissionTypeDeny; + } + } + + return defaultState ?? PermissionTypeNull; + } + + protected virtual bool SupportsDenyPermissions(PermissionInfo permissionInfo) + { + // to maintain backward compatibility the base implementation must always call the simple parameterless version of this method + return false; + } + + /// Gets the Value of the permission. + /// The permission being loaded. + /// The user. + /// The column of the Grid. + /// if the permission is granted, otherwise . + protected virtual bool GetPermission(PermissionInfo objPerm, UserInfo user, int column) + { + return Convert.ToBoolean(this.GetPermission(objPerm, user, column, PermissionTypeDeny)); + } + + /// Gets the Value of the permission. + /// The permission being loaded. + /// The user. + /// The column of the Grid. + /// Default State. + /// The permission state (one of , or ). + protected virtual string GetPermission(PermissionInfo objPerm, UserInfo user, int column, string defaultState) + { + var stateKey = defaultState; + if (this.PermissionsList != null) + { + foreach (var permission in this.PermissionsList) + { + if (permission.PermissionID == objPerm.PermissionID && permission.UserID == user.UserID) + { + if (permission.AllowAccess) + { + stateKey = PermissionTypeGrant; + } + else + { + stateKey = PermissionTypeDeny; + } + + break; + } + } + } + + return stateKey; + } + + protected virtual bool GetPermission(PermissionInfo objPerm, RoleInfo role, int column) + { + return Convert.ToBoolean(this.GetPermission(objPerm, role, column, PermissionTypeDeny)); + } + + /// Gets the Value of the permission. + /// The permission being loaded. + /// The role. + /// The column of the Grid. + /// Default State. + /// The permission state (one of , or ). + protected virtual string GetPermission(PermissionInfo objPerm, RoleInfo role, int column, string defaultState) + { + string stateKey = defaultState; + if (this.PermissionsList != null) + { + foreach (PermissionInfoBase permission in this.PermissionsList) + { + if (permission.PermissionID == objPerm.PermissionID && permission.RoleID == role.RoleID) + { + if (permission.AllowAccess) + { + stateKey = PermissionTypeGrant; + } + else + { + stateKey = PermissionTypeDeny; + } + + break; + } + } + } + + return stateKey; + } + + private void BindRolesGrid() + { + this.RolePermissions = new List(); + + /* + this.dtRolePermissions.Columns.Clear(); + this.dtRolePermissions.Rows.Clear(); + + // Add Roles Column + this.dtRolePermissions.Columns.Add(new DataColumn("RoleId")); + + // Add Roles Column + this.dtRolePermissions.Columns.Add(new DataColumn("RoleName")); + + for (int i = 0; i <= this.permissions.Count - 1; i++) + { + var permissionInfo = (PermissionInfo)this.permissions[i]; + + // Add Enabled Column + this.dtRolePermissions.Columns.Add(new DataColumn(permissionInfo.PermissionName + "_Enabled")); + + // Add Permission Column + this.dtRolePermissions.Columns.Add(new DataColumn(permissionInfo.PermissionName)); + } + */ + + this.GetRoles(); + + // this.UpdateRolePermissions(); + for (int i = 0; i <= this.roles.Count - 1; i++) + { + var role = this.roles[i]; + var roleModel = new RoleModel(); + roleModel.RoleId = role.RoleID; + roleModel.RoleName = Localization.LocalizeRole(role.RoleName); + roleModel.Permissions = new Dictionary(); + int j; + for (j = 0; j <= this.permissions.Count - 1; j++) + { + var permModel = new PermissionModel(); + PermissionInfo objPerm; + objPerm = (PermissionInfo)this.permissions[j]; + roleModel.Permissions.Add(objPerm.PermissionName, permModel); + permModel.Enabled = this.GetEnabled(objPerm, role, j + 1); + permModel.Locked = !permModel.Enabled; + if (this.SupportsDenyPermissions(objPerm)) + { + permModel.State = this.GetPermission(objPerm, role, j + 1, PermissionTypeNull); + } + else + { + if (this.GetPermission(objPerm, role, j + 1)) + { + permModel.State = PermissionTypeGrant; + } + else + { + permModel.State = PermissionTypeNull; + } + } + } + + this.RolePermissions.Add(roleModel); + } + } + + private void BindUsersGrid() + { + /* + this.dtUserPermissions.Columns.Clear(); + this.dtUserPermissions.Rows.Clear(); + + // Add Roles Column + var col = new DataColumn("UserId"); + this.dtUserPermissions.Columns.Add(col); + + // Add Roles Column + col = new DataColumn("DisplayName"); + this.dtUserPermissions.Columns.Add(col); + int i; + for (i = 0; i <= this.permissions.Count - 1; i++) + { + PermissionInfo objPerm; + objPerm = (PermissionInfo)this.permissions[i]; + + // Add Enabled Column + col = new DataColumn(objPerm.PermissionName + "_Enabled"); + this.dtUserPermissions.Columns.Add(col); + + // Add Permission Column + col = new DataColumn(objPerm.PermissionName); + this.dtUserPermissions.Columns.Add(col); + } + + if (this.userPermissionsGrid != null) + { + this.users = this.GetUsers(); + + if (this.users.Count != 0) + { + this.userPermissionsGrid.Visible = true; + DataRow row; + for (i = 0; i <= this.users.Count - 1; i++) + { + var user = (UserInfo)this.users[i]; + row = this.dtUserPermissions.NewRow(); + row["UserId"] = user.UserID; + row["DisplayName"] = user.DisplayName; + int j; + for (j = 0; j <= this.permissions.Count - 1; j++) + { + PermissionInfo objPerm; + objPerm = (PermissionInfo)this.permissions[j]; + row[objPerm.PermissionName + "_Enabled"] = this.GetEnabled(objPerm, user, j + 1); + if (this.SupportsDenyPermissions(objPerm)) + { + row[objPerm.PermissionName] = this.GetPermission(objPerm, user, j + 1, PermissionTypeNull); + } + else + { + if (this.GetPermission(objPerm, user, j + 1)) + { + row[objPerm.PermissionName] = PermissionTypeGrant; + } + else + { + row[objPerm.PermissionName] = PermissionTypeNull; + } + } + } + + this.dtUserPermissions.Rows.Add(row); + } + + this.userPermissionsGrid.DataSource = this.dtUserPermissions; + this.userPermissionsGrid.DataBind(); + } + else + { + this.dtUserPermissions.Rows.Clear(); + this.userPermissionsGrid.DataSource = this.dtUserPermissions; + this.userPermissionsGrid.DataBind(); + this.userPermissionsGrid.Visible = false; + } + } + */ + } + + private void GetRoles() + { + var checkedRoles = this.GetCheckedRoles(); + var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); + this.roles = RoleController.Instance.GetRoles(portalSettings.PortalId, r => r.SecurityMode != SecurityMode.SocialGroup && r.Status == RoleStatus.Approved && checkedRoles.Contains(r.RoleID)); + + if (checkedRoles.Contains(this.UnAuthUsersRoleId)) + { + this.roles.Add(new RoleInfo { RoleID = this.UnAuthUsersRoleId, RoleName = Globals.glbRoleUnauthUserName }); + } + + if (checkedRoles.Contains(this.AllUsersRoleId)) + { + this.roles.Add(new RoleInfo { RoleID = this.AllUsersRoleId, PortalID = portalSettings.PortalId, RoleName = Globals.glbRoleAllUsersName }); + } + + // Administrators Role always has implicit permissions, then it should be always in + this.EnsureRole(RoleController.Instance.GetRoleById(portalSettings.PortalId, portalSettings.AdministratorRoleId)); + + // Show also default roles + this.EnsureRole(RoleController.Instance.GetRoleById(portalSettings.PortalId, portalSettings.RegisteredRoleId)); + this.EnsureRole(new RoleInfo { RoleID = this.AllUsersRoleId, PortalID = portalSettings.PortalId, RoleName = Globals.glbRoleAllUsersName }); + + this.roles.Reverse(); + + // this.roles.Sort(new RoleComparer()); + } + + private IEnumerable GetCheckedRoles() + { + if (this.PermissionsList == null) + { + return new List(); + } + + return this.PermissionsList.Select(r => r.RoleID).Distinct(); + } + + private void EnsureRole(RoleInfo role) + { + if (this.roles.Cast().All(r => r.RoleID != role.RoleID)) + { + this.roles.Add(role); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/ModulePermissionsGridViewModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/ModulePermissionsGridViewModel.cs new file mode 100644 index 00000000000..6f3b1939c01 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/ModulePermissionsGridViewModel.cs @@ -0,0 +1,19 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System.Collections.Generic; + + public class ModulePermissionsGridViewModel : PermissionsGridViewModel + { + public int ModuleId { get; set; } + + public int TabId { get; set; } + + public bool InheritViewPermissionsFromTab { get; set; } + + public List RolePermissions { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionModel.cs new file mode 100644 index 00000000000..0695292837c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionModel.cs @@ -0,0 +1,23 @@ +// 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.Web.MvcPipeline.Security.Models +{ + public class PermissionModel + { + public bool Enabled { get; set; } + + public string State { get; set; } + + public bool IsFullControl { get; set; } + + public bool IsView { get; set; } + + public bool Locked { get; set; } + + public bool SupportsDenyMode { get; set; } + + public string PermissionKey { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionTriStateModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionTriStateModel.cs new file mode 100644 index 00000000000..e8eceb28310 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionTriStateModel.cs @@ -0,0 +1,23 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + using DotNetNuke.Security.Permissions; + + public class PermissionTriStateModel + { + public PermissionInfo Permission { get; set; } + + public int RoleId { get; set; } + + public int UserId { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionUpdateModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionUpdateModel.cs new file mode 100644 index 00000000000..570bbdb1a3d --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionUpdateModel.cs @@ -0,0 +1,28 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System; + using System.Collections; + using System.Collections.Generic; + + using DotNetNuke.Entities.Users; + using DotNetNuke.Security.Roles; + + public class PermissionUpdateModel + { + public int PermissionId { get; set; } + + public bool IsRolePermission { get; set; } + + public int RoleId { get; set; } + + public int UserId { get; set; } + + public string PermissionKey { get; set; } + + public bool AllowAccess { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsGridViewModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsGridViewModel.cs new file mode 100644 index 00000000000..b6ff5e7d261 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsGridViewModel.cs @@ -0,0 +1,25 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System; + using System.Collections; + using System.Collections.Generic; + + using DotNetNuke.Entities.Users; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Security.Roles; + + public class PermissionsGridViewModel + { + public List Permissions { get; set; } + + public List Users { get; set; } + + public List Roles { get; set; } + + public IEnumerable RoleGroups { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsUpdateModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsUpdateModel.cs new file mode 100644 index 00000000000..7da911f0426 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/PermissionsUpdateModel.cs @@ -0,0 +1,18 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System; + using System.Collections; + using System.Collections.Generic; + + using DotNetNuke.Entities.Users; + using DotNetNuke.Security.Roles; + + public class PermissionsUpdateModel + { + public List Permissions { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/RoleModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/RoleModel.cs new file mode 100644 index 00000000000..35f54b59335 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/RoleModel.cs @@ -0,0 +1,17 @@ +// 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.Web.MvcPipeline.Security.Models +{ + using System.Collections.Generic; + + public class RoleModel + { + public int RoleId { get; set; } + + public string RoleName { get; set; } + + public Dictionary Permissions { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/UserModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/UserModel.cs new file mode 100644 index 00000000000..67c0c86af6e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Models/UserModel.cs @@ -0,0 +1,10 @@ +// 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.Web.MvcPipeline.Security.Models +{ + public class UserModel + { + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Scripts/PermissionsGrid.js b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Scripts/PermissionsGrid.js new file mode 100644 index 00000000000..c9ea1ff55df --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Scripts/PermissionsGrid.js @@ -0,0 +1,267 @@ +class PermissionsGrid { + constructor(options) { + this.options = { + container: '.dnnPermissionsGrid', + updateUrl: '', + addRoleUrl: '', + addUserUrl: '', + ...options + }; + + this.init(); + } + + init() { + this.container = $(this.options.container); + + // Initialize tokeninput for user search + this.initUserSearch(); + + // Bind event handlers + this.bindEvents(); + + // Initialize tri-state checkboxes + this.initTriStateCheckboxes(); + } + + bindEvents() { + // Role group dropdown change + this.container.find('.roleGroupsDropDown').on('change', (e) => { + this.onRoleGroupChange(e); + }); + + // Add role button click + this.container.find('.addRoleBtn').on('click', (e) => { + e.preventDefault(); + this.addRole(); + }); + + // Add user button click + this.container.find('.addUserBtn').on('click', (e) => { + e.preventDefault(); + this.addUser(); + }); + + // Delete role click + this.container.find('.deleteRole').on('click', (e) => { + e.preventDefault(); + this.deleteRole($(e.currentTarget)); + }); + + // Delete user click + this.container.find('.deleteUser').on('click', (e) => { + e.preventDefault(); + this.deleteUser($(e.currentTarget)); + }); + + // Permission change + this.container.find('.permissionTriState').on('change', (e) => { + this.updatePermissions(); + }); + } + + initUserSearch() { + const $userSearch = this.container.find('.userSearchInput'); + const serviceFramework = $.ServicesFramework(); + + $userSearch.tokenInput('/DesktopModules/Admin/Security/API/Users/Search', { + theme: 'facebook', + resultsFormatter: (item) => { + return `
  • ${item.displayName} (${item.userName})
  • `; + }, + tokenFormatter: (item) => { + return `
  • ${item.displayName}
  • `; + }, + preventDuplicates: true, + tokenLimit: 1, + onAdd: (item) => { + this.container.find('#hiddenUserIds').val(item.id); + }, + onDelete: () => { + this.container.find('#hiddenUserIds').val(''); + }, + hintText: 'Type to search users', + noResultsText: 'No results', + searchingText: 'Searching...', + tokenValue: 'id', + propertyToSearch: 'displayName', + prePopulate: null, + animateDropdown: false, + processPrePopulate: false + }); + } + + initTriStateCheckboxes() { + this.container.find('.permissionTriState').each((i, el) => { + const $triState = $(el); + $triState.triState({ + state: $triState.data('state'), + enabled: $triState.data('enabled') + }); + }); + } + + onRoleGroupChange(e) { + const groupId = $(e.target).val(); + const $rolesDropDown = this.container.find('.rolesDropDown'); + + $.ajax({ + url: '/API/Security/GetRoles', + data: { groupId: groupId }, + success: (roles) => { + $rolesDropDown.empty(); + roles.forEach(role => { + $rolesDropDown.append(new Option(role.RoleName, role.RoleID)); + }); + } + }); + } + + addRole() { + const roleId = this.container.find('.rolesDropDown').val(); + + $.ajax({ + url: this.options.addRoleUrl, + type: 'POST', + data: { roleId: roleId }, + success: (response) => { + if (response.success) { + window.location.reload(); + } else { + alert(response.message); + } + } + }); + } + + addUser() { + const userId = this.container.find('#hiddenUserIds').val(); + if (!userId) { + alert('Please select a user'); + return; + } + + $.ajax({ + url: this.options.addUserUrl, + type: 'POST', + data: { userId: userId }, + success: (response) => { + if (response.success) { + window.location.reload(); + } else { + alert(response.message); + } + } + }); + } + + deleteRole(button) { + if (!confirm('Are you sure you want to delete this role permission?')) { + return; + } + + const row = button.closest('tr'); + const roleId = row.data('role-id'); + this.deletePermissions('role', roleId, row); + } + + deleteUser(button) { + if (!confirm('Are you sure you want to delete this user permission?')) { + return; + } + + const row = button.closest('tr'); + const userId = row.data('user-id'); + this.deletePermissions('user', userId, row); + } + + deletePermissions(type, id, row) { + const permissions = []; + row.find('.permissionTriState').each((i, el) => { + const $triState = $(el); + permissions.push({ + permissionId: $triState.data('permission-id'), + isRolePermission: type === 'role', + roleId: type === 'role' ? id : null, + userId: type === 'user' ? id : null, + permissionKey: 'Null' + }); + }); + + this.updatePermissionsOnServer(permissions, () => { + row.remove(); + }); + } + + updatePermissions() { + const permissions = []; + + // Collect role permissions + this.container.find('.rolePermissions tr[data-role-id]').each((i, row) => { + const $row = $(row); + const roleId = $row.data('role-id'); + + $row.find('.permissionTriState').each((j, checkbox) => { + const $checkbox = $(checkbox); + permissions.push({ + permissionId: $checkbox.data('permission-id'), + isRolePermission: true, + roleId: roleId, + userId: null, + permissionKey: $checkbox.triState('state') + }); + }); + }); + + // Collect user permissions + this.container.find('.userPermissions tr[data-user-id]').each((i, row) => { + const $row = $(row); + const userId = $row.data('user-id'); + + $row.find('.permissionTriState').each((j, checkbox) => { + const $checkbox = $(checkbox); + permissions.push({ + permissionId: $checkbox.data('permission-id'), + isRolePermission: false, + roleId: null, + userId: userId, + permissionKey: $checkbox.triState('state') + }); + }); + }); + + this.updatePermissionsOnServer(permissions); + } + + updatePermissionsOnServer(permissions, callback) { + $.ajax({ + url: this.options.updateUrl, + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ permissions: permissions }), + success: (response) => { + if (response.success) { + if (callback) { + callback(); + } + } else { + alert(response.message); + } + } + }); + } +} + +// jQuery plugin +(function($) { + $.fn.permissionsGrid = function(options) { + return this.each(function() { + if (!$.data(this, 'permissionsGrid')) { + $.data(this, 'permissionsGrid', new PermissionsGrid({ + container: this, + ...options + })); + } + }); + }; +})(jQuery); \ No newline at end of file diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.BreadCrumb.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.BreadCrumb.cs new file mode 100644 index 00000000000..612fd42ec01 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.BreadCrumb.cs @@ -0,0 +1,107 @@ +// 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.Web.MvcPipeline.Skins +{ + using System.Text; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Web.MvcPipeline.Models; + using Microsoft.Extensions.DependencyInjection; + + public static partial class SkinHelpers + { + public static IHtmlString BreadCrumb(this HtmlHelper helper, string cssClass = "SkinObject", string separator = "\"breadcrumb", int rootLevel = 0, bool useTitle = false, bool hideWithNoBreadCrumb = false, bool cleanerMarkup = false) + { + var portalSettings = PortalSettings.Current; + var navigationManager = helper.ViewData.Model.NavigationManager; + var breadcrumb = new StringBuilder(""); + var position = 1; + var showRoot = rootLevel < 0; + var homeUrl = string.Empty; + var homeTabName = "Root"; + + if (showRoot) + { + rootLevel = 0; + } + + if (hideWithNoBreadCrumb && portalSettings.ActiveTab.BreadCrumbs.Count == (rootLevel + 1)) + { + return MvcHtmlString.Empty; + } + + if (showRoot && portalSettings.ActiveTab.TabID != portalSettings.HomeTabId) + { + homeUrl = Globals.AddHTTP(portalSettings.PortalAlias.HTTPAlias); + + if (portalSettings.HomeTabId != -1) + { + homeUrl = navigationManager.NavigateURL(portalSettings.HomeTabId); + + var tc = new TabController(); + var homeTab = tc.GetTab(portalSettings.HomeTabId, portalSettings.PortalId, false); + homeTabName = homeTab.LocalizedTabName; + + if (useTitle && !string.IsNullOrEmpty(homeTab.Title)) + { + homeTabName = homeTab.Title; + } + } + + breadcrumb.Append(""); + breadcrumb.Append("" + homeTabName + ""); + breadcrumb.Append(""); + breadcrumb.Append(""); + breadcrumb.Append(separator); + } + + for (var i = rootLevel; i < portalSettings.ActiveTab.BreadCrumbs.Count; ++i) + { + if (i > rootLevel) + { + breadcrumb.Append(separator); + } + + var tab = (TabInfo)portalSettings.ActiveTab.BreadCrumbs[i]; + var tabName = tab.LocalizedTabName; + + if (useTitle && !string.IsNullOrEmpty(tab.Title)) + { + tabName = tab.Title; + } + + var tabUrl = tab.FullUrl; + + if (tab.DisableLink) + { + if (cleanerMarkup) + { + breadcrumb.Append("" + tabName + ""); + } + else + { + breadcrumb.Append("" + tabName + ""); + } + } + else + { + breadcrumb.Append(""); + breadcrumb.Append("" + tabName + ""); + breadcrumb.Append(""); + breadcrumb.Append(""); + } + } + + breadcrumb.Append(""); + + return new MvcHtmlString(breadcrumb.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ControlPanel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ControlPanel.cs new file mode 100644 index 00000000000..27e289d23f3 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ControlPanel.cs @@ -0,0 +1,28 @@ +// 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.Web.MvcPipeline.Skins +{ + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString ControlPanel(this HtmlHelper helper, string cssClass = "SkinObject") + { + var lblControlPanel = new TagBuilder("span"); + + if (!string.IsNullOrEmpty(cssClass)) + { + lblControlPanel.AddCssClass(cssClass); + } + + // lblControlPanel.SetInnerText(Localization.GetString("ControlPanel", Localization.GetResourceFile(helper.ViewContext.Controller, "ControlPanel.ascx"))); + return new MvcHtmlString(lblControlPanel.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Copyright.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Copyright.cs new file mode 100644 index 00000000000..4923677b6e8 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Copyright.cs @@ -0,0 +1,39 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Copyright(this HtmlHelper helper, string cssClass = "SkinObject") + { + var portalSettings = PortalSettings.Current; + var lblCopyright = new TagBuilder("span"); + + if (!string.IsNullOrEmpty(cssClass)) + { + lblCopyright.AddCssClass(cssClass); + } + + if (!string.IsNullOrEmpty(portalSettings.FooterText)) + { + lblCopyright.SetInnerText(portalSettings.FooterText.Replace("[year]", DateTime.Now.ToString("yyyy"))); + } + else + { + lblCopyright.SetInnerText(string.Format(Localization.GetString("Copyright", GetSkinsResourceFile("Copyright.ascx")), DateTime.Now.Year, portalSettings.PortalName)); + } + + return new MvcHtmlString(lblCopyright.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.CurrentDate.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.CurrentDate.cs new file mode 100644 index 00000000000..bb8c386aacd --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.CurrentDate.cs @@ -0,0 +1,29 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString CurrentDate(this HtmlHelper helper, string cssClass = "SkinObject") + { + var lblDate = new TagBuilder("span"); + + if (!string.IsNullOrEmpty(cssClass)) + { + lblDate.AddCssClass(cssClass); + } + + lblDate.SetInnerText(DateTime.Now.ToString("D")); + + return new MvcHtmlString(lblDate.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssExclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssExclude.cs new file mode 100644 index 00000000000..da4c2699f9e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssExclude.cs @@ -0,0 +1,23 @@ +// 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.Web.MvcPipeline.Skins +{ + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DnnCssExclude(this HtmlHelper helper, string name) + { + var cssExclude = new TagBuilder("dnn:DnnCssExclude"); + cssExclude.Attributes.Add("ID", "ctlExclude"); + cssExclude.Attributes.Add("Name", name); + + return new MvcHtmlString(cssExclude.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs new file mode 100644 index 00000000000..bc883451e33 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs @@ -0,0 +1,60 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using ClientDependency.Core; + using ClientDependency.Core.Mvc; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DnnCssInclude(this HtmlHelper helper, string filePath, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") + { + helper.RequiresCss(filePath, pathNameAlias, priority); + if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) + { + return new MvcHtmlString(string.Format("", ClientDependencyType.Css, filePath, forceProvider, priority)); + } + + return new MvcHtmlString(string.Empty); + } + + public static IHtmlString DnnCssInclude(this HtmlHelper helper, string bundleName, string[] filePaths, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") + { + // ClientDependency.Core.BundleManager.CreateCssBundle( + // bundleName, + // filePaths.Select(p => new CssFile(p) { PathNameAlias = pathNameAlias, Priority = priority }).ToArray()); + + // helper.RequiresCssBundle(bundleName); + if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) + { + return new MvcHtmlString(string.Format("", ClientDependencyType.Css, string.Join(",", filePaths), forceProvider, priority)); + } + + return new MvcHtmlString(string.Empty); + } + + public static IHtmlString DnnCssIncludeDefaultStylesheet(this HtmlHelper helper, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") + { + var filePath = string.Concat(Common.Globals.ApplicationPath, "/Resources/Shared/stylesheets/dnndefault/7.0.0/default.css"); + MvcClientResourceManager.RegisterDefaultStylesheet(helper.ViewContext, filePath); + + if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) + { + return new MvcHtmlString(string.Format("", ClientDependencyType.Css, filePath, forceProvider, priority)); + } + + return new MvcHtmlString(string.Empty); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsExclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsExclude.cs new file mode 100644 index 00000000000..2ea1e9f5ae4 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsExclude.cs @@ -0,0 +1,25 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DnnJsExclude(this HtmlHelper helper, string name) + { + var jsExclude = new TagBuilder("dnn:DnnJsExclude"); + jsExclude.Attributes.Add("ID", "ctlExclude"); + jsExclude.Attributes.Add("runat", "server"); + jsExclude.Attributes.Add("Name", name); + + return new MvcHtmlString(jsExclude.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs new file mode 100644 index 00000000000..a71fbd9e406 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs @@ -0,0 +1,39 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Collections.Generic; + using System.Security.Policy; + using System.Web; + using System.Web.Mvc; + + using ClientDependency.Core; + using ClientDependency.Core.Mvc; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DnnJsInclude(this HtmlHelper helper, string filePath, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, bool defer = false) + { + // var htmlAttibs = new { nonce = helper.ViewContext.HttpContext.Items["CSP-NONCE"].ToString(), defer = defer ? "defer" : string.Empty }; + var htmlAttibs = new Dictionary(); + htmlAttibs.Add("nonce", helper.ViewContext.HttpContext.Items["CSP-NONCE"].ToString()); + if (defer) + { + htmlAttibs.Add("defer", "defer"); + } + + helper.RequiresJs(filePath, pathNameAlias, priority, htmlAttibs); + + if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) + { + return new MvcHtmlString(string.Format("", ClientDependencyType.Javascript, filePath, forceProvider, priority)); + } + + return new MvcHtmlString(string.Empty); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnLink.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnLink.cs new file mode 100644 index 00000000000..c40fa909511 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnLink.cs @@ -0,0 +1,26 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DnnLink(this HtmlHelper helper, string cssClass = "", string target = "") + { + var link = new TagBuilder("a"); + link.Attributes.Add("href", "http://www.dnnsoftware.com/community?utm_source=dnn-install&utm_medium=web-link&utm_content=gravity-skin-link&utm_campaign=dnn-install"); + link.Attributes.Add("class", cssClass); + link.Attributes.Add("target", target); + link.SetInnerText("CMS by DNN"); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DotNetNuke.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DotNetNuke.cs new file mode 100644 index 00000000000..4cbb2346e6a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DotNetNuke.cs @@ -0,0 +1,28 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Application; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString DotNetNuke(this HtmlHelper helper, string cssClass = "") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + link.Attributes.Add("href", DotNetNukeContext.Current.Application.Url); + link.Attributes.Add("class", cssClass); + link.SetInnerText(DotNetNukeContext.Current.Application.LegalCopyright); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Help.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Help.cs new file mode 100644 index 00000000000..6ac16c0f52e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Help.cs @@ -0,0 +1,27 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Help(this HtmlHelper helper, string cssClass = "") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + link.Attributes.Add("href", "mailto:" + portalSettings.Email + "?subject=" + portalSettings.PortalName + " Support Request"); + link.Attributes.Add("class", cssClass); + link.SetInnerText("Help"); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.HostName.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.HostName.cs new file mode 100644 index 00000000000..12cd723e748 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.HostName.cs @@ -0,0 +1,27 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Host; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString HostName(this HtmlHelper helper, string cssClass = "") + { + var link = new TagBuilder("a"); + link.Attributes.Add("href", Globals.AddHTTP(Host.HostURL)); + link.Attributes.Add("class", cssClass); + link.SetInnerText(Host.HostTitle); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.JavaScriptLibraryInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.JavaScriptLibraryInclude.cs new file mode 100644 index 00000000000..191c7aaf620 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.JavaScriptLibraryInclude.cs @@ -0,0 +1,34 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString JavaScriptLibraryInclude(this HtmlHelper helper, string name, string version = "", string specificVersion = "") + { + var script = new TagBuilder("script"); + script.Attributes.Add("src", name); + script.Attributes.Add("type", "text/javascript"); + + if (!string.IsNullOrEmpty(version)) + { + script.Attributes.Add("data-version", version); + } + + if (!string.IsNullOrEmpty(specificVersion)) + { + script.Attributes.Add("data-specific-version", specificVersion); + } + + return new MvcHtmlString(script.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Language.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Language.cs new file mode 100644 index 00000000000..3bcdd9e606c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Language.cs @@ -0,0 +1,218 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Web; + using System.Web.Mvc; + using System.Web.UI.WebControls; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Skins.Controls; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Language( + this HtmlHelper helper, + string cssClass = "", + string itemTemplate = "", + string selectedItemTemplate = "", + string headerTemplate = "", + string footerTemplate = "", + string alternateTemplate = "", + string separatorTemplate = "", + string commonHeaderTemplate = "", + string commonFooterTemplate = "", + bool showMenu = true, + bool showLinks = false, + bool useCurrentCultureForTemplate = false) + { + var portalSettings = PortalSettings.Current; + var currentCulture = CultureInfo.CurrentCulture.ToString(); + var templateCulture = useCurrentCultureForTemplate ? currentCulture : "en-US"; + var localResourceFile = GetSkinsResourceFile("Language.ascx"); + var localTokenReplace = new LanguageTokenReplace { resourceFile = localResourceFile }; + + var locales = new Dictionary(); + IEnumerable cultureListItems = Localization.LoadCultureInListItems(CultureDropDownTypes.NativeName, currentCulture, string.Empty, false); + foreach (Locale loc in LocaleController.Instance.GetLocales(portalSettings.PortalId).Values) + { + string defaultRoles = PortalController.GetPortalSetting(string.Format("DefaultTranslatorRoles-{0}", loc.Code), portalSettings.PortalId, "Administrators"); + if (!portalSettings.ContentLocalizationEnabled || + (LocaleIsAvailable(loc, portalSettings) && + (PortalSecurity.IsInRoles(portalSettings.AdministratorRoleName) || loc.IsPublished || PortalSecurity.IsInRoles(defaultRoles)))) + { + locales.Add(loc.Code, loc); + } + } + + int cultureCount = 0; + + var selectCulture = new TagBuilder("select"); + selectCulture.Attributes.Add("id", "selectCulture"); + selectCulture.Attributes.Add("name", "selectCulture"); + selectCulture.AddCssClass("NormalTextBox"); + + if (!string.IsNullOrEmpty(cssClass)) + { + selectCulture.AddCssClass(cssClass); + } + + // TimoBreumelhof: This is not ideal but it works for now.. should really be a UL IMO + foreach (var cultureItem in cultureListItems) + { + cultureCount++; + if (locales.ContainsKey(cultureItem.Value)) + { + var option = new TagBuilder("option"); + option.Attributes.Add("value", cultureItem.Value); + option.Attributes.Add("data-link", ParseTemplate("[URL]", cultureItem.Value, localTokenReplace, currentCulture)); + option.Attributes.Add("onclick", "var url = this.dataset.link; window.location.href= url"); + option.SetInnerText(cultureItem.Text); + if (cultureItem.Value == currentCulture) + { + option.Attributes.Add("selected", "selected"); + } + + selectCulture.InnerHtml += option.ToString(); + } + } + + if (string.IsNullOrEmpty(commonHeaderTemplate)) + { + commonHeaderTemplate = Localization.GetString("CommonHeaderTemplate.Default", localResourceFile, templateCulture); + } + + if (string.IsNullOrEmpty(commonFooterTemplate)) + { + commonFooterTemplate = Localization.GetString("CommonFooterTemplate.Default", localResourceFile, templateCulture); + } + + string languageContainer = string.Empty; + languageContainer += commonHeaderTemplate; + + if (showMenu) + { + languageContainer += selectCulture.ToString(); + } + + if (showLinks) + { + if (string.IsNullOrEmpty(itemTemplate)) + { + itemTemplate = Localization.GetString("ItemTemplate.Default", localResourceFile, templateCulture); + } + + if (string.IsNullOrEmpty(alternateTemplate)) + { + alternateTemplate = Localization.GetString("AlternateTemplate.Default", localResourceFile, templateCulture); + } + + if (string.IsNullOrEmpty(selectedItemTemplate)) + { + selectedItemTemplate = Localization.GetString("SelectedItemTemplate.Default", localResourceFile, templateCulture); + } + + if (string.IsNullOrEmpty(headerTemplate)) + { + headerTemplate = Localization.GetString("HeaderTemplate.Default", localResourceFile, templateCulture); + } + + if (string.IsNullOrEmpty(footerTemplate)) + { + footerTemplate = Localization.GetString("FooterTemplate.Default", localResourceFile, templateCulture); + } + + languageContainer += headerTemplate; + + string listItems = string.Empty; + + bool alt = false; + foreach (var locale in locales.Values) + { + string listItem = string.Empty; + if (locale.Code == currentCulture && !string.IsNullOrEmpty(selectedItemTemplate)) + { + listItem += ParseTemplate(selectedItemTemplate, locale.Code, localTokenReplace, currentCulture); + } + else + { + if (alt) + { + listItem += ParseTemplate(alternateTemplate, locale.Code, localTokenReplace, currentCulture); + } + else + { + listItem += ParseTemplate(itemTemplate, locale.Code, localTokenReplace, currentCulture); + } + + alt = !alt; + } + + listItems += listItem; + } + + languageContainer += listItems; + + languageContainer += footerTemplate; + } + + languageContainer += commonFooterTemplate; + + if (cultureCount <= 1) + { + languageContainer = string.Empty; + } + + return new MvcHtmlString(languageContainer); + } + + private static string ParseTemplate(string template, string locale, LanguageTokenReplace localTokenReplace, string currentCulture) + { + string strReturnValue = template; + try + { + if (!string.IsNullOrEmpty(locale)) + { + localTokenReplace.Language = locale; + } + else + { + localTokenReplace.Language = currentCulture; + } + + strReturnValue = localTokenReplace.ReplaceEnvironmentTokens(strReturnValue); + } + catch (Exception ex) + { + Exceptions.ProcessPageLoadException(ex, HttpContext.Current.Request.RawUrl); + } + + return strReturnValue; + } + + private static bool LocaleIsAvailable(Locale locale, PortalSettings portalSettings) + { + var tab = portalSettings.ActiveTab; + if (tab.DefaultLanguageTab != null) + { + tab = tab.DefaultLanguageTab; + } + + var localizedTab = TabController.Instance.GetTabByCulture(tab.TabID, tab.PortalID, locale); + + return localizedTab != null && !localizedTab.IsDeleted && TabPermissionController.CanViewPage(localizedTab); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LeftMenu.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LeftMenu.cs new file mode 100644 index 00000000000..913c80ec88e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LeftMenu.cs @@ -0,0 +1,20 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString LeftMenu(this HtmlHelper helper) + { + return new MvcHtmlString(string.Empty); // LeftMenu is deprecated and should return an empty string. + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToFullSite.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToFullSite.cs new file mode 100644 index 00000000000..12b873beca8 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToFullSite.cs @@ -0,0 +1,29 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString LinkToFullSite(this HtmlHelper helper, string cssClass = "SkinObject") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + link.Attributes.Add("href", portalSettings.PortalAlias.HTTPAlias); + link.SetInnerText(Localization.GetString("lnkPortal.Text", GetSkinsResourceFile("LinkToFullSite.ascx"))); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToMobileSite.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToMobileSite.cs new file mode 100644 index 00000000000..32c59e04b46 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToMobileSite.cs @@ -0,0 +1,38 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Mobile; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString LinkToMobileSite(this HtmlHelper helper, string cssClass = "SkinObject") + { + var redirectionController = new RedirectionController(); + var redirectUrl = redirectionController.GetMobileSiteUrl(); + if (!string.IsNullOrEmpty(redirectUrl)) + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + link.Attributes.Add("href", portalSettings.PortalAlias.HTTPAlias); + link.SetInnerText(Localization.GetString("lnkPortal.Text", GetSkinsResourceFile("LinkToMobileSite.ascx"))); + return new MvcHtmlString(link.ToString()); + } + else + { + return new MvcHtmlString(string.Empty); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToTabletSite.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToTabletSite.cs new file mode 100644 index 00000000000..93baec0d64d --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.LinkToTabletSite.cs @@ -0,0 +1,29 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString LinkToTabletSite(this HtmlHelper helper, string cssClass = "SkinObject") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + link.Attributes.Add("href", portalSettings.PortalAlias.HTTPAlias); + link.SetInnerText(Localization.GetString("lnkPortal.Text", GetSkinsResourceFile("LinkToTabletSite.ascx"))); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Links.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Links.cs new file mode 100644 index 00000000000..8c07c906303 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Links.cs @@ -0,0 +1,66 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Text; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.UI; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Links(this HtmlHelper helper, string cssClass = "SkinObject", string separator = " ", string level = "same", bool showDisabled = false, bool forceLinks = true, bool includeActiveTab = true) + { + var portalSettings = PortalSettings.Current; + var links = new StringBuilder(); + + var tabs = TabController.GetTabsBySortOrder(portalSettings.PortalId); + foreach (var tab in tabs) + { + if (Navigation.CanShowTab(tab, false, showDisabled)) + { + if (level == "same" && tab.ParentId == portalSettings.ActiveTab.ParentId) + { + if (includeActiveTab || tab.TabID != portalSettings.ActiveTab.TabID) + { + links.Append($"{tab.TabName}{separator}"); + } + } + else if (level == "child" && tab.ParentId == portalSettings.ActiveTab.TabID) + { + links.Append($"{tab.TabName}{separator}"); + } + else if (level == "parent" && tab.TabID == portalSettings.ActiveTab.ParentId) + { + links.Append($"{tab.TabName}{separator}"); + } + else if (level == "root" && tab.Level == 0) + { + links.Append($"{tab.TabName}{separator}"); + } + } + } + + if (forceLinks && string.IsNullOrEmpty(links.ToString())) + { + foreach (var tab in tabs) + { + if (Navigation.CanShowTab(tab, false, showDisabled)) + { + links.Append($"{tab.TabName}{separator}"); + } + } + } + + return new MvcHtmlString(links.ToString().TrimEnd(separator.ToCharArray())); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Login.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Login.cs new file mode 100644 index 00000000000..cd57a9d94fb --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Login.cs @@ -0,0 +1,172 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Runtime.CompilerServices; + using System.Security.Policy; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Authentication; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + private const string LoginFileName = "Login.ascx"; + + public static IHtmlString Login(this HtmlHelper helper, string cssClass = "SkinObject", string text = "", string logoffText = "", bool legacyMode = true, bool showInErrorPage = false) + { + var navigationManager = helper.ViewData.Model.NavigationManager; + var nonce = helper.ViewData.Model.ContentSecurityPolicy.Nonce; + var portalSettings = PortalSettings.Current; + var request = HttpContext.Current.Request; + + var isVisible = (!portalSettings.HideLoginControl || request.IsAuthenticated) + && (!portalSettings.InErrorPageRequest() || showInErrorPage); + + if (!isVisible) + { + return MvcHtmlString.Empty; + } + + if (legacyMode) + { + return BuildLegacyLogin(text, cssClass, logoffText, nonce, navigationManager); + } + + return BuildEnhancedLogin(text, cssClass, logoffText, nonce, navigationManager); + } + + private static MvcHtmlString BuildLegacyLogin(string text, string cssClass, string logoffText, string nonce, INavigationManager navigationManager) + { + var link = new TagBuilder("a"); + ConfigureLoginLink(link, text, cssClass, logoffText, out string loginScript, nonce, navigationManager); + return new MvcHtmlString(link.ToString() + loginScript); + } + + private static MvcHtmlString BuildEnhancedLogin(string text, string cssClass, string logoffText, string nonce, INavigationManager navigationManager) + { + var container = new TagBuilder("div"); + container.AddCssClass("loginGroup"); + + var link = new TagBuilder("a"); + link.AddCssClass("secondaryActionsList"); + ConfigureLoginLink(link, text, cssClass, logoffText, out string loginScript, nonce, navigationManager); + + container.InnerHtml = link.ToString(); + return new MvcHtmlString(container.ToString() + loginScript); + } + + private static void ConfigureLoginLink(TagBuilder link, string text, string cssClass, string logoffText, out string loginScript, string nonce, INavigationManager navigationManager) + { + var portalSettings = PortalSettings.Current; + var request = HttpContext.Current.Request; + + loginScript = string.Empty; + + if (!string.IsNullOrEmpty(cssClass)) + { + link.AddCssClass(cssClass); + } + else + { + link.AddCssClass("SkinObject"); + } + + link.Attributes["rel"] = "nofollow"; + + if (request.IsAuthenticated) + { + var displayText = !string.IsNullOrEmpty(logoffText) + ? logoffText.Replace("src=\"", "src=\"" + portalSettings.ActiveTab.SkinPath) + : Localization.GetString("Logout", GetSkinsResourceFile(LoginFileName)); + + link.SetInnerText(displayText); + link.Attributes["title"] = displayText; + link.Attributes["href"] = navigationManager.NavigateURL(portalSettings.ActiveTab.TabID, "Logoff"); + } + else + { + var displayText = !string.IsNullOrEmpty(text) + ? text.Replace("src=\"", "src=\"" + portalSettings.ActiveTab.SkinPath) + : Localization.GetString("Login", GetSkinsResourceFile(LoginFileName)); + + link.SetInnerText(displayText); + link.Attributes["title"] = displayText; + + string returnUrl = request.RawUrl; + if (returnUrl.IndexOf("?returnurl=", StringComparison.OrdinalIgnoreCase) != -1) + { + returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?returnurl=", StringComparison.OrdinalIgnoreCase)); + } + + returnUrl = HttpUtility.UrlEncode(returnUrl); + + var loginUrl = Globals.LoginURL(returnUrl, request.QueryString["override"] != null); + link.Attributes["href"] = loginUrl; + + // link.Attributes["data-url"] = loginUrl; + link.Attributes["class"] += " dnnLoginLink"; + + loginScript = GetLoginScript(loginUrl, nonce); + } + } + + private static string GetLoginScript(string loginUrl, string nonce) + { + var portalSettings = PortalSettings.Current; + var request = HttpContext.Current.Request; + + if (!request.IsAuthenticated) + { + var script = string.Format( + @" + "; + + return script; + } + + return string.Empty; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Logo.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Logo.cs new file mode 100644 index 00000000000..ae0170dd365 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Logo.cs @@ -0,0 +1,156 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Linq; + using System.Web; + using System.Web.Mvc; + using System.Xml; + using System.Xml.Linq; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.FileSystem; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Logo(this HtmlHelper helper, string borderWidth = "", string cssClass = "", string linkCssClass = "", bool injectSvg = false) + { + var portalSettings = PortalSettings.Current; + var navigationManager = helper.ViewData.Model.NavigationManager; + + TagBuilder tbImage = new TagBuilder("img"); + if (!string.IsNullOrEmpty(borderWidth)) + { + tbImage.Attributes.Add("style", $"border-width:{borderWidth};"); + } + + if (!string.IsNullOrEmpty(cssClass)) + { + tbImage.AddCssClass(cssClass); + } + + tbImage.Attributes.Add("alt", portalSettings.PortalName); + + TagBuilder tbLink = new TagBuilder("a"); + if (!string.IsNullOrEmpty(linkCssClass)) + { + tbLink.AddCssClass(linkCssClass); + } + + if (!string.IsNullOrEmpty(portalSettings.LogoFile)) + { + var fileInfo = GetLogoFileInfo(portalSettings); + if (fileInfo != null) + { + if (injectSvg && "svg".Equals(fileInfo.Extension, StringComparison.OrdinalIgnoreCase)) + { + string svgContent = GetSvgContent(fileInfo, portalSettings, cssClass); + if (!string.IsNullOrEmpty(svgContent)) + { + tbLink.InnerHtml = svgContent; + } + } + else + { + string imageUrl = FileManager.Instance.GetUrl(fileInfo); + if (!string.IsNullOrEmpty(imageUrl)) + { + tbImage.Attributes.Add("src", imageUrl); + tbLink.InnerHtml = tbImage.ToString(); + } + } + } + } + + tbLink.Attributes.Add("title", portalSettings.PortalName); + tbLink.Attributes.Add("aria-label", portalSettings.PortalName); + + if (portalSettings.HomeTabId != -1) + { + tbLink.Attributes.Add("href", navigationManager.NavigateURL(portalSettings.HomeTabId)); + } + else + { + tbLink.Attributes.Add("href", Globals.AddHTTP(portalSettings.PortalAlias.HTTPAlias)); + } + + return new MvcHtmlString(tbLink.ToString()); + } + + private static IFileInfo GetLogoFileInfo(PortalSettings portalSettings) + { + string cacheKey = string.Format(DataCache.PortalCacheKey, portalSettings.PortalId, portalSettings.CultureCode) + "LogoFile"; + var file = CBO.GetCachedObject( + new CacheItemArgs(cacheKey, DataCache.PortalCacheTimeOut, DataCache.PortalCachePriority), + (CacheItemArgs itemArgs) => + { + return FileManager.Instance.GetFile(portalSettings.PortalId, portalSettings.LogoFile); + }); + + return file; + } + + private static string GetSvgContent(IFileInfo svgFile, PortalSettings portalSettings, string cssClass) + { + var cacheKey = string.Format(DataCache.PortalCacheKey, portalSettings.PortalId, portalSettings.CultureCode) + "LogoSvg"; + return CBO.GetCachedObject( + new CacheItemArgs(cacheKey, DataCache.PortalCacheTimeOut, DataCache.PortalCachePriority, svgFile), + (_) => + { + try + { + XDocument svgDocument; + using (var fileContent = FileManager.Instance.GetFileContent(svgFile)) + { + svgDocument = XDocument.Load(fileContent); + } + + var svgXmlNode = svgDocument.Descendants() + .SingleOrDefault(x => x.Name.LocalName.Equals("svg", StringComparison.Ordinal)); + if (svgXmlNode == null) + { + throw new InvalidFileContentException("The svg file has no svg node."); + } + + if (!string.IsNullOrEmpty(cssClass)) + { + // Append the css class. + var classes = svgXmlNode.Attribute("class")?.Value; + svgXmlNode.SetAttributeValue("class", string.IsNullOrEmpty(classes) ? cssClass : $"{classes} {cssClass}"); + } + + if (svgXmlNode.Descendants().FirstOrDefault(x => x.Name.LocalName.Equals("title", StringComparison.Ordinal)) == null) + { + // Add the title for ADA compliance. + var ns = svgXmlNode.GetDefaultNamespace(); + var titleNode = new XElement( + ns + "title", + new XAttribute("id", "logoTitle"), + portalSettings.PortalName); + + svgXmlNode.AddFirst(titleNode); + + // Link the title to the svg node. + svgXmlNode.SetAttributeValue("aria-labelledby", "logoTitle"); + } + + // Ensure we have the image role for ADA Compliance + svgXmlNode.SetAttributeValue("role", "img"); + + return svgDocument.ToString(); + } + catch (XmlException ex) + { + throw new InvalidFileContentException("Invalid SVG file: " + ex.Message); + } + }); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Meta.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Meta.cs new file mode 100644 index 00000000000..889dc8382df --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Meta.cs @@ -0,0 +1,37 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Meta(this HtmlHelper helper, string name = "", string content = "", string httpEquiv = "", bool insertFirst = false) + { + var metaTag = new TagBuilder("meta"); + + if (!string.IsNullOrEmpty(name)) + { + metaTag.Attributes.Add("name", name); + } + + if (!string.IsNullOrEmpty(content)) + { + metaTag.Attributes.Add("content", content); + } + + if (!string.IsNullOrEmpty(httpEquiv)) + { + metaTag.Attributes.Add("http-equiv", httpEquiv); + } + + return new MvcHtmlString(metaTag.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ModuleMessage.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ModuleMessage.cs new file mode 100644 index 00000000000..2f37fbbce5c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.ModuleMessage.cs @@ -0,0 +1,45 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString ModuleMessage(this HtmlHelper helper, string heading = "", string message = "", string cssClass = "dnnModuleMessage", string headingCssClass = "dnnModMessageHeading") + { + var panel = new TagBuilder("div"); + panel.AddCssClass(cssClass); + + if (!string.IsNullOrEmpty(heading)) + { + var headingLabel = new TagBuilder("span"); + headingLabel.AddCssClass(headingCssClass); + headingLabel.SetInnerText(heading); + panel.InnerHtml += headingLabel.ToString(); + } + + var messageLabel = new TagBuilder("span"); + messageLabel.SetInnerText(message); + panel.InnerHtml += messageLabel.ToString(); + + var script = new TagBuilder("script"); + script.Attributes.Add("type", "text/javascript"); + script.InnerHtml = @" + jQuery(document).ready(function ($) { + var $body = window.opera ? (document.compatMode == 'CSS1Compat' ? $('html') : $('body')) : $('html,body'); + var scrollTop = $('#" + panel.Attributes["id"] + @"').offset().top - parseInt($(document.body).css('margin-top')); + $body.animate({ scrollTop: scrollTop }, 'fast'); + }); + "; + + return new MvcHtmlString(panel.ToString() + script.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs new file mode 100644 index 00000000000..ea0cdf2750a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Framework.JavaScriptLibraries; + + using DotNetNuke.UI.Modules; + using DotNetNuke.UI.Skins; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinExtensions + { + public static IHtmlString Pane(this HtmlHelper htmlHelper, string id, string cssClass = "") + { + var model = htmlHelper.ViewData.Model; + if (model == null) + { + throw new InvalidOperationException("The model need to be present."); + } + + var editDiv = new TagBuilder("div"); + + // editDiv.GenerateId("dnn_" + id + "_SyncPanel"); + var paneDiv = new TagBuilder("div"); + paneDiv.AddCssClass("dnnPane"); + paneDiv.GenerateId("dnn_" + id); + if (model.IsEditMode) + { + editDiv.AddCssClass(cssClass); + + // paneDiv.AddCssClass(model.Skin.PaneCssClass); + paneDiv.Attributes["data-name"] = id; + } + else + { + paneDiv.AddCssClass(cssClass); + } + + if (model.Skin.Panes.ContainsKey(id)) + { + var pane = model.Skin.Panes[id]; + paneDiv.AddCssClass(pane.CssClass); + foreach (var container in pane.Containers) + { + string sanitizedModuleName = Null.NullString; + if (!string.IsNullOrEmpty(container.Value.ModuleConfiguration.DesktopModule.ModuleName)) + { + sanitizedModuleName = Globals.CreateValidClass(container.Value.ModuleConfiguration.DesktopModule.ModuleName, false); + } + + var moduleDiv = new TagBuilder("div"); + moduleDiv.AddCssClass("DnnModule-" + container.Value.ModuleConfiguration.ModuleID); + moduleDiv.AddCssClass("DnnModule-" + sanitizedModuleName); + moduleDiv.AddCssClass("DnnModule"); + if (model.IsEditMode) + { + moduleDiv.Attributes["data-module-title"] = container.Value.ModuleConfiguration.ModuleTitle; + } + + if (Globals.IsAdminControl()) + { + moduleDiv.AddCssClass("DnnModule-Admin"); + } + + var anchor = new TagBuilder("a"); + anchor.Attributes["name"] = container.Value.ModuleConfiguration.ModuleID.ToString(); + moduleDiv.InnerHtml += anchor.ToString(); + + moduleDiv.InnerHtml += htmlHelper.Partial(container.Value.ContainerRazorFile, container.Value).ToHtmlString(); + paneDiv.InnerHtml += moduleDiv.ToString(); + } + } + else + { + paneDiv.AddCssClass("DNNEmptyPane"); + } + + if (model.IsEditMode) + { + editDiv.InnerHtml += paneDiv.ToString(); + return MvcHtmlString.Create(editDiv.ToString()); + } + else + { + return MvcHtmlString.Create(paneDiv.InnerHtml); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Privacy.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Privacy.cs new file mode 100644 index 00000000000..44a9f97253a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Privacy.cs @@ -0,0 +1,50 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Privacy(this HtmlHelper helper, string text = "", string cssClass = "SkinObject", string rel = "nofollow") + { + var navigationManager = helper.ViewData.Model.NavigationManager; + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + // Add Css Class + link.Attributes.Add("class", cssClass); + + // Add Text + if (string.IsNullOrWhiteSpace(text)) + { + text = Localization.GetString("Privacy.Text", GetSkinsResourceFile("Privacy.ascx")); + } + + link.SetInnerText(text); + + // Add Link + var href = portalSettings.PrivacyTabId == Null.NullInteger ? navigationManager.NavigateURL(portalSettings.ActiveTab.TabID, "Privacy") : navigationManager.NavigateURL(portalSettings.PrivacyTabId); + link.Attributes.Add("href", href); + + // Add Rel + if (!string.IsNullOrWhiteSpace(rel)) + { + link.Attributes.Add("rel", rel); + } + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Search.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Search.cs new file mode 100644 index 00000000000..e86a70a2515 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Search.cs @@ -0,0 +1,394 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Security.Policy; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Icons; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + private const string SearchFileName = "Search.ascx"; + + public static MvcHtmlString Search( + this HtmlHelper helper, + string id, + bool useDropDownList = false, + bool showWeb = true, + bool showSite = true, + string cssClass = "", + string submit = null, + string webIconURL = null, + string webText = null, + string webToolTip = null, + string webUrl = null, + string siteText = null, + bool useWebForSite = false, + bool enableWildSearch = true, + int minCharRequired = 2, + int autoSearchDelayInMilliSecond = 400) + { + var navigationManager = helper.ViewData.Model.NavigationManager; + var nonce = helper.ViewData.Model.ContentSecurityPolicy.Nonce; + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + MvcClientResourceManager.RegisterStyleSheet(helper.ViewContext, "~/Resources/Search/SearchSkinObjectPreview.css", FileOrder.Css.ModuleCss); + var htmlAttributes = new Dictionary + { + { "defer", "defer" }, + }; + MvcClientResourceManager.RegisterScript(helper.ViewContext, "~/Resources/Search/SearchSkinObjectPreview.js", htmlAttributes: htmlAttributes); + + var searchType = "S"; + /* + if (this.WebRadioButton.Visible) + { + if (this.WebRadioButton.Checked) + { + this.SearchType = "W"; + } + } + */ + if (string.IsNullOrEmpty(webIconURL)) + { + webIconURL = IconController.IconURL("GoogleSearch"); + } + + if (string.IsNullOrEmpty(webText)) + { + webUrl = Localization.GetString("Web", SkinHelpers.GetSkinsResourceFile(SearchFileName)); + } + + if (string.IsNullOrEmpty(webToolTip)) + { + webUrl = Localization.GetString("Web.ToolTip", SkinHelpers.GetSkinsResourceFile(SearchFileName)); + } + + if (string.IsNullOrEmpty(webUrl)) + { + webUrl = Localization.GetString("URL", SkinHelpers.GetSkinsResourceFile(SearchFileName)); + } + + var searchUrl = SkinHelpers.ExecuteSearchUrl(string.Empty, searchType, useWebForSite, webUrl, navigationManager); + if (!useDropDownList) + { + return BuildClassicSearch(id, showWeb, showSite, cssClass, submit, webText, siteText, enableWildSearch, minCharRequired, autoSearchDelayInMilliSecond, searchUrl, nonce); + } + + return BuildDropDownSearch(id, cssClass, submit, webText, siteText, enableWildSearch, minCharRequired, autoSearchDelayInMilliSecond, searchUrl, nonce); + } + + private static MvcHtmlString BuildClassicSearch( + string id, + bool showWeb, + bool showSite, + string cssClass, + string submit, + string webText, + string siteText, + bool enableWildSearch, + int minCharRequired, + int autoSearchDelayInMilliSecond, + string searchUrl, + string nonce) + { + var container = new TagBuilder("span"); + container.GenerateId("dnn_" + id + "_ClassicSearch"); + var containerId = container.Attributes["id"]; + + if (showWeb) + { + var radio = new TagBuilder("input"); + radio.Attributes["type"] = "radio"; + radio.Attributes["name"] = "SearchType"; + radio.Attributes["value"] = "W"; + radio.Attributes["id"] = "dnn_" + id + "WebRadioButton"; + radio.Attributes["class"] = cssClass; + radio.Attributes["checked"] = "checked"; + container.InnerHtml += radio.ToString(TagRenderMode.SelfClosing); + + var label = new TagBuilder("label"); + label.Attributes["for"] = "WebRadioButton"; + label.SetInnerText(webText ?? Localization.GetString("Web", GetSkinsResourceFile(SearchFileName))); + container.InnerHtml += label.ToString(); + } + + if (showSite) + { + var radio = new TagBuilder("input"); + radio.Attributes["type"] = "radio"; + radio.Attributes["name"] = "SearchType"; + radio.Attributes["value"] = "S"; + radio.Attributes["id"] = "dnn_" + id + "SiteRadioButton"; + radio.Attributes["class"] = cssClass; + container.InnerHtml += radio.ToString(TagRenderMode.SelfClosing); + + var label = new TagBuilder("label"); + label.Attributes["for"] = "SiteRadioButton"; + label.SetInnerText(siteText ?? Localization.GetString("Site", GetSkinsResourceFile(SearchFileName))); + container.InnerHtml += label.ToString(); + } + + container.InnerHtml += BuildSearchInput(id, "txtSearch", "NormalTextBox"); + container.InnerHtml += BuildSearchButton(cssClass, submit, searchUrl); + + return new MvcHtmlString(container.ToString() + GetInitScript(false, enableWildSearch, minCharRequired, autoSearchDelayInMilliSecond, containerId, nonce)); + } + + private static MvcHtmlString BuildDropDownSearch( + string id, + string cssClass, + string submit, + string webText, + string siteText, + bool enableWildSearch, + int minCharRequired, + int autoSearchDelayInMilliSecond, + string searchUrl, + string nonce) + { + var container = new TagBuilder("div"); + container.GenerateId("dnn" + id + "DropDownSearch"); + container.AddCssClass("SearchContainer"); + var containerId = container.Attributes["id"]; + + var searchBorder = new TagBuilder("div"); + searchBorder.AddCssClass("SearchBorder"); + + var searchIcon = new TagBuilder("div"); + searchIcon.GenerateId("dnn" + id + "SearchIcon"); + searchIcon.AddCssClass("SearchIcon"); + + var img = new TagBuilder("img"); + img.Attributes["src"] = IconController.IconURL("Action"); + img.Attributes["alt"] = Localization.GetString("DropDownGlyph.AltText", GetSkinsResourceFile(SearchFileName)); + searchIcon.InnerHtml = img.ToString(TagRenderMode.SelfClosing); + + searchBorder.InnerHtml += searchIcon.ToString(); + searchBorder.InnerHtml += BuildSearchInput(id, "txtSearchNew", "SearchTextBox"); + + var choices = new TagBuilder("ul"); + choices.GenerateId("SearchChoices"); + + var siteLi = new TagBuilder("li"); + siteLi.GenerateId("dnn" + id + "_SearchIconSite"); + siteLi.SetInnerText(siteText ?? Localization.GetString("Site", GetSkinsResourceFile(SearchFileName))); + choices.InnerHtml += siteLi.ToString(); + + var webLi = new TagBuilder("li"); + webLi.GenerateId("SearchIconWeb"); + webLi.SetInnerText(webText ?? Localization.GetString("Web", GetSkinsResourceFile(SearchFileName))); + choices.InnerHtml += webLi.ToString(); + + searchBorder.InnerHtml += choices.ToString(); + container.InnerHtml = searchBorder.ToString() + BuildSearchButton(cssClass, submit, searchUrl); + + return new MvcHtmlString(container.ToString() + GetInitScript(true, enableWildSearch, minCharRequired, autoSearchDelayInMilliSecond, containerId, nonce)); + } + + private static string BuildSearchInput(string id, string inputId, string cssClass) + { + var container = new TagBuilder("span"); + container.AddCssClass("searchInputContainer"); + container.Attributes["data-moreresults"] = GetSeeMoreText(); + container.Attributes["data-noresult"] = GetNoResultText(); + + var input = new TagBuilder("input"); + input.Attributes["type"] = "text"; + input.Attributes["id"] = "dnn_" + id + "_" + inputId; + input.Attributes["class"] = cssClass; + input.Attributes["maxlength"] = "255"; + input.Attributes["autocomplete"] = "off"; + input.Attributes["placeholder"] = GetPlaceholderText(); + input.Attributes["aria-label"] = "Search"; + + var clear = new TagBuilder("a"); + clear.AddCssClass("dnnSearchBoxClearText"); + clear.Attributes["title"] = GetClearQueryText(); + clear.Attributes["href"] = "#"; + + container.InnerHtml = input.ToString(TagRenderMode.SelfClosing) + clear.ToString(); + return container.ToString(); + } + + private static string BuildSearchButton(string cssClass, string submit, string searchUrl) + { + var button = new TagBuilder("a"); + button.AddCssClass("SearchButton " + cssClass); + button.Attributes["href"] = searchUrl; // "#"; + button.InnerHtml = submit ?? Localization.GetString("Search", GetSkinsResourceFile(SearchFileName)); + return button.ToString(); + } + + private static string GetInitScript(bool useDropDownList, bool enableWildSearch, int minCharRequired, int autoSearchDelayInMilliSecond, string id, string nonce) + { + return string.Format( + @" + ", + autoSearchDelayInMilliSecond, + minCharRequired, + "S", + enableWildSearch.ToString().ToLowerInvariant(), + System.Threading.Thread.CurrentThread.CurrentCulture.ToString(), + PortalSettings.Current.PortalId, + useDropDownList ? "if (typeof dnn.initDropdownSearch != 'undefined') { dnn.initDropdownSearch(searchSkinObject); }" : string.Empty, + id, + nonce); + } + + private static string GetSeeMoreText() + { + return Localization.GetSafeJSString("SeeMoreResults", GetSkinsResourceFile(SearchFileName)); + } + + private static string GetNoResultText() + { + return Localization.GetSafeJSString("NoResult", GetSkinsResourceFile(SearchFileName)); + } + + private static string GetClearQueryText() + { + return Localization.GetSafeJSString("SearchClearQuery", GetSkinsResourceFile(SearchFileName)); + } + + private static string GetPlaceholderText() + { + return Localization.GetSafeJSString("Placeholder", GetSkinsResourceFile(SearchFileName)); + } + + private static string ExecuteSearchUrl(string searchText, string searchType, bool useWebForSite, string webURL, INavigationManager navigationManager) + { + PortalSettings portalSettings = PortalSettings.Current; + + int searchTabId = SkinHelpers.GetSearchTabId(portalSettings); + + if (searchTabId == Null.NullInteger) + { + return string.Empty; + } + + string strURL; + if (!string.IsNullOrEmpty(searchText)) + { + switch (searchType) + { + case "S": + // site + if (useWebForSite) + { + /* + strURL = this.SiteURL; + if (!string.IsNullOrEmpty(strURL)) + { + strURL = strURL.Replace("[TEXT]", this.Server.UrlEncode(searchText)); + strURL = strURL.Replace("[DOMAIN]", this.Request.Url.Host); + UrlUtils.OpenNewWindow(this.Page, this.GetType(), strURL); + } + */ + return string.Empty; + } + else + { + if (Host.UseFriendlyUrls) + { + return navigationManager.NavigateURL(searchTabId) /* + "?Search=" + this.Server.UrlEncode(searchText)*/; + } + else + { + return navigationManager.NavigateURL(searchTabId) /* + "&Search=" + this.Server.UrlEncode(searchText)*/; + } + } + + case "W": + // web + strURL = webURL; + if (!string.IsNullOrEmpty(strURL)) + { + /* + strURL = strURL.Replace("[TEXT]", this.Server.UrlEncode(searchText)); + strURL = strURL.Replace("[DOMAIN]", string.Empty); + UrlUtils.OpenNewWindow(this.Page, this.GetType(), strURL); + */ + } + + return string.Empty; + } + } + else + { + if (Host.UseFriendlyUrls) + { + return navigationManager.NavigateURL(searchTabId); + } + else + { + return navigationManager.NavigateURL(searchTabId); + } + } + + return string.Empty; + } + + private static int GetSearchTabId(PortalSettings portalSettings) + { + int searchTabId = portalSettings.SearchTabId; + if (searchTabId == Null.NullInteger) + { + ArrayList arrModules = ModuleController.Instance.GetModulesByDefinition(portalSettings.PortalId, "Search Results"); + if (arrModules.Count > 1) + { + foreach (ModuleInfo searchModule in arrModules) + { + if (searchModule.CultureCode == portalSettings.CultureCode) + { + searchTabId = searchModule.TabID; + } + } + } + else if (arrModules.Count == 1) + { + searchTabId = ((ModuleInfo)arrModules[0]).TabID; + } + } + + return searchTabId; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.SkinPartial.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.SkinPartial.cs new file mode 100644 index 00000000000..b87de47f19b --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.SkinPartial.cs @@ -0,0 +1,30 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + using System.Web.Mvc.Html; + using System.Web.WebPages.Html; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString SkinPartial(this HtmlHelper helper, string name = "") + { + var model = helper.ViewData.Model; + if (model == null) + { + throw new InvalidOperationException("The model need to be present."); + } + + var skinPath = Path.GetDirectoryName(model.Skin.SkinSrc); + return helper.Partial("~" + skinPath + "/Views/" + name + ".cshtml"); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Styles.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Styles.cs new file mode 100644 index 00000000000..5d8072da407 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Styles.cs @@ -0,0 +1,43 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.UI.Skins; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Styles(this HtmlHelper helper, string styleSheet, string condition = "", bool isFirst = false, bool useSkinPath = true, string media = "") + { + var skinPath = useSkinPath ? ((Skin)helper.ViewContext.Controller.ViewData["Skin"]).SkinPath : string.Empty; + var link = new TagBuilder("link"); + link.Attributes.Add("rel", "stylesheet"); + link.Attributes.Add("type", "text/css"); + link.Attributes.Add("href", skinPath + styleSheet); + if (!string.IsNullOrEmpty(media)) + { + link.Attributes.Add("media", media); + } + + if (string.IsNullOrEmpty(condition)) + { + return new MvcHtmlString(link.ToString()); + } + else + { + var openIf = new TagBuilder("span"); + openIf.InnerHtml = $""; + return new MvcHtmlString(openIf.ToString() + link.ToString() + closeIf.ToString()); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Tags.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Tags.cs new file mode 100644 index 00000000000..1d0001a80c5 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Tags.cs @@ -0,0 +1,36 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Tags(this HtmlHelper helper, string cssClass = "", string addImageUrl = "", string cancelImageUrl = "", string saveImageUrl = "", bool allowTagging = true, bool showCategories = true, bool showTags = true, string separator = ", ", string objectType = "Page", string repeatDirection = "Horizontal") + { + var portalSettings = PortalSettings.Current; + var tagsControl = new TagBuilder("dnn:tags"); + tagsControl.Attributes.Add("id", "tagsControl"); + tagsControl.Attributes.Add("runat", "server"); + tagsControl.Attributes.Add("CssClass", cssClass); + tagsControl.Attributes.Add("AddImageUrl", addImageUrl); + tagsControl.Attributes.Add("CancelImageUrl", cancelImageUrl); + tagsControl.Attributes.Add("SaveImageUrl", saveImageUrl); + tagsControl.Attributes.Add("AllowTagging", allowTagging.ToString()); + tagsControl.Attributes.Add("ShowCategories", showCategories.ToString()); + tagsControl.Attributes.Add("ShowTags", showTags.ToString()); + tagsControl.Attributes.Add("Separator", separator); + tagsControl.Attributes.Add("ObjectType", objectType); + tagsControl.Attributes.Add("RepeatDirection", repeatDirection); + + return new MvcHtmlString(tagsControl.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Terms.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Terms.cs new file mode 100644 index 00000000000..8cffefa10a5 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Terms.cs @@ -0,0 +1,50 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Terms(this HtmlHelper helper, string text = "", string cssClass = "SkinObject", string rel = "nofollow") + { + var navigationManager = helper.ViewData.Model.NavigationManager; + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + // Add Css Class + link.Attributes.Add("class", cssClass); + + // Add Text + if (string.IsNullOrWhiteSpace(text)) + { + text = Localization.GetString("Terms.Text", GetSkinsResourceFile("Terms.ascx")); + } + + link.SetInnerText(text); + + // Add Link + var href = portalSettings.TermsTabId == Null.NullInteger ? navigationManager.NavigateURL(portalSettings.ActiveTab.TabID, "Terms") : navigationManager.NavigateURL(portalSettings.TermsTabId); + link.Attributes.Add("href", href); + + // Add Rel + if (!string.IsNullOrWhiteSpace(rel)) + { + link.Attributes.Add("rel", rel); + } + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Text.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Text.cs new file mode 100644 index 00000000000..074e193a316 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Text.cs @@ -0,0 +1,51 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.IO; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Tokens; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Text(this HtmlHelper helper, string showText = "", string cssClass = "", string resourceKey = "", bool replaceTokens = false) + { + var portalSettings = PortalSettings.Current; + var text = showText; + + if (!string.IsNullOrEmpty(resourceKey)) + { + var file = Path.GetFileName(helper.ViewContext.HttpContext.Server.MapPath(portalSettings.ActiveTab.SkinSrc)); + file = portalSettings.ActiveTab.SkinPath + Localization.LocalResourceDirectory + "/" + file; + var localization = Localization.GetString(resourceKey, file); + if (!string.IsNullOrEmpty(localization)) + { + text = localization; + } + } + + if (replaceTokens) + { + var tr = new TokenReplace { AccessingUser = portalSettings.UserInfo }; + text = tr.ReplaceEnvironmentTokens(text); + } + + var label = new TagBuilder("span"); + label.SetInnerText(text); + if (!string.IsNullOrEmpty(cssClass)) + { + label.AddCssClass(cssClass); + } + + return new MvcHtmlString(label.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Toast.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Toast.cs new file mode 100644 index 00000000000..b31b2f34f04 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Toast.cs @@ -0,0 +1,28 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString Toast(this HtmlHelper helper, string cssClass = "SkinObject") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + link.Attributes.Add("href", portalSettings.PortalAlias.HTTPAlias); + link.SetInnerText(Localization.GetString("SeeAllMessage.Text", GetSkinsResourceFile("Toast.ascx"))); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.TreeViewMenu.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.TreeViewMenu.cs new file mode 100644 index 00000000000..a2371cd47bb --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.TreeViewMenu.cs @@ -0,0 +1,28 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString TreeViewMenu(this HtmlHelper helper, string cssClass = "SkinObject") + { + var portalSettings = PortalSettings.Current; + var link = new TagBuilder("a"); + + link.Attributes.Add("href", portalSettings.PortalAlias.HTTPAlias); + link.SetInnerText(Localization.GetString("Title.Text", GetSkinsResourceFile("TreeViewMenu.ascx"))); + + return new MvcHtmlString(link.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.User.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.User.cs new file mode 100644 index 00000000000..97fc67b1e13 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.User.cs @@ -0,0 +1,326 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Authentication; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Social.Messaging.Internal; + using DotNetNuke.Services.Social.Notifications; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + private static string userResourceFile = GetSkinsResourceFile("User.ascx"); + + public static IHtmlString User(this HtmlHelper helper, string cssClass = "SkinObject", string text = "", string url = "", bool showUnreadMessages = true, bool showAvatar = true, bool legacyMode = true, bool showInErrorPage = false) + { + var nonce = helper.ViewData.Model.ContentSecurityPolicy.Nonce; + var portalSettings = PortalSettings.Current; + var navigationManager = helper.ViewData.Model.NavigationManager; + + if (portalSettings.InErrorPageRequest() && !showInErrorPage) + { + return MvcHtmlString.Empty; + } + + var registerText = Localization.GetString("Register", userResourceFile); + if (!string.IsNullOrEmpty(text)) + { + registerText = text; + if (text.IndexOf("src=") != -1) + { + registerText = text.Replace("src=\"", "src=\"" + portalSettings.ActiveTab.SkinPath); + } + } + + if (legacyMode) + { + if (portalSettings.UserRegistration == (int)Globals.PortalRegistrationType.NoRegistration || + (portalSettings.Users > portalSettings.UserQuota && portalSettings.UserQuota != 0)) + { + return MvcHtmlString.Empty; + } + + var registerLink = new TagBuilder("a"); + registerLink.AddCssClass("dnnRegisterLink"); + if (!string.IsNullOrEmpty(cssClass)) + { + registerLink.AddCssClass(cssClass); + } + + registerLink.Attributes.Add("rel", "nofollow"); + registerLink.InnerHtml = registerText; + registerLink.Attributes.Add("href", !string.IsNullOrEmpty(url) ? url : Globals.RegisterURL(HttpUtility.UrlEncode(navigationManager.NavigateURL()), Null.NullString)); + + string registerScript = string.Empty; + if (portalSettings.EnablePopUps && portalSettings.RegisterTabId == Null.NullInteger && !AuthenticationController.HasSocialAuthenticationEnabled(null)) + { + // var clickEvent = "return " + UrlUtils.PopUpUrl(registerLink.Attributes["href"], portalSettings, true, false, 600, 950); + // registerLink.Attributes.Add("onclick", clickEvent); + registerScript = GetRegisterScript(registerLink.Attributes["href"], nonce); + } + + return new MvcHtmlString(registerLink.ToString() + registerScript); + } + else + { + string registerScript = string.Empty; + var userWrapperDiv = new TagBuilder("div"); + userWrapperDiv.AddCssClass("registerGroup"); + var ul = new TagBuilder("ul"); + ul.AddCssClass("buttonGroup"); + if (!HttpContext.Current.Request.IsAuthenticated) + { + // Unauthenticated User Logic + if (portalSettings.UserRegistration != (int)Globals.PortalRegistrationType.NoRegistration && + (portalSettings.Users < portalSettings.UserQuota || portalSettings.UserQuota == 0)) + { + // User Register + var registerLi = new TagBuilder("li"); + registerLi.AddCssClass("userRegister"); + + var registerLink = new TagBuilder("a"); + registerLink.AddCssClass("dnnRegisterLink"); + registerLink.AddCssClass(cssClass); + registerLink.Attributes.Add("rel", "nofollow"); + registerLink.InnerHtml = !string.IsNullOrEmpty(text) ? text.Replace("src=\"", "src=\"" + portalSettings.ActiveTab.SkinPath) : Localization.GetString("Register", userResourceFile); + registerLink.Attributes.Add("href", !string.IsNullOrEmpty(url) ? url : Globals.RegisterURL(HttpUtility.UrlEncode(navigationManager.NavigateURL()), Null.NullString)); + + if (portalSettings.EnablePopUps && portalSettings.RegisterTabId == Null.NullInteger/*&& !AuthenticationController.HasSocialAuthenticationEnabled(portalSettings)*/) + { + // var clickEvent = "return " + UrlUtils.PopUpUrl(registerLink.Attributes["href"], portalSettings, true, false, 600, 950); + // registerLink.Attributes.Add("onclick", clickEvent); + registerScript = GetRegisterScript(registerLink.Attributes["href"], nonce); + } + + registerLi.InnerHtml = registerLink.ToString(); + ul.InnerHtml += registerLi.ToString(); + } + } + else + { + var userInfo = UserController.Instance.GetCurrentUserInfo(); + if (userInfo.UserID != -1) + { + // Add menu-items (viewProfile, userMessages, userNotifications, etc.) + if (showUnreadMessages) + { + // Create Messages + var unreadMessages = InternalMessagingController.Instance.CountUnreadMessages(userInfo.UserID, PortalController.GetEffectivePortalId(userInfo.PortalID)); + + var messageLinkText = unreadMessages > 0 ? string.Format(Localization.GetString("Messages", userResourceFile), unreadMessages) : string.Format(Localization.GetString("NoMessages", userResourceFile)); + ul.InnerHtml += CreateMenuItem(messageLinkText, "userMessages", navigationManager.NavigateURL(GetMessageTab(portalSettings))); + + // Create Notifications + var unreadAlerts = NotificationsController.Instance.CountNotifications(userInfo.UserID, PortalController.GetEffectivePortalId(userInfo.PortalID)); + var alertLink = navigationManager.NavigateURL(GetMessageTab(portalSettings), string.Empty, string.Format("userId={0}", userInfo.UserID), "view=notifications", "action=notifications"); + var alertLinkText = unreadAlerts > 0 ? string.Format(Localization.GetString("Notifications", userResourceFile), unreadAlerts) : string.Format(Localization.GetString("NoNotifications", userResourceFile)); + + ul.InnerHtml += CreateMenuItem(alertLinkText, "userNotifications", alertLink); + } + + // Create User Display Name Link + var userDisplayText = userInfo.DisplayName; + var userDisplayTextUrl = Globals.UserProfileURL(userInfo.UserID); + var userDisplayTextToolTip = Localization.GetString("VisitMyProfile", userResourceFile); + + ul.InnerHtml += CreateMenuItem(userDisplayText, "userDisplayName", userDisplayTextUrl); + + if (showAvatar) + { + var userProfileLi = new TagBuilder("li"); + userProfileLi.AddCssClass("userProfile"); + + // Get the Profile Image + var profileImg = new TagBuilder("img"); + profileImg.Attributes.Add("src", UserController.Instance.GetUserProfilePictureUrl(userInfo.UserID, 32, 32)); + profileImg.Attributes.Add("alt", Localization.GetString("ProfilePicture", userResourceFile)); + + ul.InnerHtml += CreateMenuItem(profileImg.ToString(), "userProfileImg", userDisplayTextUrl); + } + } + } + + userWrapperDiv.InnerHtml = ul.ToString(); + return new MvcHtmlString(userWrapperDiv.ToString() + registerScript); + } + } + + private static string CreateMenuItem(string cssClass, string href, string resourceKey, bool isStrong = false) + { + var li = new TagBuilder("li"); + li.AddCssClass(cssClass); + + var a = new TagBuilder("a"); + a.Attributes.Add("href", href); + var text = Localization.GetString(resourceKey, userResourceFile); + a.InnerHtml = isStrong ? $"{text}" : text; + + li.InnerHtml = a.ToString(); + return li.ToString(); + } + + private static string CreateMenuItem(string text, string cssClass, string href) + { + var li = new TagBuilder("li"); + li.AddCssClass(cssClass); + + var a = new TagBuilder("a"); + a.Attributes.Add("href", href); + + a.InnerHtml += text; + + li.InnerHtml = a.ToString(); + return li.ToString(); + } + + private static string CreateMessageMenuItem(string cssClass, string href, string resourceKey, int count) + { + var li = new TagBuilder("li"); + li.AddCssClass(cssClass); + + var a = new TagBuilder("a"); + a.Attributes.Add("href", href); + + if (count > 0 || AlwaysShowCount(PortalSettings.Current)) + { + var span = new TagBuilder("span"); + span.AddCssClass(cssClass == "userMessages" ? "messageCount" : "notificationCount"); + span.InnerHtml = count.ToString(); + a.InnerHtml = span.ToString(); + } + + var innerText = Localization.GetString(resourceKey, userResourceFile); + innerText = string.Format(innerText, count.ToString()); + a.InnerHtml += innerText; + + li.InnerHtml = a.ToString(); + return li.ToString(); + } + + private static int GetMessageTab(PortalSettings portalSettings) + { + var cacheKey = string.Format("MessageCenterTab:{0}:{1}", portalSettings.PortalId, portalSettings.CultureCode); + var messageTabId = DataCache.GetCache(cacheKey); + if (messageTabId > 0) + { + return messageTabId; + } + + // Find the Message Tab + messageTabId = FindMessageTab(portalSettings); + + // save in cache + DataCache.SetCache(cacheKey, messageTabId, TimeSpan.FromMinutes(20)); + + return messageTabId; + } + + private static int FindMessageTab(PortalSettings portalSettings) + { + var profileTab = TabController.Instance.GetTab(portalSettings.UserTabId, portalSettings.PortalId, false); + if (profileTab != null) + { + var childTabs = TabController.Instance.GetTabsByPortal(profileTab.PortalID).DescendentsOf(profileTab.TabID); + foreach (TabInfo tab in childTabs) + { + foreach (KeyValuePair kvp in ModuleController.Instance.GetTabModules(tab.TabID)) + { + var module = kvp.Value; + if (module.DesktopModule.FriendlyName == "Message Center" && !module.IsDeleted) + { + return tab.TabID; + } + } + } + } + + // default to User Profile Page + return portalSettings.UserTabId; + } + + private static bool AlwaysShowCount(PortalSettings portalSettings) + { + const string SettingKey = "UserAndLogin_AlwaysShowCount"; + var alwaysShowCount = false; + + var portalSetting = PortalController.GetPortalSetting(SettingKey, portalSettings.PortalId, string.Empty); + if (!string.IsNullOrEmpty(portalSetting) && bool.TryParse(portalSetting, out alwaysShowCount)) + { + return alwaysShowCount; + } + + var hostSetting = HostController.Instance.GetString(SettingKey, string.Empty); + if (!string.IsNullOrEmpty(hostSetting) && bool.TryParse(hostSetting, out alwaysShowCount)) + { + return alwaysShowCount; + } + + return false; + } + + private static string GetRegisterScript(string loginUrl, string nonce) + { + var portalSettings = PortalSettings.Current; + var request = HttpContext.Current.Request; + + if (!request.IsAuthenticated) + { + var script = string.Format( + @" + "; + + return script; + } + + return string.Empty; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.UserAndLogin.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.UserAndLogin.cs new file mode 100644 index 00000000000..0c7cc531936 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.UserAndLogin.cs @@ -0,0 +1,179 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Authentication; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Social.Messaging.Internal; + using DotNetNuke.Services.Social.Notifications; + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString UserAndLogin(this HtmlHelper helper, bool showInErrorPage = false) + { + var portalSettings = PortalSettings.Current; + var navigationManager = helper.ViewData.Model.NavigationManager; + + if (!showInErrorPage && portalSettings.InErrorPageRequest()) + { + return MvcHtmlString.Empty; + } + + var sb = new StringBuilder(); + sb.Append("
    "); + + var result = sb.ToString(); + + if (UsePopUp(portalSettings)) + { + result = result.Replace("id=\"registerLink\"", $"id=\"registerLink\" onclick=\"{RegisterUrlForClickEvent(navigationManager, portalSettings, helper)}\""); + result = result.Replace("id=\"loginLink\"", $"id=\"loginLink\" onclick=\"{LoginUrlForClickEvent(portalSettings, helper)}\""); + } + + return new MvcHtmlString(result); + } + + private static bool CanRegister(PortalSettings portalSettings) + { + return (portalSettings.UserRegistration != (int)Globals.PortalRegistrationType.NoRegistration) + && (portalSettings.Users < portalSettings.UserQuota || portalSettings.UserQuota == 0); + } + + private static string RegisterUrl(INavigationManager navigationManager) + { + return Globals.RegisterURL(HttpUtility.UrlEncode(navigationManager.NavigateURL()), Null.NullString); + } + + private static string LoginUrl() + { + string returnUrl = HttpContext.Current.Request.RawUrl; + if (returnUrl.IndexOf("?returnurl=", StringComparison.OrdinalIgnoreCase) != -1) + { + returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?returnurl=", StringComparison.OrdinalIgnoreCase)); + } + + returnUrl = HttpUtility.UrlEncode(returnUrl); + + return Globals.LoginURL(returnUrl, HttpContext.Current.Request.QueryString["override"] != null); + } + + private static bool UsePopUp(PortalSettings portalSettings) + { + return portalSettings.EnablePopUps + && portalSettings.LoginTabId == Null.NullInteger + /* && !AuthenticationController.HasSocialAuthenticationEnabled(portalSettings)*/; + } + + private static string RegisterUrlForClickEvent(INavigationManager navigationManager, PortalSettings portalSettings, HtmlHelper helper) + { + return "return " + UrlUtils.PopUpUrl(HttpUtility.UrlDecode(RegisterUrl(navigationManager)), portalSettings, true, false, 600, 950); + } + + private static string LoginUrlForClickEvent(PortalSettings portalSettings, HtmlHelper helper) + { + return "return " + UrlUtils.PopUpUrl(HttpUtility.UrlDecode(LoginUrl()), portalSettings, true, false, 300, 650); + } + + private static string LocalizeString(HtmlHelper helper, string key) + { + return Localization.GetString(key, GetSkinsResourceFile("UserAndLogin.ascx")); + } + + /* + private static int GetMessageTab(PortalSettings portalSettings) + { + var cacheKey = $"MessageCenterTab:{portalSettings.PortalId}:{portalSettings.CultureCode}"; + var messageTabId = DataCache.GetCache(cacheKey); + if (messageTabId > 0) + { + return messageTabId; + } + + messageTabId = FindMessageTab(portalSettings); + DataCache.SetCache(cacheKey, messageTabId, TimeSpan.FromMinutes(20)); + + return messageTabId; + } + private static int FindMessageTab(PortalSettings portalSettings) + { + var profileTab = TabController.Instance.GetTab(portalSettings.UserTabId, portalSettings.PortalId, false); + if (profileTab != null) + { + var childTabs = TabController.Instance.GetTabsByPortal(profileTab.PortalID).DescendentsOf(profileTab.TabID); + foreach (TabInfo tab in childTabs) + { + foreach (KeyValuePair kvp in ModuleController.Instance.GetTabModules(tab.TabID)) + { + var module = kvp.Value; + if (module.DesktopModule.FriendlyName == "Message Center" && !module.IsDeleted) + { + return tab.TabID; + } + } + } + } + + return portalSettings.UserTabId; + } + */ + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.cs new file mode 100644 index 00000000000..1f077e5ba95 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.cs @@ -0,0 +1,24 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + + using DotNetNuke.Services.Localization; + + public static partial class SkinHelpers + { + public static string GetResourceFile(string templateSourceDirectory, string fileName) + { + return templateSourceDirectory + "/" + Localization.LocalResourceDirectory + "/" + fileName; + } + + public static string GetSkinsResourceFile(string fileName) + { + return GetResourceFile("/admin/Skins", fileName); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.jQuery.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.jQuery.cs new file mode 100644 index 00000000000..bf73d26c062 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.jQuery.cs @@ -0,0 +1,39 @@ +// 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.Web.MvcPipeline.Skins +{ + using System; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Models; + + public static partial class SkinHelpers + { + public static IHtmlString JQuery(this HtmlHelper helper, bool dnnjQueryPlugins = false, bool jQueryHoverIntent = false, bool jQueryUI = false) + { + var script = new TagBuilder("script"); + script.Attributes.Add("src", "~/Resources/Shared/Scripts/jquery/jquery.js"); + script.Attributes.Add("type", "text/javascript"); + + if (dnnjQueryPlugins) + { + script.InnerHtml += ""; + } + + if (jQueryHoverIntent) + { + script.InnerHtml += ""; + } + + if (jQueryUI) + { + script.InnerHtml += ""; + } + + return new MvcHtmlString(script.ToString()); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs new file mode 100644 index 00000000000..b282091a9e2 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs @@ -0,0 +1,29 @@ +// 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.Web.MvcPipeline +{ + using DotNetNuke.Common; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.DependencyInjection; + using DotNetNuke.Web.Mvc.Extensions; + using Microsoft.Extensions.DependencyInjection; + using System.Web.Mvc; + + public class Startup : IDnnStartup + { + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddMvcControllers(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + DependencyResolver.SetResolver(new DnnMvcPipelineDependencyResolver(Globals.DependencyProvider)); + services.AddScoped(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/dnn.json b/DNN Platform/DotNetNuke.Web.MvcPipeline/dnn.json new file mode 100644 index 00000000000..c6e3d864a5a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/dnn.json @@ -0,0 +1,16 @@ +{ + "projectType": "library", + "name": "Dnn_DotNetNukeWebMvcPipeline", + "friendlyName": "Dnn DotNetNukeWebMvcPipeline", + "description": "Dnn DotNetNukeWebMvcPipeline Library", + "packageName": "Dnn_DotNetNukeWebMvcPipeline", + "folder": "Dnn/DotNetNukeWebMvcPipeline", + "library": {}, + "pathsAndFiles": { + "pathToAssemblies": "./bin", + "pathToScripts": "./Server/SqlScripts", + "assemblies": ["DotNetNuke.Web.MvcPipeline.dll"], + "releaseFiles": [], + "zipName": "Dnn.DotNetNukeWebMvcPipeline" + } +} diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 49ecd36d279..d0e48f079de 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -276,6 +276,7 @@ + @@ -307,6 +308,8 @@ + + @@ -700,6 +703,7 @@ + diff --git a/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs b/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs new file mode 100644 index 00000000000..37f5dc3ac71 --- /dev/null +++ b/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs @@ -0,0 +1,3224 @@ +// 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.Web.MvcPipeline.Integration.Entities.Urls +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Security.Principal; + using System.Text.RegularExpressions; + using System.Threading; + using System.Web; + using System.Web.Configuration; + using System.Web.Security; + + using DotNetNuke.Application; + using DotNetNuke.Common; + using DotNetNuke.Common.Internal; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Urls; + using DotNetNuke.Framework; + using DotNetNuke.Services.EventQueue; + + public class MvcAdvancedUrlRewriter : UrlRewriterBase + { + private const string ProductName = "AdvancedUrlRewriter"; + private static readonly Regex DefaultPageRegex = new Regex(@"(?.[^&]+)=$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); + private static readonly Regex UrlSlashesRegex = new Regex("[\\\\/]\\.\\.[\\\\/]", RegexOptions.Compiled); + private static readonly Regex AliasUrlRegex = new Regex(@"(?:^(?http[s]{0,1}://){0,1})(?:(?_ALIAS_)(?$|\?[\w]*|/[\w]*))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); + private FriendlyUrlSettings settings; + + public void ProcessTestRequestWithContext( + HttpContext context, + Uri requestUri, + bool useFriendlyUrls, + UrlAction result, + FriendlyUrlSettings settings) + { + Guid parentTraceId = Guid.Empty; + this.settings = settings; + this.ProcessRequest( + context, + requestUri, + useFriendlyUrls, + result, + settings, + false, + parentTraceId); + } + + internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) + { + bool mvcCtl = false; + var skinSrc = string.Empty; + + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; + if (string.IsNullOrEmpty(skinSrc)) + { + skinSrc = PortalSettings.Current.DefaultPortalSkin; + } + } + } + + if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) + { + var tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + skinSrc = tab.SkinSrc; + } + } + + if (!string.IsNullOrEmpty(skinSrc)) + { + mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); + } + + /* + var mvcCtls = new[] { "Module", "Terms", "Privacy" }; + bool mvcSkin = false; + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + mvcSkin = !string.IsNullOrEmpty(PortalSettings.Current.ActiveTab.SkinSrc) && + PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); + } + } + + if (result.RewritePath.Contains("&ctl=")) + { + foreach (var item in mvcCtls) + { + mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); + } + + if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); + } + } + else + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = result.RawUrl.EndsWith("mvc"); + } + + mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; + mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; + */ + + return mvcCtl; + } + + internal static void RewriteAsChildAliasRoot( + HttpContext context, + UrlAction result, + string aliasQueryString, + FriendlyUrlSettings settings) + { + string culture = null; + + // look for specific alias to rewrite language parameter + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (result.PortalId > -1 && result.HttpAlias != null) + { + culture = primaryAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); + } + + if (string.IsNullOrEmpty(culture)) + { + // 732 : when no culture returned can be "" as well as null : no culture causes no rewrite, which results in redirect to parent alias + // set the default culture code here + // 735 : switch to custom method for getting portal + PortalInfo pi = CacheController.GetPortal(result.PortalId, false); + if (pi != null) + { + culture = pi.DefaultLanguage; + } + } + + if (!string.IsNullOrEmpty(culture)) + { + // a culture was identified for the alias root + if (RewriteController.AddLanguageCodeToRewritePath(ref aliasQueryString, culture)) + { + result.CultureCode = culture; + } + + result.DoRewrite = true; + result.RewritePath = "~/" + Globals.glbDefaultPage + aliasQueryString; + + // the expected /default.aspx path (defaultPageUrl) matches the requested Url (/default.aspx) + if (context != null) + { + // only do if not testing + RewriterUtils.RewriteUrl(context, result.RewritePath); + } + } + } + + internal static bool CheckForChildPortalRootUrl(string requestUrl, UrlAction result, out string aliasQueryString) + { + bool isChildPortalRootUrl = false; + + // what we are going to test for here is that if this is a child portal request, for the /default.aspx of the child portal + // then we are going to avoid the core 302 redirect to ?alias=portalALias by rewriting to the /default.aspx of the site root + // 684 : don't convert querystring items to lower case + // do the check by constructing what a child alias url would look like and compare it with the requested urls + // 912 : when requested without a valid portal alias, portalALias is null. Refuse and return false. + aliasQueryString = null; + if (result.PortalAlias != null && result.PortalAlias.HTTPAlias != null) + { + string defaultPageUrl = result.Scheme + result.PortalAlias.HTTPAlias + "/" + + Globals.glbDefaultPage.ToLowerInvariant(); // child alias Url with /default.aspx + + // 660 : look for a querystring on the site root for a child portal, and handle it if so + if (string.CompareOrdinal(requestUrl.ToLowerInvariant(), defaultPageUrl) == 0) + { + // exact match : that's the alias root + isChildPortalRootUrl = true; + aliasQueryString = string.Empty; + } + + if (!isChildPortalRootUrl && requestUrl.Contains("?")) + { + // is we didn't get an exact match but there is a querystring, then investigate + string[] requestUrlParts = requestUrl.Split('?'); + if (requestUrlParts.GetUpperBound(0) > 0) + { + string rootPart = requestUrlParts[0]; + string queryString = requestUrlParts[1]; + if (string.Compare(rootPart, defaultPageUrl, StringComparison.OrdinalIgnoreCase) == 0) + { + // rewrite, but put in the querystring on the rewrite path + isChildPortalRootUrl = true; + aliasQueryString = "?" + queryString; + + // 674: check for 301 if this value is a tabid/xx - otherwise the url will just evaluate as is + if (queryString.ToLowerInvariant().StartsWith("tabid=")) + { + result.Action = ActionType.CheckFor301; + } + } + } + } + } + + return isChildPortalRootUrl; + } + + /// Make sure any redirect to the site root doesn't append the nasty /default.aspx on the end. + /// + /// + /// without at the end. + internal static string CheckForSiteRootRedirect(string alias, string destUrl) + { + // 540 - don't append /default.aspx onto the end of a site root redirect. + if (destUrl.EndsWith(alias + "/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // this is just the portal alias root + /defualt.aspx. + // we don't want that, just the portalAliasRoot + "/" + string aliasPlusSlash = alias + "/"; + + // get everything up to the end of the portal alias + destUrl = destUrl.Substring(0, destUrl.IndexOf(aliasPlusSlash, StringComparison.Ordinal) + aliasPlusSlash.Length); + } + + return destUrl; + } + + /// + internal override void RewriteUrl(object sender, EventArgs e) + { + Guid parentTraceId = Guid.Empty; + const bool debug = true; + bool failedInitialization = false; + bool ignoreForInstall = false; + var app = (HttpApplication)sender; + try + { + // 875 : completely ignore install/upgrade requests immediately + ignoreForInstall = IgnoreRequestForInstall(app.Request); + + if (ignoreForInstall == false) + { + this.settings = new FriendlyUrlSettings(-1); + + this.SecurityCheck(app); + } + } + catch (Exception ex) + { + // exception handling for advanced Url Rewriting requests + failedInitialization = true; + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + if (app.Context != null) + { + ShowDebugData(app.Context, app.Request.Url.AbsoluteUri, null, ex); + var action = new UrlAction(app.Request) { Action = ActionType.Output404 }; + Handle404OrException(this.settings, app.Context, ex, action, false, debug); + } + else + { + throw; + } + } + + if (!failedInitialization && !ignoreForInstall) + { + // if made it through there and not installing, go to next call. Not in exception catch because it implements it's own top-level exception handling + var request = app.Context.Request; + + // 829 : change constructor to stop using physical path + var result = new UrlAction(request) + { + IsSecureConnection = request.IsSecureConnection, + IsSSLOffloaded = UrlUtils.IsSslOffloadEnabled(request), + RawUrl = request.RawUrl, + }; + this.ProcessRequest( + app.Context, + app.Context.Request.Url, + Host.UseFriendlyUrls, + result, + this.settings, + true, + parentTraceId); + } + } + + protected bool IsPortalAliasIncorrect( + HttpContext context, + HttpRequest request, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + Guid parentTraceId, + out string httpAlias) + { + // now check to make sure it's the primary portal alias for this portal/language/browser + bool incorrectAlias = false; + httpAlias = null; + + // if (result.RedirectAllowed && result.PortalId > -1) + if (result.PortalId > -1) + { + // portal has been identified + var portalAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + // if we're not on the primary alias, and portalaliasmapping is set to redirect, we might need to be redirected + var redirectToPrimary = !result.PortalAlias.IsPrimary && result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect; + + // forceAlias used in querystring? + var forceAliasInQueryString = queryStringCol != null && queryStringCol["forceAlias"] != null && queryStringCol["forceAlias"] != "true"; + if (redirectToPrimary || forceAliasInQueryString) + { + if (portalAliases.Count > 0) + { + string checkAlias = result.HttpAlias; + bool continueLoop = true; + bool triedWWW = false; + while (httpAlias == null && continueLoop) + { + if (portalAliases.ContainsAlias(result.PortalId, checkAlias)) + { + if (portalAliases.Count > 0) + { + // var cpa = portalAliases.GetAliasByPortalIdAndSettings(result); + string url = requestUri.ToString(); + RewriteController.CheckLanguageMatch(ref url, result); + var cpa = portalAliases + .Where(a => a.IsPrimary || result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) + .GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, result.BrowserType); + + if (cpa != null) + { + httpAlias = cpa.HTTPAlias; + continueLoop = false; + } + + if (string.IsNullOrEmpty(result.CultureCode) && cpa == null) + { + // if there is a specific culture for this portal alias, then check that + string culture = portalAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); + + // if this matches the alias of the request, then we know we have the correct alias because it is a specific culture + if (!string.IsNullOrEmpty(culture)) + { + continueLoop = false; + } + } + } + } + + // check whether to still go on or not + if (continueLoop) + { + // this alias doesn't exist in the list + // check if it has a www on it - if not, try adding, if it does, try removing + if (!triedWWW) + { + triedWWW = true; // now tried adding/removing www + if (checkAlias.StartsWith("www.", StringComparison.InvariantCultureIgnoreCase)) + { + checkAlias = checkAlias.Substring(4); + } + else + { + checkAlias = "www." + checkAlias; + } + } + else + { + // last thing to try, get the default language and see if there is a portal alias for that + // thus, any aliases not identified as belonging to a language are redirected back to the + // alias named for the default language + continueLoop = false; + + // 735 : switch to custom method for getting portal + PortalInfo pi = CacheController.GetPortal(result.PortalId, false); + if (pi != null) + { + string cultureCode = pi.DefaultLanguage; + if (!string.IsNullOrEmpty(cultureCode)) + { + var primaryPortalAlias = portalAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, cultureCode, settings); + if (primaryPortalAlias != null) + { + httpAlias = primaryPortalAlias.HTTPAlias; + } + } + } + } + } + } + } + + // check to see if it is a custom tab alais - in that case, it is allowed to be requested for the tab + if (CheckIfAliasIsCustomTabAlias(ref result, httpAlias, settings)) + { + // change the primary alias to the custom tab alias that has been requested. + result.PrimaryAlias = result.PortalAlias; + } + else + if (httpAlias != null && string.Compare(httpAlias, result.HttpAlias, StringComparison.OrdinalIgnoreCase) != 0) + { + incorrectAlias = true; + } + } + } + + return incorrectAlias; + } + + private static void ShowDebugData(HttpContext context, string requestUri, UrlAction result, Exception ex) + { + if (context != null) + { + HttpResponse response = context.Response; + + // handle null responses wherever they might be found - this routine must be tolerant to all kinds of invalid inputs + if (requestUri == null) + { + requestUri = "null Uri"; + } + + string finalUrl = "null final Url"; + string rewritePath = "null rewrite path"; + string action = "null action"; + if (result != null) + { + finalUrl = result.FinalUrl; + action = result.Action.ToString(); + rewritePath = result.RewritePath; + } + + // format up the error message to show + const string debugMsg = "{0}, {1}, {2}, {3}, {4}, {5}, {6}"; + string productVer = DotNetNukeContext.Current.Application.Version.ToString(); + string portalSettings = string.Empty; + string browser = "Unknown"; + + // 949 : don't rely on 'result' being non-null + if (result != null) + { + browser = result.BrowserType.ToString(); + } + + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + portalSettings = ps.PortalId.ToString(); + if (ps.PortalAlias != null) + { + portalSettings += ":" + ps.PortalAlias.HTTPAlias; + } + } + } + + response.AppendHeader( + "X-" + ProductName + "-Debug", + string.Format( + debugMsg, + requestUri, + finalUrl, + rewritePath, + action, + productVer, + portalSettings, + browser)); + int msgNum = 1; + if (result != null) + { + foreach (string msg in result.DebugMessages) + { + response.AppendHeader("X-" + ProductName + "-Debug-" + msgNum.ToString("00"), msg); + msgNum++; + } + } + + if (ex != null) + { + response.AppendHeader("X-" + ProductName + "-Ex", ex.Message); + } + } + } + + private static void Handle404OrException(FriendlyUrlSettings settings, HttpContext context, Exception ex, UrlAction result, bool transfer, bool showDebug) + { + // handle Auto-Add Alias + if (result.Action == ActionType.Output404 && CanAutoAddPortalAlias()) + { + // Need to determine if this is a real 404 or a possible new alias. + var portalId = Host.HostPortalID; + if (portalId > Null.NullInteger) + { + if (string.IsNullOrEmpty(result.DomainName)) + { + result.DomainName = Globals.GetDomainName(context.Request); // parse the domain name out of the request + } + + // Get all the existing aliases + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(portalId).ToList(); + + bool autoaddAlias; + bool isPrimary = false; + if (!aliases.Any()) + { + autoaddAlias = true; + isPrimary = true; + } + else + { + autoaddAlias = true; + foreach (var alias in aliases) + { + if (result.DomainName.ToLowerInvariant().IndexOf(alias.HTTPAlias, StringComparison.Ordinal) == 0 + && result.DomainName.Length >= alias.HTTPAlias.Length) + { + autoaddAlias = false; + break; + } + } + } + + if (autoaddAlias) + { + var portalAliasInfo = new PortalAliasInfo + { + PortalID = portalId, + HTTPAlias = result.DomainName, + IsPrimary = isPrimary, + }; + PortalAliasController.Instance.AddPortalAlias(portalAliasInfo); + + context.Response.Redirect(context.Request.Url.ToString(), true); + } + } + } + + if (context != null) + { + HttpRequest request = context.Request; + HttpResponse response = context.Response; + HttpServerUtility server = context.Server; + + const string errorPageHtmlHeader = @"{0}"; + const string errorPageHtmlFooter = @""; + var errorPageHtml = new StringWriter(); + CustomErrorsSection ceSection = null; + + // 876 : security catch for custom error reading + try + { + ceSection = (CustomErrorsSection)WebConfigurationManager.GetSection("system.web/customErrors"); + } + + // ReSharper disable once EmptyGeneralCatchClause + catch (Exception) + { + // on some medium trust environments, this will throw an exception for trying to read the custom Errors + // do nothing + } + + /* 454 new 404/500 error handling routine */ + bool useDNNTab = false; + int errTabId = -1; + string errUrl = null; + string status = string.Empty; + bool isPostback = false; + if (settings != null) + { + if (request.RequestType == "POST") + { + isPostback = true; + } + + if (result != null && ex != null) + { + result.DebugMessages.Add("Exception: " + ex.Message); + result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); + if (ex.InnerException != null) + { + result.DebugMessages.Add("Inner Ex : " + ex.InnerException.Message); + result.DebugMessages.Add("Stack Trace: " + ex.InnerException.StackTrace); + } + else + { + result.DebugMessages.Add("Inner Ex : null"); + } + } + + string errRH; + string errRV; + int statusCode; + if (result != null && result.Action != ActionType.Output404) + { + // output everything but 404 (usually 500) + if (settings.TabId500 > -1) + { + // tabid specified for 500 error page, use that + useDNNTab = true; + errTabId = settings.TabId500; + } + + errUrl = settings.Url500; + errRH = "X-UrlRewriter-500"; + errRV = "500 Rewritten to {0} : {1}"; + statusCode = 500; + status = "500 Internal Server Error"; + } + else + { + // output 404 error + // if the tabid is specified for a 404 page, then use that + if (settings.TabId404 > -1) + { + useDNNTab = true; + errTabId = settings.TabId404; + } + + // with 404 errors, there's an option to catch certain urls and use an external url for extra processing. + if (!string.IsNullOrEmpty(settings.Regex404)) + { + try + { + // 944 : check the original Url in case the requested Url has been rewritten before discovering it's a 404 error + string requestedUrl = request.Url.ToString(); + if (result != null && !string.IsNullOrEmpty(result.OriginalPath)) + { + requestedUrl = result.OriginalPath; + } + + if (Regex.IsMatch(requestedUrl, settings.Regex404, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + useDNNTab = false; + + // if we have a match in the 404 regex value, then don't use the tabid + } + } + catch (Exception regexEx) + { + // .some type of exception : output in response header, and go back to using the tabid + response.AppendHeader("X-UrlRewriter-404Exception", regexEx.Message); + } + } + + errUrl = settings.Url404; + errRH = "X-UrlRewriter-404"; + errRV = "404 Rewritten to {0} : {1} : Reason {2}"; + status = "404 Not Found"; + statusCode = 404; + } + + // check for 404 logging + if (result == null || result.Action == ActionType.Output404) + { + // Log 404 errors to Event Log + UrlRewriterUtils.Log404(request, settings, result); + } + + // 912 : use unhandled 404 switch + string reason404 = null; + bool unhandled404 = true; + if (useDNNTab && errTabId > -1) + { + unhandled404 = false; // we're handling it here + TabInfo errTab = TabController.Instance.GetTab(errTabId, result.PortalId, true); + if (errTab != null) + { + bool redirect = false; + + // ok, valid tabid. what we're going to do is to load up this tab via a rewrite of the url, and then change the output status + string reason = "Not Found"; + if (result != null) + { + reason = result.Reason.ToString(); + } + + response.AppendHeader( + errRH, + string.Format( + errRV, + "DNN Tab", + errTab.TabName + "(Tabid:" + errTabId.ToString() + ")", + reason)); + + // show debug messages even if in debug mode + if (context != null && response != null && result != null && showDebug) + { + ShowDebugData(context, result.OriginalPath, result, null); + } + + if (!isPostback) + { + response.ClearContent(); + response.StatusCode = statusCode; + response.Status = status; + } + else + { + redirect = true; + + // redirect postbacks as you can't postback successfully to a server.transfer + } + + errUrl = Globals.glbDefaultPage + TabIndexController.CreateRewritePath(errTab.TabID, string.Empty); + + // have to update the portal settings with the new tabid + PortalSettings ps = null; + if (context != null && context.Items != null) + { + if (context.Items.Contains("PortalSettings")) + { + ps = (PortalSettings)context.Items["PortalSettings"]; + context.Items.Remove("PortalSettings"); // nix it from the context + } + } + + if (ps != null && ps.PortalAlias != null) + { + ps = new PortalSettings(errTabId, ps.PortalAlias); + } + else + { + if (result.HttpAlias != null && result.PortalId > -1) + { + PortalAliasInfo pa = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias, result.PortalId); + ps = new PortalSettings(errTabId, pa); + } + else + { + // 912 : handle 404 when no valid portal can be identified + // results when iis is configured to handle portal alias, but + // DNN isn't. This always returns 404 because a multi-portal site + // can't just show the 404 page of the host site. + ArrayList portals = PortalController.Instance.GetPortals(); + if (portals != null && portals.Count == 1) + { + // single portal install, load up portal settings for this portal + var singlePortal = (PortalInfo)portals[0]; + + // list of aliases from database + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(singlePortal.PortalID).ToList(); + + // list of aliases from Advanced Url settings + List chosen = aliases.GetAliasesForPortalId(singlePortal.PortalID); + PortalAliasInfo useFor404 = null; + + // go through all aliases and either get the first valid one, or the first + // as chosen in the advanced url management settings + foreach (var pa in aliases) + { + if (useFor404 == null) + { + useFor404 = pa; // first one by default + } + + // matching? + if (chosen != null && chosen.Count > 0) + { + if (chosen.Contains(pa.HTTPAlias)) + { + useFor404 = pa; + } + } + else + { + break; // no further checking + } + } + + // now configure that as the portal settings + if (useFor404 != null) + { + // create portal settings context for identified portal alias in single portal install + ps = new PortalSettings(errTabId, useFor404); + } + } + else + { + reason404 = "Requested domain name is not configured as valid website"; + unhandled404 = true; + } + } + } + + if (ps != null) + { + // re-add the context items portal settings back in + context.Items.Add("PortalSettings", ps); + } + + if (redirect) + { + errUrl = TestableGlobals.Instance.NavigateURL(); + response.Redirect(errUrl, true); // redirect and end response. + + // It will mean the user will have to postback again, but it will work the second time + } + else + { + if (transfer) + { + // execute a server transfer to the default.aspx?tabid=xx url + // 767 : object not set error on extensionless 404 errors + if (context.User == null) + { + context.User = GetCurrentPrincipal(context); + } + + response.TrySkipIisCustomErrors = true; + + // 881 : spoof the basePage object so that the client dependency framework + // is satisfied it's working with a page-based handler + IHttpHandler spoofPage = new CDefault(); + context.Handler = spoofPage; + server.Transfer("~/" + errUrl, true); + } + else + { + context.RewritePath("~/Default.aspx", false); + response.TrySkipIisCustomErrors = true; + response.Status = "404 Not Found"; + response.StatusCode = 404; + } + } + } + } + + // 912 : change to new if statement to handle cases where the TabId404 couldn't be handled correctly + if (unhandled404) + { + // proces the error on the external Url by rewriting to the external url + if (!string.IsNullOrEmpty(errUrl)) + { + response.ClearContent(); + response.TrySkipIisCustomErrors = true; + string reason = "Not Found"; + if (result != null) + { + reason = result.Reason.ToString(); + } + + response.AppendHeader(errRH, string.Format(errRV, "Url", errUrl, reason)); + if (reason404 != null) + { + response.AppendHeader("X-Url-Master-404-Data", reason404); + } + + response.StatusCode = statusCode; + response.Status = status; + server.Transfer("~/" + errUrl, true); + } + else + { + errorPageHtml.Write(status + "
    The requested Url does not return any valid content."); + if (reason404 != null) + { + errorPageHtml.Write(status + "
    " + reason404); + } + + errorPageHtml.Write("
    Administrators
    "); + errorPageHtml.Write("
    Change this message by configuring a specific 404 Error Page or Url for this website.
    "); + + // output a reason for the 404 + string reason = string.Empty; + if (result != null) + { + reason = result.Reason.ToString(); + } + + if (!string.IsNullOrEmpty(errRH) && !string.IsNullOrEmpty(reason)) + { + response.AppendHeader(errRH, reason); + } + + response.StatusCode = statusCode; + response.Status = status; + } + } + } + else + { + // fallback output if not valid settings + if (result != null && result.Action == ActionType.Output404) + { + // don't restate the requested Url to prevent cross site scripting + errorPageHtml.Write("404 Not Found
    The requested Url does not return any valid content."); + response.StatusCode = 404; + response.Status = "404 Not Found"; + } + else + { + // error, especially if invalid result object + errorPageHtml.Write("500 Server Error
    An error occured during processing : if possible, check the event log of the server
    "); + response.StatusCode = 500; + response.Status = "500 Internal Server Error"; + if (result != null) + { + result.Action = ActionType.Output500; + } + } + } + + if (ex != null) + { + if (context != null) + { + if (context.Items.Contains("UrlRewrite:Exception") == false) + { + context.Items.Add("UrlRewrite:Exception", ex.Message); + context.Items.Add("UrlRewrite:StackTrace", ex.StackTrace); + } + } + + if (ceSection != null && ceSection.Mode == CustomErrorsMode.Off) + { + errorPageHtml.Write(errorPageHtmlHeader); + errorPageHtml.Write("
    Exception:
    " + ex.Message + "
    "); + errorPageHtml.Write("
    Stack Trace:
    " + ex.StackTrace + "
    "); + errorPageHtml.Write("
    Administrators
    "); + errorPageHtml.Write("
    You can see this exception because the customErrors attribute in the web.config is set to 'off'. Change this value to 'on' or 'RemoteOnly' to show Error Handling
    "); + try + { + if (errUrl != null && errUrl.StartsWith("~")) + { + errUrl = VirtualPathUtility.ToAbsolute(errUrl); + } + } + finally + { + if (errUrl != null) + { + errorPageHtml.Write("
    The error handling would have shown this page : " + errUrl + "
    "); + } + else + { + errorPageHtml.Write("
    The error handling could not determine the correct page to show.
    "); + } + } + } + } + + string errorPageHtmlBody = errorPageHtml.ToString(); + if (errorPageHtmlBody.Length > 0) + { + response.Write(errorPageHtmlHeader); + response.Write(errorPageHtmlBody); + response.Write(errorPageHtmlFooter); + } + + if (ex != null) + { + UrlRewriterUtils.LogExceptionInRequest(ex, status, result); + } + } + } + + private static IPrincipal GetCurrentPrincipal(HttpContext context) + { + // Extract the forms authentication cookie + var authCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName]; + var currentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), new string[0]); + + try + { + if (authCookie != null) + { + var authTicket = FormsAuthentication.Decrypt(authCookie.Value); + if (authTicket != null && !authTicket.Expired) + { + var roles = authTicket.UserData.Split('|'); + var id = new FormsIdentity(authTicket); + currentPrincipal = new GenericPrincipal(id, roles); + } + } + } + catch (Exception) + { + // do nothing here. + } + + return currentPrincipal; + } + + private static bool CheckForDebug(HttpRequest request, NameValueCollection queryStringCol, bool debugEnabled) + { + string debugValue = string.Empty; + bool retVal = false; + + if (debugEnabled) + { + const string debugToken = "_aumdebug"; + if (queryStringCol != null && queryStringCol[debugToken] != null) + { + debugValue = queryStringCol[debugToken]; + } + else + { + if (request != null) + { + debugValue = request.Params.Get("HTTP_" + debugToken.ToUpper()); + } + + if (debugValue == null) + { + debugValue = "false"; + } + } + } + + switch (debugValue.ToLowerInvariant()) + { + case "true": + retVal = true; + break; + } + + return retVal; + } + + private static bool CheckForTabExternalForwardOrRedirect( + HttpContext context, + ref UrlAction result, + HttpResponse response, + FriendlyUrlSettings settings, + Guid parentTraceId) + { + bool finished = false; + HttpRequest request = null; + if (context != null) + { + request = context.Request; + } + + try + { + // check for external forwarding or a permanent redirect request + // 592 : check for permanent redirect (823 : moved location from 'checkForRedirects') + if (result.TabId > -1 && result.PortalId > -1 && + (settings.ForwardExternalUrlsType != DNNPageForwardType.NoForward || + result.Reason == RedirectReason.Tab_Permanent_Redirect)) + { + bool allowRedirect = !(result.RewritePath != null && result.RewritePath.ToLowerInvariant().Contains("&ctl=tab")); + + // 594 : do not redirect settings pages for external urls + if (allowRedirect) + { + TabInfo tab; + allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, false, out tab, settings); + if (allowRedirect) + { + // 772 : not redirecting file type Urls when requested. + bool permanentRedirect = false; + string redirectUrl = null; + string cleanPath = null; + bool doRedirect = false; + switch (tab.TabType) + { + case TabType.File: + // have to fudge in a portal settings object for this to work - shortcoming of LinkClick URl generation + var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); + if (context != null) + { + context.Items.Add("PortalSettings", portalSettings); + result.Reason = RedirectReason.File_Url; + string fileUrl = Globals.LinkClick(tab.Url, tab.TabID, -1); + context.Items.Remove("PortalSettings"); + + // take back out again, because it will be done further downstream + // do a check to make sure we're not repeating the Url again, because the tabid is set but we don't want to touch + // a linkclick url + if (!result.OriginalPathNoAlias.EndsWith(HttpUtility.UrlDecode(fileUrl), true, CultureInfo.InvariantCulture)) + { + redirectUrl = fileUrl; + } + } + + if (redirectUrl != null) + { + doRedirect = true; + } + + break; + case TabType.Url: + result.Reason = RedirectReason.Tab_External_Url; + redirectUrl = tab.Url; + if (redirectUrl != null) + { + doRedirect = true; + if (tab.PermanentRedirect) + { + result.Action = ActionType.Redirect301; + } + else + { + result.Action = ActionType.Redirect302; + } + } + + break; + case TabType.Tab: + // get the redirect path of the specific tab, as long as we have a valid request to work from + if (request != null) + { + // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider + cleanPath = RewriteController.GetRewriteOrRequestedPath(result, request.Url); + + // 727 prevent redirectLoop with do301 in querystring + if (result.Action == ActionType.Redirect301 || + result.Action == ActionType.Redirect302) + { + cleanPath = RedirectTokens.RemoveAnyRedirectTokens( + cleanPath, + request.QueryString); + } + + // get the redirect Url from the friendly url provider using the tab, path and settings + redirectUrl = RedirectController.GetTabRedirectUrl( + tab, + settings, + cleanPath, + result, + out permanentRedirect, + parentTraceId); + } + + // check to make sure there isn't a blank redirect Url + if (redirectUrl == null) + { + // problem : no redirect Url to redirect to + // solution : cancel the redirect + string message = "Permanent Redirect chosen for Tab " + + tab.TabPath.Replace("//", "/") + + " but forwarding Url was not valid"; + RedirectController.CancelRedirect(ref result, context, settings, message); + } + else + { + // if there was a redirect Url, set the redirect action and set the type of redirect going to use + doRedirect = true; + if (permanentRedirect) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Tab_Permanent_Redirect; + } + else + { + // not a permanent redirect, check if the page forwarding is set + result.Action = ActionType.Redirect302; + result.Reason = RedirectReason.Tab_Temporary_Redirect; + } + + // should be already set, anyway + result.RewritePath = cleanPath; + } + + break; + default: + // only concern here is if permanent redirect is requested, but there is no external url specified + if (result.Reason == RedirectReason.Tab_Permanent_Redirect) + { + bool permRedirect = tab.PermanentRedirect; + if (permRedirect) + { + // problem : permanent redirect marked, but no forwarding url supplied + // solution : cancel redirect + string message = "Permanent Redirect chosen for Tab " + + tab.TabPath.Replace("//", "/") + + " but no forwarding Url Supplied"; + RedirectController.CancelRedirect(ref result, context, settings, message); + } + } + + break; + } + + // do the redirect we have specified + if (doRedirect && + (result.Action == ActionType.Redirect301 || result.Action == ActionType.Redirect302)) + { + result.FinalUrl = redirectUrl; + if (result.Action == ActionType.Redirect301) + { + if (response != null) + { + // perform a 301 redirect to the external url of the tab + response.AppendHeader( + "X-Redirect-Reason", + result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + } + } + else + { + if (result.Action == ActionType.Redirect302) + { + if (response != null) + { + // perform a 301 redirect to the external url of the tab + response.AppendHeader( + "X-Redirect-Reason", + result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl); + } + } + } + + finished = true; + } + } + } + } + } + catch (ThreadAbortException) + { + // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest + // level try/catch block, so we handle it here. + } + + return finished; + } + + /// Redirects an alias if that is allowed by the settings. + /// + /// + /// + /// if the is a redirect, otherwise . + private static bool RedirectPortalAlias(string httpAlias, ref UrlAction result, FriendlyUrlSettings settings) + { + bool redirected = false; + + // redirect to primary alias + if (result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect && result.RedirectAllowed) + { + if (result.Reason == RedirectReason.Wrong_Portal_Alias_For_Browser_Type || result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture || + result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) + { + redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, result.Reason, settings.InternalAliasList, settings); + } + else + { + redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, settings.InternalAliasList, settings); + } + } + + return redirected; + } + + private static bool ConfigurePortalAliasRedirect( + ref UrlAction result, + string wrongAlias, + string rightAlias, + bool ignoreCustomAliasTabs, + List internalAliases, + FriendlyUrlSettings settings) + { + return ConfigurePortalAliasRedirect( + ref result, + wrongAlias, + rightAlias, + ignoreCustomAliasTabs, + RedirectReason.Wrong_Portal_Alias, + internalAliases, + settings); + } + + /// Checks to see whether the specified alias is a customTabAlias. + /// + /// + /// + /// if the alias is a custom tab alias, otherwise . + private static bool CheckIfAliasIsCustomTabAlias(ref UrlAction result, string httpAlias, FriendlyUrlSettings settings) + { + List customAliasesForTabs = TabIndexController.GetCustomPortalAliases(settings); + bool isACustomTabAlias = false; + if (customAliasesForTabs != null && customAliasesForTabs.Count > 0) + { + // remove any customAliases that are also primary aliases. + foreach (var cpa in PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId)) + { + if (cpa.IsPrimary == true && customAliasesForTabs.Contains(cpa.HTTPAlias)) + { + customAliasesForTabs.Remove(cpa.HTTPAlias); + } + } + + isACustomTabAlias = customAliasesForTabs.Contains(httpAlias.ToLowerInvariant()); + } + + return isACustomTabAlias; + } + + /// Checks to see whether the specified alias is a customTabAlias for the TabId in result. + /// + /// + /// if the the current alias is a custom tab alias, otherwise . + private static bool CheckIfAliasIsCurrentTabCustomTabAlias(ref UrlAction result, FriendlyUrlSettings settings) + { + var customAliasesForTab = TabController.Instance.GetCustomAliases(result.TabId, result.PortalId); + bool isCurrentTabCustomTabAlias = false; + if (customAliasesForTab != null && customAliasesForTab.Count > 0) + { + // see if we have a customAlias for the current CultureCode + if (customAliasesForTab.ContainsKey(result.CultureCode)) + { + // if it is for the current culture, we need to know if it's a primary alias + var tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(customAliasesForTab[result.CultureCode]); + if (tabPortalAlias != null && !tabPortalAlias.IsPrimary) + { + // it's not a primary alias, so must be a custom tab alias + isCurrentTabCustomTabAlias = true; + } + } + } + + // if it's not a custom alias for the current tab, we'll need to change the result + if (!isCurrentTabCustomTabAlias) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Wrong_Portal_Alias; + } + + return isCurrentTabCustomTabAlias; + } + + /// Configures the result object to set the correct Alias redirect parameters and destination URL. + /// + /// + /// + /// + /// + /// + /// + /// if the is a redirect, otherwise . + private static bool ConfigurePortalAliasRedirect( + ref UrlAction result, + string wrongAlias, + string rightAlias, + bool ignoreCustomAliasTabs, + RedirectReason redirectReason, + List internalAliases, + FriendlyUrlSettings settings) + { + // wrong alias for the portal + // check to see if the wrong portal alias could be a custom alias for a tab + bool doRedirect; + if (ignoreCustomAliasTabs == false) + { + // check out custom alias tabs collection + // if an alias is a custom tab alias for a specific tab, then don't redirect + // if we have the TabId, we'll need to check if the alias is valid for the current tab + if (result.TabId > 0 && CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) + { + doRedirect = false; + } + else if (result.TabId < 0 && CheckIfAliasIsCustomTabAlias(ref result, wrongAlias, settings)) + { + doRedirect = false; + } + else + { + doRedirect = true; + } + } + else + { + doRedirect = true; // do redirect, ignore custom alias entries for tabs + } + + // check to see if it is an internal alias. These are used to block redirects + // to allow for reverse proxy requests, which must change the rewritten alias + // while leaving the requested alias + bool internalAliasFound = false; + if (doRedirect && internalAliases != null && internalAliases.Count > 0) + { + if (internalAliases.Any(ia => string.Compare(ia.HttpAlias, wrongAlias, StringComparison.OrdinalIgnoreCase) == 0)) + { + internalAliasFound = true; + doRedirect = false; + } + } + + // if still need to do redirect, then set the settings that will cause the redirect (redirect not done here) + if (doRedirect) + { + result.Action = ActionType.Redirect301; + result.Reason = redirectReason; + var destUrl = result.OriginalPath; + if (result.OriginalPath.Contains(wrongAlias)) + { + destUrl = result.OriginalPath.Replace(wrongAlias, rightAlias); + } + else if (result.OriginalPath.ToLowerInvariant().Contains(wrongAlias)) + { + destUrl = result.OriginalPath.ToLowerInvariant().Replace(wrongAlias, rightAlias); + } + + if (redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture || + redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) + { + destUrl = destUrl.Replace("/language/" + result.CultureCode, string.Empty); + } + + destUrl = CheckForSiteRootRedirect(rightAlias, destUrl); + result.FinalUrl = destUrl; + } + else + { + // 838 : don't overwrite the reason if already have checkfor301 + // and don't do a check on the basis that an internal alias was found + if (result.Action != ActionType.CheckFor301 && internalAliasFound == false) + { + // set status to 'check for redirect' + result.Action = ActionType.CheckFor301; + result.Reason = RedirectReason.Custom_Tab_Alias; + } + } + + return doRedirect; + } + + private static string MakeUrlWithAlias(Uri requestUri, string httpAlias) + { + return requestUri.AbsoluteUri.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase) + ? "https://" + httpAlias.Replace("*.", string.Empty) + "/" + : "http://" + httpAlias.Replace("*.", string.Empty) + "/"; + } + + private static string MakeUrlWithAlias(Uri requestUri, PortalAliasInfo alias) + { + return MakeUrlWithAlias(requestUri, alias.HTTPAlias); + } + + /// Determines if this is a request from an install / upgrade url. + /// + /// + /// + /// + /// if the request is for an install URL, otherwise . + /// + /// //875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1. + /// + private static bool IgnoreRequestForInstall(string physicalPath, string refererPath, string requestedDomain, string refererDomain) + { + if (physicalPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) + || physicalPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) + || physicalPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture) + || Globals.Status == Globals.UpgradeStatus.Install + || Globals.Status == Globals.UpgradeStatus.Upgrade) + { + return true; + } + + // 954 : DNN 7.0 compatibility + // check for /default.aspx which is default Url launched from the Upgrade/Install wizard page + // 961 : check domain as well as path for the referer + if (physicalPath.EndsWith(Globals.glbDefaultPage, true, CultureInfo.InvariantCulture) == false + && refererPath != null + && string.Compare(requestedDomain, refererDomain, StringComparison.OrdinalIgnoreCase) == 0 + && (refererPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) + || refererPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) + || refererPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + private static bool IgnoreRequestForWebServer(string requestedPath) + { + // Should standardize comparison methods + if (requestedPath.IndexOf("synchronizecache.aspx", StringComparison.OrdinalIgnoreCase) > 1 + || requestedPath.EndsWith("keepalive.aspx", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Get the root + var rootPath = requestedPath.Substring(0, requestedPath.LastIndexOf("/", StringComparison.Ordinal)); + rootPath = rootPath.Substring(rootPath.IndexOf("://", StringComparison.Ordinal) + 3); + + // Check if this is a WebServer and not a portalalias. + // if can auto add portal alias enabled, then return false, alias will add later. + var alias = PortalAliasController.Instance.GetPortalAlias(rootPath); + if (alias != null || CanAutoAddPortalAlias()) + { + return false; + } + + // Check if this is a WebServer + var server = ServerController.GetEnabledServers().SingleOrDefault(s => s.Url == rootPath); + if (server != null) + { + return true; + } + + return false; + } + + private static bool IgnoreRequestForInstall(HttpRequest request) + { + try + { + string physicalPath = request.PhysicalPath; + string requestedDomain = request.Url.Host; + string refererPath = null, refererDomain = null; + if (request.UrlReferrer != null) + { + refererDomain = request.UrlReferrer.Host; + refererPath = request.UrlReferrer.LocalPath; + } + + return IgnoreRequestForInstall(physicalPath, refererPath, requestedDomain, refererDomain); + } + catch (PathTooLongException) + { + // catch and handle this exception, caused by an excessively long file path based on the + // mapped virtual url + return false; + } + catch (ArgumentException) + { + // catch and handle this exception, caused by an invalid character in the file path based on the + // mapped virtual url + return false; + } + catch (UriFormatException) + { + // catch and handle this exception, caused by an invalid hostname in the referrer + return false; + } + } + + private static bool IgnoreRequest(UrlAction result, string requestedPath, string ignoreRegex, HttpRequest request) + { + bool retVal = false; + + // check if we are upgrading/installing + // 829 : use result physical path instead of requset physical path + // 875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1 + if (request != null && (IgnoreRequestForInstall(request) || IgnoreRequestForWebServer(requestedPath))) + { + // ignore all install requests + retVal = true; + } + else if (request != null && request.Path.EndsWith("imagechallenge.captcha.aspx", StringComparison.InvariantCultureIgnoreCase)) + { + retVal = true; + } + else + { + try + { + if (ignoreRegex.Length > 0) + { + if (Regex.IsMatch(requestedPath, ignoreRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + retVal = true; + } + } + } + catch (Exception ex) + { + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + result.Ex = ex; + } + } + + return retVal; + } + + private static void CheckForRewrite( + string fullUrl, + string querystring, + UrlAction result, + bool useFriendlyUrls, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + out bool isPhysicalResource, + Guid parentTraceId) + { + bool checkForRewrites; + + // just check to make sure it isn't a physical resource on the server + RewriteController.IdentifyByPhysicalResource( + result.PhysicalPath, + fullUrl, + queryStringCol, + ref result, + useFriendlyUrls, + settings, + out isPhysicalResource, + out checkForRewrites, + parentTraceId); + + if (checkForRewrites && RewriteController.CanRewriteRequest(result, fullUrl, settings)) + { + bool doSiteUrlProcessing = false; + + // 728 new regex expression to pass values straight onto the siteurls.config file + if (!string.IsNullOrEmpty(settings.UseSiteUrlsRegex)) + { + doSiteUrlProcessing = Regex.IsMatch(fullUrl, settings.UseSiteUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + // if a virtual request, and not starting with the siteUrls.config file, go on to find the rewritten path + if (!doSiteUrlProcessing) + { + // looks up the page index to find the correct Url + bool doRewrite = RewriteController.IdentifyByTabPathEx(fullUrl, querystring, result, queryStringCol, settings, parentTraceId); + if (!doRewrite) + { + doSiteUrlProcessing = true; + } + } + + if (doSiteUrlProcessing) + { + // 728 : compare requests against the siteurls.config file, either if no other match was found, or if we want to skip the rest of the processing + // the standard DNN way of rewriting, using expressions found in the siteurls.config file + RewriteController.IdentifyByRegEx(fullUrl, querystring, result.ApplicationPath, ref result, settings, parentTraceId); + } + } + } + + private static bool CheckForRedirects( + Uri requestUri, + string fullUrl, + NameValueCollection queryStringCol, + UrlAction result, + string requestType, + FriendlyUrlSettings settings, + int portalHomeTabId) + { + bool redirected = false; + if (queryStringCol["error"] == null && queryStringCol["message"] == null && requestType != "POST") + { + // if the / is missing from an extension-less request, then check for a 301 redirect + if (settings.PageExtensionUsageType == PageExtensionUsageType.Never) + { + // 575 check on absolutePath instead of absoluteUri : this ignores query strings and fragments like # + // 610 don't always end with '/' - reverses previous setting + // 687 don't double-check 301 redirects. 'CheckFor301' is less concise than 'Redirect301' + // DNN-21906: if the redirect is for splash page, then we should continue the 302 redirect. + if (requestUri.AbsolutePath.EndsWith("/") && result.Action != ActionType.Redirect301 && result.Reason != RedirectReason.Requested_SplashPage) + { + result.Action = ActionType.CheckFor301; + } + } + + if (settings.RedirectWrongCase && result.Action == ActionType.Continue) + { + result.Action = ActionType.CheckFor301; + } + + string scheme = requestUri.Scheme + Uri.SchemeDelimiter; + bool queryStringHas301Parm = queryStringCol["do301"] != null; + + // 727 : keep a bool value if there is a do301 request in the querystring + // check for a 301 request in the query string, or an explicit 301 or 302 request + // 2.0 - check for explicit do301=true instead of just do301 key + string do301Val = queryStringCol["do301"]; + if (result.TabId > -1 + && (result.Action == ActionType.Redirect301 + || (do301Val != null && do301Val == "true") + || result.Action == ActionType.Redirect302)) + { + // valid tab, specific 301 redirect, rewrite hint for specific 301 redirect, or specific 302 redirect + // we have ordered a 301 redirect earlier in the code + // get the url for redirection by re-submitting the path into the Friendly Url Provider + string pathOnly = RewriteController.GetRewriteOrRequestedPath(result, requestUri); + + // 727 prevent redirectLoop with do301 in querystring + if (result.Action == ActionType.Redirect301 || queryStringHas301Parm || result.Action == ActionType.Redirect302) + { + pathOnly = RedirectTokens.RemoveAnyRedirectTokens(pathOnly, queryStringCol); + } + + // check for exclusion by regex for this url + if (result.RedirectAllowed) + { + // get the tab so we know where to go + TabInfo tab; + bool checkRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + + if (checkRedirect) + { + if ((result.Reason == RedirectReason.Deleted_Page || result.Reason == RedirectReason.Disabled_Page) + && portalHomeTabId > 0 + && settings.DeletedTabHandlingType == DeletedTabHandlingType.Do301RedirectToPortalHome) + { + // redirecting to home page + TabInfo homeTab = TabController.Instance.GetTab(portalHomeTabId, result.PortalId, false); + if (homeTab != null) + { + string homePageUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + homeTab, + pathOnly, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + result.Action = ActionType.Redirect301; + result.FinalUrl = homePageUrl; + result.RewritePath = pathOnly; + redirected = true; + } + } + else + { + // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider + string cleanPath = RewriteController.GetRewriteOrRequestedPath(result, requestUri); + + // 727 prevent redirectLoop with do301 in querystring + // also check for existing in path of do301 token + if (result.Action == ActionType.Redirect301 || do301Val != null || result.Action == ActionType.Redirect302) + { + cleanPath = RedirectTokens.RemoveAnyRedirectTokens(cleanPath, queryStringCol); + } + + // get best friendly url from friendly url provider + string bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + + // get what the friendly Url for this tab should be and stick it in as the redirect + // 727 : using boolean because we wanted to get rid of the do301 before calculating the correct url + if (queryStringHas301Parm) + { + result.Action = ActionType.Redirect301; + if (result.Reason == RedirectReason.Not_Redirected) + { + result.Reason = RedirectReason.Unfriendly_Url_1; + } + } + + result.FinalUrl = bestFriendlyUrl; + result.RewritePath = pathOnly; + redirected = true; // mark as redirected + } + } + else + { + // redirect disallowed + // 618: dont' clear if 302 redirect selected + if (result.Action != ActionType.Redirect302Now || result.Action != ActionType.Redirect302) + { + RedirectController.CancelRedirect(ref result, null, settings, "Redirect requested but cancelled because disallowed"); + } + } + } + } + else if (result.TabId > -1 && result.RedirectAllowed && result.Action == ActionType.CheckFor301) + { + // 301 check was requested in earlier processing + // get the tab controller and retrieve the tab the request is for + // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab + if (settings.RedirectUnfriendly) + { + TabInfo tab; + bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + if (allowRedirect && tab != null) + { + // remove the http alias from the url. Do this by putting the url back together from the request and removing the alias + string rewritePathOnly; + if (result.DoRewrite) + { + rewritePathOnly = result.RewritePath; + var pos = rewritePathOnly.IndexOf("default.aspx", StringComparison.OrdinalIgnoreCase); + if (pos > Null.NullInteger) + { + rewritePathOnly = rewritePathOnly.Substring(pos); + } + } + else + { + rewritePathOnly = requestUri.Host + requestUri.PathAndQuery; + } + + // remove the http alias from the path + var pathAliasEnd = rewritePathOnly.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase); + var queryStringIndex = rewritePathOnly.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); + if (pathAliasEnd > Null.NullInteger && (queryStringIndex == Null.NullInteger || pathAliasEnd < queryStringIndex)) + { + rewritePathOnly = rewritePathOnly.Substring(pathAliasEnd + result.PortalAlias.HTTPAlias.Length); + } + + // now check to see if need to remove /default.aspx from the end of the requested Url + string requestedUrl = fullUrl; + int requestedUrlAliasEnd = requestedUrl.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase) + + (result.PortalAlias.HTTPAlias + "/").Length; + if (requestedUrlAliasEnd > Null.NullInteger) + { + // 818 : when a site root is used for a custom page Url, then check for max length within bounds + if ((requestedUrl.Length - requestedUrlAliasEnd) >= 12 && requestedUrl.Substring(requestedUrlAliasEnd).Equals("default.aspx", StringComparison.InvariantCultureIgnoreCase)) + { + requestedUrl = requestedUrl.Substring(0, requestedUrl.Length - 12); + + // 12 = default.aspx length + } + } + + // what happens here is that the request is reverse-engineered to see if it matches what the friendly Url shoudl have been + // get what the friendly Url for this tab should be + string bestFriendlyUrl; + + // 819 : leaving /do301/check in Url because not using cleanPath to remove from + string cleanPath = RedirectTokens.RemoveAnyRedirectTokensAndReasons(rewritePathOnly); + + // string cleanPath = rewritePathOnly.Replace("&do301=check","");//remove check parameter if it exists + // cleanPath = cleanPath.Replace("&do301=true", "");//don't pass through internal redirect check parameter + cleanPath = cleanPath.Replace("&_aumdebug=true", string.Empty); // remove debug parameter if it exists + + Match match = RewritePathRx.Match(rewritePathOnly ?? string.Empty); + if (match.Success) + { + // when the pathOnly value ends with '=' it means there is a query string pair with a key and no value + // make the assumption that this was passed in as a page name OTHER than default page + string pageName = match.Groups["parm"].Value; // get the last parameter in the list + + cleanPath = cleanPath.Replace(match.Value, string.Empty); + + // remove the last parameter from the path + + // generate teh friendly URl name with the last parameter as the page name, not a query string parameter + bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + pageName + settings.PageExtension, + result.HttpAlias, + false, + settings, + Guid.Empty); + } + else + { + bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + } + + // if the incoming request doesn't match the 'most friendly' url, a 301 Moved Permanently status is returned, along with the friendly url + // check the bestFriendlyUrl against either the url, or rawUrl (with and without host) + // in each case, the aumdebug parameter will be searched for and replaced + var urlDecode = HttpUtility.UrlDecode(requestedUrl); + if (urlDecode != null) + { + string rawUrlWithHost = StripDebugParameter(urlDecode.ToLowerInvariant()); + + // string rawUrlWithHost = StripDebugParameter(System.Web.HttpUtility.UrlDecode(scheme + requestUri.Host + requestUri.PathAndQuery).ToLowerInvariant()); + string rawUrlWithHostNoScheme = StripDebugParameter(rawUrlWithHost.Replace(scheme, string.Empty)); + string bestFriendlyNoScheme = StripDebugParameter(bestFriendlyUrl.ToLowerInvariant().Replace(scheme, string.Empty)); + string requestedPathNoScheme = StripDebugParameter(requestUri.AbsoluteUri.Replace(scheme, string.Empty).ToLowerInvariant()); + string rawUrlLowerCase = StripDebugParameter(requestUri.AbsoluteUri.ToLowerInvariant()); + + // check to see if just an alias redirect of an internal alias + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + if (settings.InternalAliasList != null && settings.InternalAliasList.Count > 0 && primaryAliases.Count > 0) + { + var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result); + if (cpa != null) + { + string chosenAlias = cpa.HTTPAlias.ToLowerInvariant(); + foreach (InternalAlias ia in settings.InternalAliasList) + { + string internalAlias = ia.HttpAlias.ToLowerInvariant(); + if (requestedPathNoScheme.Contains(internalAlias)) + { + // an internal alias has been used. + // replace this in the comparison charts to do a 'fair' comparison + requestedPathNoScheme = requestedPathNoScheme.Replace(internalAlias, chosenAlias); + rawUrlWithHost = rawUrlWithHost.Replace(scheme + internalAlias, scheme + chosenAlias); + rawUrlWithHostNoScheme = rawUrlWithHostNoScheme.Replace(internalAlias, chosenAlias); + rawUrlLowerCase = rawUrlLowerCase.Replace(internalAlias, chosenAlias); + break; + } + } + } + } + + // DNN-9158: prevent SSL Offloading infinite redirects + if (!result.IsSecureConnection && result.IsSSLOffloaded && bestFriendlyNoScheme.StartsWith("https")) + { + bestFriendlyNoScheme = $"http://{bestFriendlyNoScheme.Substring(8)}"; + } + + if (!(bestFriendlyNoScheme == requestedPathNoScheme + || bestFriendlyNoScheme == rawUrlWithHost + || HttpUtility.UrlDecode(bestFriendlyNoScheme) == rawUrlWithHost + || bestFriendlyNoScheme == rawUrlWithHostNoScheme + || bestFriendlyNoScheme == HttpUtility.UrlDecode(requestedPathNoScheme) + || HttpUtility.UrlDecode(bestFriendlyNoScheme) == HttpUtility.UrlDecode(requestedPathNoScheme) + || bestFriendlyNoScheme == rawUrlLowerCase)) + { + redirected = true; + result.Action = ActionType.Redirect301; + result.FinalUrl = bestFriendlyUrl; + if (result.Reason != RedirectReason.Custom_Tab_Alias && + result.Reason != RedirectReason.Deleted_Page && + result.Reason != RedirectReason.Disabled_Page) + { + result.Reason = RedirectReason.Unfriendly_Url_2; + } + + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + requestedPathNoScheme + " [requested with no scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHost + " [requested with host and scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHostNoScheme + " [requested with host, no scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + HttpUtility.UrlDecode(requestedPathNoScheme) + " [requested and decoded]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlLowerCase + " [requested raw Url]"); + } + } + } + } + } + + if (result.RedirectAllowed && settings.RedirectWrongCase) + { + // check for redirects where a redirectToSubDomain is specified, + // redirect for Wrong case is specified, and there is a valid tab and it's not already redirected somewhere else + bool doRedirect = false; + string redirectPath = redirected ? result.FinalUrl : requestUri.AbsoluteUri; + string redirectPathOnly = redirectPath; + if (redirectPathOnly.Contains("?")) + { + redirectPathOnly = redirectPathOnly.Substring(0, redirectPathOnly.IndexOf("?", StringComparison.Ordinal)); + } + + // Thanks Etienne for the fix for Diacritic Characters Terminal Loop! + // if the url contains url encoded characters, they appear here uppercase -> %C3%83%C2 + // decode the url to get back the original character and do proper casing comparison + string urlDecodedRedirectPath = HttpUtility.UrlDecode(redirectPathOnly); + + // check for wrong case redirection + if (urlDecodedRedirectPath != null && (settings.RedirectWrongCase && string.CompareOrdinal(urlDecodedRedirectPath, urlDecodedRedirectPath.ToLowerInvariant()) != 0)) + { + TabInfo tab; + bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + + if (allowRedirect && !string.IsNullOrEmpty(settings.ForceLowerCaseRegex)) + { + // don't allow redirect if excluded from redirecting in the force lower case regex pattern (606) + allowRedirect = !Regex.IsMatch(redirectPath, settings.ForceLowerCaseRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + if (allowRedirect) + { + // special case : when IIS automatically places /default.aspx on the end of the string, + // then don't try and redirect to the lower case /default.aspx, just let it through. + // we don't know whether IIS appended /Default.aspx on the end, however, we can guess + // if the redirectDefault.aspx is turned on (511) + if (settings.RedirectDefaultPage == false && redirectPathOnly.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // ignore this, because it's just a redirect of the /Default.aspx to /default.aspx + } + else + { + redirectPath = redirectPath.Replace(redirectPathOnly, redirectPathOnly.ToLowerInvariant()); + doRedirect = true; + result.Reason = RedirectReason.Not_Lower_Case; + } + } + } + + if (doRedirect) + { + result.Action = ActionType.Redirect301; + result.FinalUrl = CheckForSiteRootRedirect(result.PortalAlias.HTTPAlias, redirectPath); + redirected = true; + } + } + } + + return redirected; + } + + private static string StripDebugParameter(string url) + { + return AumDebugRegex.Replace(url, string.Empty); + } + + private static bool CheckFor301RedirectExclusion(int tabId, int portalId, bool checkBaseUrls, out TabInfo tab, FriendlyUrlSettings settings) + { + bool doRedirect = false; + tab = TabController.Instance.GetTab(tabId, portalId, false); + + // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab + if (tab != null && tab.IsSuperTab == false && !tab.DoNotRedirect) + { + if (checkBaseUrls) + { + // no redirect for friendly url purposes if the tab is in the 'base friendly urls' section + doRedirect = !RewriteController.IsExcludedFromFriendlyUrls(tab, settings, true); + } + else + { + doRedirect = true; + } + } + + return doRedirect; + } + + private PortalAliasInfo GetPortalAlias(FriendlyUrlSettings settings, string requestUrl, out bool redirectAlias, out bool isPrimaryAlias, out string wrongAlias) + { + PortalAliasInfo aliasInfo = null; + redirectAlias = false; + wrongAlias = null; + isPrimaryAlias = false; + OrderedDictionary portalAliases = TabIndexController.GetPortalAliases(settings); + foreach (string alias in portalAliases.Keys) + { + var urlToMatch = requestUrl; + + // in fact, requested url should contain alias + // for better performance, need to check whether we want to proceed with a whole url matching or not + // if alias is not a part of url -> let's proceed to the next iteration + var aliasIndex = urlToMatch.IndexOf(alias, StringComparison.InvariantCultureIgnoreCase); + if (aliasIndex < 0) + { + continue; + } + else + { + // we do not accept URL if the first occurence of alias is presented somewhere in the query string + var queryIndex = urlToMatch.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); + if (queryIndex >= 0 && queryIndex < aliasIndex) + { + // alias is in the query string, go to the next alias + continue; + } + + // we are fine here, lets prepare URL to be validated in regex + urlToMatch = urlToMatch.ReplaceIgnoreCase(alias, "_ALIAS_"); + } + + // check whether requested URL has the right URL format containing existing alias + // i.e. url is http://dnndev.me/site1/query?string=test, alias is dnndev.me/site1 + // in the below expression we will validate following value http://_ALIAS_/query?string=test + var aliasMatch = AliasUrlRegex.Match(urlToMatch); + if (aliasMatch.Success) + { + // check for mobile browser and matching + var aliasEx = (PortalAliasInfo)portalAliases[alias]; + redirectAlias = aliasEx.Redirect; + if (redirectAlias) + { + wrongAlias = alias; + } + + isPrimaryAlias = aliasEx.IsPrimary; + aliasInfo = aliasEx; + break; + } + } + + return aliasInfo; + } + + private void ProcessRequest( + HttpContext context, + Uri requestUri, + bool useFriendlyUrls, + UrlAction result, + FriendlyUrlSettings settings, + bool allowSettingsChange, + Guid parentTraceId) + { + bool finished = false; + bool showDebug = false; + bool postRequest = false; + + HttpRequest request = context.Request; + HttpResponse response = context.Response; + string requestType = request.RequestType; + NameValueCollection queryStringCol = request.QueryString; + + try + { + string fullUrl, querystring; + + // 699: get the full url based on the request and the quersytring, rather than the requestUri.ToString() + // there is a difference in encoding, which can corrupt results when an encoded value is in the querystring + RewriteController.GetUrlWithQuerystring(request, requestUri, out fullUrl, out querystring); + + showDebug = CheckForDebug(request, queryStringCol, settings.AllowDebugCode); + string ignoreRegex = settings.IgnoreRegex; + bool ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); + bool redirectAlias = false; + if (!ignoreRequest) + { + // set original path + context.Items["UrlRewrite:OriginalUrl"] = requestUri.AbsoluteUri; + + // set the path of the result object, and determine if a redirect is allowed on this request + result.SetOriginalPath(requestUri.ToString(), settings); + + // 737 : set the mobile browser + result.SetBrowserType(request, response, settings); + + // add to context + context.Items["UrlRewrite:BrowserType"] = result.BrowserType.ToString(); + + // 839 : split out this check + result.SetRedirectAllowed(result.OriginalPath, settings); + + // find the portal alias first + string wrongAlias; + bool isPrimaryAlias; + var requestedAlias = this.GetPortalAlias(settings, fullUrl, out redirectAlias, out isPrimaryAlias, out wrongAlias); + + if (requestedAlias != null) + { + // 827 : now get the correct settings for this portal (if not a test request) + // 839 : separate out redirect check as well and move above first redirect test (ConfigurePortalAliasRedirect) + if (allowSettingsChange) + { + settings = new FriendlyUrlSettings(requestedAlias.PortalID); + result.SetRedirectAllowed(result.OriginalPath, settings); + } + + result.PortalAlias = requestedAlias; + result.PrimaryAlias = requestedAlias; // this is the primary alias + result.PortalId = requestedAlias.PortalID; + result.CultureCode = requestedAlias.CultureCode; + + // get the portal alias mapping for this portal + result.PortalAliasMapping = PortalSettingsController.Instance().GetPortalAliasMappingMode(requestedAlias.PortalID); + + // if requested alias wasn't the primary, we have a replacement, redirects are allowed and the portal alias mapping mode is redirect + // then do a redirect based on the wrong portal + if ((redirectAlias && wrongAlias != null) && result.RedirectAllowed && result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) + { + // this is the alias, we are going to enforce it as the primary alias + result.PortalAlias = requestedAlias; + result.PrimaryAlias = requestedAlias; + + // going to redirect this alias because it is incorrect + // or do we just want to mark as 'check for 301??' + redirectAlias = ConfigurePortalAliasRedirect( + ref result, + wrongAlias, + requestedAlias.HTTPAlias, + false, + settings.InternalAliasList, + settings); + } + else + { + // do not redirect the wrong alias, but set the primary alias value + if (wrongAlias != null) + { + // get the portal alias info for the requested alias (which is the wrong one) + // and set that as the alias, but also set the found alias as the primary + PortalAliasInfo wrongAliasInfo = PortalAliasController.Instance.GetPortalAlias(wrongAlias); + if (wrongAliasInfo != null) + { + result.PortalAlias = wrongAliasInfo; + result.PrimaryAlias = requestedAlias; + } + } + } + } + } + + ignoreRegex = settings.IgnoreRegex; + ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); + if (!ignoreRequest) + { + // check to see if a post request + if (request.RequestType == "POST") + { + postRequest = true; + } + + // check the portal alias again. This time, in more depth now that the portal Id is known + // this check handles browser types/language specific aliases & mobile aliases + string primaryHttpAlias; + if (!redirectAlias && this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryHttpAlias)) + { + // it was an incorrect alias + PortalAliasInfo primaryAlias = PortalAliasController.Instance.GetPortalAlias(primaryHttpAlias); + if (primaryAlias != null) + { + result.PrimaryAlias = primaryAlias; + } + + // try and redirect the alias if the settings allow it + redirectAlias = RedirectPortalAlias(primaryHttpAlias, ref result, settings); + } + + if (redirectAlias) + { + // not correct alias for portal : will be redirected + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + + if (!finished) + { + // Check to see if this to be rewritten into default.aspx?tabId=nn format + // this call is the main rewriting matching call. It makes the decision on whether it is a + // physical file, whether it is toe be rewritten or redirected by way of a stored rule + + // Check if we have a standard url + var uri = new Uri(fullUrl); + if (uri.PathAndQuery.StartsWith("/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + result.DoRewrite = true; + result.Action = ActionType.CheckFor301; + result.RewritePath = Globals.glbDefaultPage + uri.Query; + } + else + { + bool isPhysicalResource; + CheckForRewrite(fullUrl, querystring, result, useFriendlyUrls, queryStringCol, settings, out isPhysicalResource, parentTraceId); + } + + // return 404 if there is no portal alias for a rewritten request + if (result.DoRewrite && result.PortalAlias == null) + { + // 882 : move this logic in from where it was before to here + // so that non-rewritten requests don't trip over it + // no portal alias found for the request : that's a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.No_Portal_Alias; + + Handle404OrException(settings, context, null, result, false, showDebug); + finished = true; // cannot fulfil request unless correct portal alias specified + } + } + + // now we may know the TabId. If the current alias is not the same as the primary alias, + // we should check if the current alias is indeed a valid custom alias for the current tab. + if (result.TabId > 0 && result.HttpAlias != result.PrimaryAlias.HTTPAlias && !CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) + { + // it was an incorrect alias + // try and redirect the alias if the settings allow it + if (RedirectPortalAlias(result.PrimaryAlias.HTTPAlias, ref result, settings)) + { + // not correct alias for tab : will be redirected + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + } + + if (!finished && result.DoRewrite) + { + // check the identified portal alias details for any extra rewrite information required + // this includes the culture and the skin, which can be placed into the rewrite path + // This logic is here because it will catch any Urls which are excluded from rewriting + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + if (result.PortalId > -1 && result.HttpAlias != null) + { + string culture; + string skin; + BrowserTypes browserType; + primaryAliases.GetSettingsByPortalIdAndAlias( + result.PortalId, + result.HttpAlias, + out culture, + out browserType, + out skin); + + // add language code to path if it exists (not null) and if it's not already there + string rewritePath = result.RewritePath; + if (RewriteController.AddLanguageCodeToRewritePath(ref rewritePath, culture)) + { + result.CultureCode = culture; + } + + // 852: add skinSrc to path if it exists and if it's not already there + string debugMessage; + RewriteController.AddSkinToRewritePath(result.TabId, result.PortalId, ref rewritePath, skin, out debugMessage); + result.RewritePath = rewritePath; // reset back from ref temp var + if (debugMessage != null) + { + result.DebugMessages.Add(debugMessage); + } + } + } + + if (!finished && result.DoRewrite) + { + // if so, do the rewrite + if (result.RewritePath.StartsWith(result.Scheme) || result.RewritePath.StartsWith(Globals.glbDefaultPage) == false) + { + if (result.RewritePath.Contains(Globals.glbDefaultPage) == false) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + else + { + // if there is no TabId and we have the domain + if (!result.RewritePath.ToLowerInvariant().Contains("tabId=")) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + else + { + RewriterUtils.RewriteUrl(context, result.RewritePath); + } + } + } + else + { + if (IsMvc(result, queryStringCol, context, result.TabId, result.PortalId)) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath.Replace(Globals.glbDefaultPage, "DesktopModules/Default/Page/" + result.TabId + "/" + result.CultureCode)); + } + else + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + } + } + + // confirm which portal the request is for + if (!finished) + { + this.IdentifyPortalAlias(context, request, requestUri, result, queryStringCol, settings, parentTraceId); + if (result.Action == ActionType.Redirect302Now) + { + // performs a 302 redirect if requested + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl, false); + finished = true; + } + else + { + if (result.Action == ActionType.Redirect301 && !string.IsNullOrEmpty(result.FinalUrl)) + { + finished = true; + + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + } + } + } + + if (!finished) + { + // check to see if this tab has an external url that should be forwared or not + finished = CheckForTabExternalForwardOrRedirect(context, ref result, response, settings, parentTraceId); + } + + // check for a parameter redirect (we had to do all the previous processing to know we are on the right portal and identify the tabid) + // if the CustomParmRewrite flag is set, it means we already rewrote these parameters, so they have to be correct, and aren't subject to + // redirection. The only reason to do a custom parm rewrite is to interpret already-friendly parameters + if (!finished + && !postRequest /* either request is null, or it's not a post - 551 */ + && result.HttpAlias != null /* must have a http alias */ + && !result.CustomParmRewrite && /* not custom rewritten parms */ + ((settings.EnableCustomProviders && + RedirectController.CheckForModuleProviderRedirect(requestUri, ref result, queryStringCol, settings, parentTraceId)) + + // 894 : allow disable of all custom providers + || + RedirectController.CheckForParameterRedirect(requestUri, ref result, queryStringCol, settings))) + { + // 301 redirect to new location based on parameter match + if (response != null) + { + switch (result.Action) + { + case ActionType.Redirect301: + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + break; + case ActionType.Redirect302: + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl); + break; + case ActionType.Output404: + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + Handle404OrException(settings, context, null, result, true, showDebug); + break; + } + } + + finished = true; + } + + // shifted until after the 301 redirect code to allow redirects to be checked for pages which have no rewrite value + // look for a 404 result from the rewrite, because of a deleted page or rule + if (!finished && result.Action == ActionType.Output404) + { + if (result.OriginalPath.Equals(result.HttpAlias, StringComparison.InvariantCultureIgnoreCase) + && result.PortalAlias != null + && result.Reason != RedirectReason.Deleted_Page + && result.Reason != RedirectReason.Disabled_Page) + { + // Request for domain with no page identified (and no home page set in Site Settings) + result.Action = ActionType.Continue; + } + else + { + finished = true; + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, null); + } + + // show the 404 page if configured + result.Reason = RedirectReason.Requested_404; + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + + if (!finished) + { + // add the portal settings to the app context if the portal alias has been found and is correct + if (result.PortalId != -1 && result.PortalAlias != null) + { + // for invalid tab id other than -1, show the 404 page + TabInfo tabInfo = TabController.Instance.GetTab(result.TabId, result.PortalId, false); + if (tabInfo == null && result.TabId > -1) + { + finished = true; + + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, null); + } + + // show the 404 page if configured + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Requested_404; + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + Handle404OrException(settings, context, null, result, true, showDebug); + } + else + { + Globals.SetApplicationName(result.PortalId); + + // load the PortalSettings into current context + var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); + + // set the primary alias if one was specified + if (result.PrimaryAlias != null) + { + portalSettings.PrimaryAlias = result.PrimaryAlias; + } + + if (result.CultureCode != null && fullUrl.Contains(result.CultureCode) && + portalSettings.DefaultLanguage == result.CultureCode) + { + // when the request culture code is the same as the portal default, check for a 301 redirect, because we try and remove the language from the url where possible + result.Action = ActionType.CheckFor301; + } + + int portalHomeTabId = portalSettings.HomeTabId; + if (context != null && portalSettings != null && !context.Items.Contains("PortalSettings")) + { + context.Items.Add("PortalSettings", portalSettings); + + // load PortalSettings and HostSettings dictionaries into current context + // specifically for use in DotNetNuke.Web.Client, which can't reference DotNetNuke.dll to get settings the normal way + context.Items.Add("PortalSettingsDictionary", PortalController.Instance.GetPortalSettings(portalSettings.PortalId)); + context.Items.Add("HostSettingsDictionary", HostController.Instance.GetSettingsDictionary()); + } + + // check if a secure redirection is needed + // this would be done earlier in the piece, but need to know the portal settings, tabid etc before processing it + bool redirectSecure = this.CheckForSecureRedirect(portalSettings, requestUri, result, queryStringCol, settings); + if (redirectSecure) + { + if (response != null) + { + // 702 : don't check final url until checked for null reference first + if (result.FinalUrl != null) + { + if (result.FinalUrl.StartsWith("https://")) + { + if (showDebug) + { + /* + string debugMsg = "{0}, {1}, {2}, {3}, {4}"; + string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); + response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); + */ + ShowDebugData(context, fullUrl, result, null); + } + + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + finished = true; + } + else + { + if (settings.SSLClientRedirect) + { + // redirect back to http version, use client redirect + response.Clear(); + + // add a refresh header to the response + response.AddHeader("Refresh", "0;URL=" + result.FinalUrl); + + // add the clientside javascript redirection script + var finalUrl = HttpUtility.HtmlEncode(result.FinalUrl); + response.Write(""); + response.Write(@""); + response.Write(""); + if (showDebug) + { + /* + string debugMsg = "{0}, {1}, {2}, {3}, {4}"; + string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); + response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); + */ + ShowDebugData(context, fullUrl, result, null); + } + + // send the response + // 891 : reinstate the response.end to stop the entire page loading + response.End(); + finished = true; + } + else + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + finished = true; + } + } + } + } + } + else + { + // check for, and do a 301 redirect if required + if (CheckForRedirects(requestUri, fullUrl, queryStringCol, result, requestType, settings, portalHomeTabId)) + { + if (response != null) + { + if (result.Action == ActionType.Redirect301) + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + else if (result.Action == ActionType.Redirect302) + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl, false); + finished = true; + } + } + } + else + { + // 612 : Don't clear out a 302 redirect if set + if (result.Action != ActionType.Redirect302 && + result.Action != ActionType.Redirect302Now) + { + result.Reason = RedirectReason.Not_Redirected; + result.FinalUrl = null; + } + } + } + } + } + else + { + // alias does not exist in database + // and all attempts to find another have failed + // this should only happen if the HostPortal does not have any aliases + result.Action = ActionType.Output404; + if (response != null) + { + if (showDebug) + { + ShowDebugData(context, fullUrl, result, null); + } + + result.Reason = RedirectReason.Requested_404; + + // 912 : change 404 type to transfer to allow transfer to main portal in single-portal installs + Handle404OrException(settings, context, null, result, true, showDebug); + finished = true; + } + } + } + + // 404 page ?? + if (settings.TabId404 > 0 && settings.TabId404 == result.TabId) + { + string status = queryStringCol["status"]; + if (status == "404") + { + // respond with a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Requested_404_In_Url; + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + else + { + if (result.DoRewrite == false && result.CanRewrite != StateBoolean.False && !finished && + result.Action == ActionType.Continue) + { + // 739 : catch no-extension 404 errors + string pathWithNoQs = result.OriginalPath; + if (pathWithNoQs.Contains("?")) + { + pathWithNoQs = pathWithNoQs.Substring(0, pathWithNoQs.IndexOf("?", StringComparison.Ordinal)); + } + + if (!pathWithNoQs.Substring(pathWithNoQs.Length - 5, 5).Contains(".")) + { + // no page extension, output a 404 if the Url is not found + // 766 : check for physical path before passing off as a 404 error + // 829 : change to use action physical path + // 893 : filter by regex pattern to exclude urls which are valid, but show up as extensionless + if ((request != null && Directory.Exists(result.PhysicalPath)) + || + Regex.IsMatch(pathWithNoQs, settings.ValidExtensionlessUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + // do nothing : it's a request for a valid physical path, maybe including a default document + result.VirtualPath = StateBoolean.False; + } + else + { + if (!Globals.ServicesFrameworkRegex.IsMatch(context.Request.RawUrl)) + { + // no physical path, intercept the request and hand out a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Page_404; + result.VirtualPath = StateBoolean.True; + + // add in a message to explain this 404, becaue it can be cryptic + result.DebugMessages.Add("404 Reason : Not found and no extension"); + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + } + } + } + + // show debug messages after extensionless-url special 404 handling + if (showDebug) + { + ShowDebugData(context, fullUrl, result, null); + } + } + } + catch (ThreadAbortException) + { + // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest + // level try/catch block, so we handle it here. + Thread.ResetAbort(); + } + catch (Exception ex) + { + if (showDebug) + { + Services.Exceptions.Exceptions.LogException(ex); + } + + if (response != null) + { + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, ex); + } + + if (result != null) + { + result.Ex = ex; + result.Reason = RedirectReason.Exception; + } + + Handle404OrException(settings, context, ex, result, false, showDebug); + } + else + { + if (result != null && result.DebugMessages != null) + { + result.DebugMessages.Add("Exception: " + ex.Message); + result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); + } + + throw; + } + } + finally + { + // 809 : add in new code copied from urlRewrite class in standard Url Rewrite module + if (context != null && context.Items["FirstRequest"] != null) + { + context.Items.Remove("FirstRequest"); + + // process any messages in the eventQueue for the Application_Start_FIrstRequest event + EventQueueController.ProcessMessages("Application_Start_FirstRequest"); + } + } + } + + private bool CheckForSecureRedirect( + PortalSettings portalSettings, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings) + { + bool redirectSecure = false; + string url = requestUri.ToString(); + + // 889 : don't run secure redirect code for physical resources or requests that aren't a rewritten Url + if (result.IsPhysicalResource == false && result.TabId >= 0) + { + // no secure redirection for physical resources, only tab-specific requests can be redirected for ssl connections + if (portalSettings.ActiveTab != null) + { + result.DebugMessages.Add("ActiveTab: " + portalSettings.ActiveTab.TabID.ToString() + "/" + + portalSettings.ActiveTab.TabName + " IsSecure: " + + portalSettings.ActiveTab.IsSecure.ToString()); + + switch (portalSettings.SSLSetup) + { + case Abstractions.Security.SiteSslSetup.On: + if (!result.IsSecureConnection) + { + redirectSecure = true; + url = url.Replace("http://", "https://"); + } + + break; + case Abstractions.Security.SiteSslSetup.Advanced: + // 717 : check page is secure, connection is not secure + // 952 : support SSl Offloading in DNN 6.2+ + if (portalSettings.ActiveTab.IsSecure && !result.IsSecureConnection && !result.IsSSLOffloaded) + { + redirectSecure = true; + string stdUrl = portalSettings.STDURL; + string sslUrl = portalSettings.SSLURL; + if (string.IsNullOrEmpty(result.HttpAlias) == false) + { + stdUrl = result.HttpAlias; + } + + url = url.Replace("http://", "https://"); + url = this.ReplaceDomainName(url, stdUrl, sslUrl); + } + + if (portalSettings.SSLEnforced) + { + // Prevent browser's mixed-content error in case we open a secure PopUp or a secure iframe + // from an unsecure page + if (!portalSettings.ActiveTab.IsSecure && + result.IsSecureConnection && + !UrlUtils.IsPopUp(url)) + { + // has connection already been forced to secure? + if (queryStringCol["ssl"] == null) + { + // no? well this page shouldn't be secure + string stdUrl = portalSettings.STDURL; + string sslUrl = portalSettings.SSLURL; + url = url.Replace("https://", "http://"); + url = this.ReplaceDomainName(url, sslUrl, stdUrl); + redirectSecure = true; + } + } + } + + break; + } + } + + if (redirectSecure) + { + // now check to see if excluded. Why now? because less requests are made to redirect secure, + // so we don't have to check the exclusion as often. + bool exclude = false; + string doNotRedirectSecureRegex = settings.DoNotRedirectSecureRegex; + if (!string.IsNullOrEmpty(doNotRedirectSecureRegex)) + { + // match the raw url + exclude = Regex.IsMatch(result.RawUrl, doNotRedirectSecureRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + if (!exclude) + { + result.Action = ActionType.Redirect302Now; + result.Reason = RedirectReason.Secure_Page_Requested; + + // 760 : get the culture specific home page tabid for a redirect comparison + int homePageTabId = portalSettings.HomeTabId; + homePageTabId = TabPathHelper.GetHomePageTabIdForCulture( + portalSettings.DefaultLanguage, + portalSettings.PortalId, + result.CultureCode, + homePageTabId); + if (result.TabId == homePageTabId) + { + // replace the /default.aspx in the Url if it was found + url = DefaultPageRegex.Replace(url, "/"); + } + + result.FinalUrl = url; + } + else + { + // 702 : change return value if exclusion has occured + redirectSecure = false; + } + } + } + + return redirectSecure; + } + + private string ReplaceDomainName(string url, string replaceDomain, string withDomain) + { + if (replaceDomain != string.Empty && withDomain != string.Empty) + { + // 951 : change find/replace routine to regex for more accurate replacement + // (previous method gives false positives if the SSL Url is contained within the STD url) + string find = @"(?<=https?://)" + Regex.Escape(withDomain); + if (Regex.IsMatch(url, find, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) == false) + { + string replaceFind = @"(?<=https?://)" + Regex.Escape(replaceDomain); + url = Regex.Replace(url, replaceFind, withDomain, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + } + + return url; + } + + private void IdentifyPortalAlias( + HttpContext context, + HttpRequest request, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + Guid parentTraceId) + { + // get the domain name of the request, if it isn't already supplied + if (request != null && string.IsNullOrEmpty(result.DomainName)) + { + result.DomainName = Globals.GetDomainName(request); // parse the domain name out of the request + } + + // get tabId from querystring ( this is mandatory for maintaining portal context for child portals ) + if (queryStringCol["tabid"] != null) + { + string raw = queryStringCol["tabid"]; + int tabId; + if (int.TryParse(raw, out tabId)) + { + result.TabId = tabId; + } + else + { + // couldn't parse tab id + // split in two? + string[] tabids = raw.Split(','); + if (tabids.GetUpperBound(0) > 0) + { + // hmm more than one tabid + if (int.TryParse(tabids[0], out tabId)) + { + result.TabId = tabId; + + // but we want to warn against this! + var ex = + new Exception( + "Illegal request exception : Two TabId parameters provided in a single request: " + + requestUri); + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + + result.Ex = ex; + } + else + { + // yeah, nothing, divert to 404 + result.Action = ActionType.Output404; + var ex = + new Exception( + "Illegal request exception : TabId parameters in query string, but invalid TabId requested : " + + requestUri); + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + result.Ex = ex; + } + } + } + } + + // get PortalId from querystring ( this is used for host menu options as well as child portal navigation ) + if (queryStringCol["portalid"] != null) + { + string raw = queryStringCol["portalid"]; + int portalId; + if (int.TryParse(raw, out portalId)) + { + // 848 : if portal already found is different to portal id in querystring, then load up different alias + // this is so the portal settings will be loaded correctly. + if (result.PortalId != portalId) + { + // portal id different to what we expected + result.PortalId = portalId; + + // check the loaded portal alias, because it might be wrong + if (result.PortalAlias != null && result.PortalAlias.PortalID != portalId) + { + // yes, the identified portal alias is wrong. Find the correct alias for this portal + PortalAliasInfo pa = TabIndexController.GetPortalAliasByPortal(portalId, result.DomainName); + if (pa != null) + { + // note: sets portal id and portal alias + result.PortalAlias = pa; + } + } + } + } + } + else + { + // check for a portal alias if there's no portal Id in the query string + // check for absence of captcha value, because the captcha string re-uses the alias querystring value + if (queryStringCol["alias"] != null && queryStringCol["captcha"] == null) + { + string alias = queryStringCol["alias"]; + PortalAliasInfo portalAlias = PortalAliasController.Instance.GetPortalAlias(alias); + if (portalAlias != null) + { + // ok the portal alias was found by the alias name + // check if the alias contains the domain name + if (alias.Contains(result.DomainName) == false) + { + // replaced to the domain defined in the alias + if (request != null) + { + string redirectDomain = Globals.GetPortalDomainName(alias, request, true); + + // retVal.Url = redirectDomain; + result.FinalUrl = redirectDomain; + result.Action = ActionType.Redirect302Now; + result.Reason = RedirectReason.Alias_In_Url; + } + } + else + { + // the alias is the same as the current domain + result.HttpAlias = portalAlias.HTTPAlias; + result.PortalAlias = portalAlias; + result.PortalId = portalAlias.PortalID; + + // don't use this crap though - we don't want ?alias=portalAlias in our Url + if (result.RedirectAllowed) + { + string redirect = requestUri.Scheme + Uri.SchemeDelimiter + result.PortalAlias.HTTPAlias + + "/"; + result.Action = ActionType.Redirect301; + result.FinalUrl = redirect; + result.Reason = RedirectReason.Unfriendly_Url_Child_Portal; + } + } + } + } + } + + // first try and identify the portal using the tabId, but only if we identified this tab by looking up the tabid + // from the original url + // 668 : error in child portal redirects to child portal home page because of mismatch in tab/domain name + if (result.TabId != -1 && result.FriendlyRewrite == false) + { + // get the alias from the tabid, but only if it is for a tab in that domain + // 2.0 : change to compare retrieved alias to the already-set httpAlias + string httpAliasFromTab = PortalAliasController.GetPortalAliasByTab(result.TabId, result.DomainName); + if (httpAliasFromTab != null) + { + // 882 : account for situation when portalAlias is null. + if ((result.PortalAlias != null && string.Compare(result.PortalAlias.HTTPAlias, httpAliasFromTab, StringComparison.OrdinalIgnoreCase) != 0) + || result.PortalAlias == null) + { + // 691 : change logic to force change in portal alias context rather than force back. + // This is because the tabid in the query string should take precedence over the portal alias + // to handle parent.com/default.aspx?tabid=xx where xx lives in parent.com/child/ + var tab = TabController.Instance.GetTab(result.TabId, Null.NullInteger, false); + + // when result alias is null or result alias is different from tab-identified portalAlias + if (tab != null && (result.PortalAlias == null || tab.PortalID != result.PortalAlias.PortalID)) + { + // the tabid is different to the identified portalid from the original alias identified + // so get a new alias + PortalAliasInfo tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(httpAliasFromTab, tab.PortalID); + if (tabPortalAlias != null) + { + result.PortalId = tabPortalAlias.PortalID; + result.PortalAlias = tabPortalAlias; + result.Action = ActionType.CheckFor301; + result.Reason = RedirectReason.Wrong_Portal; + } + } + } + } + } + + // if no alias, try and set by using the identified http alias or domain name + if (result.PortalAlias == null) + { + if (!string.IsNullOrEmpty(result.HttpAlias)) + { + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias); + } + else + { + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); + if (result.PortalAlias == null && result.DomainName.EndsWith("/")) + { + result.DomainName = result.DomainName.TrimEnd('/'); + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); + } + } + } + + if (result.PortalId == -1) + { + if (!requestUri.LocalPath.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // allows requests for aspx pages in custom folder locations to be processed + return; + } + + // the domain name was not found so try using the host portal's first alias + if (Host.HostPortalID != -1) + { + result.PortalId = Host.HostPortalID; + + // use the host portal, but replaced to the host portal home page + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (aliases.Count > 0) + { + string alias = null; + + // get the alias as the chosen portal alias for the host portal based on the result culture code + var cpa = aliases.GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, settings); + if (cpa != null) + { + alias = cpa.HTTPAlias; + } + + if (alias != null) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Host_Portal_Used; + string destUrl = MakeUrlWithAlias(requestUri, alias); + destUrl = CheckForSiteRootRedirect(alias, destUrl); + result.FinalUrl = destUrl; + } + else + { + // Get the first Alias for the host portal + result.PortalAlias = aliases[result.PortalId]; + string url = MakeUrlWithAlias(requestUri, result.PortalAlias); + if (result.TabId != -1) + { + url += requestUri.Query; + } + + result.FinalUrl = url; + result.Reason = RedirectReason.Host_Portal_Used; + result.Action = ActionType.Redirect302Now; + } + } + } + } + + // double check to make sure we still have the correct alias now that all other information is known (ie tab, portal, culture) + // 770 : redirect alias based on tab id when custom alias used + if (result.TabId == -1 && result.Action == ActionType.CheckFor301 && + result.Reason == RedirectReason.Custom_Tab_Alias) + { + // here because the portal alias matched, but no tab was found, and because there are custom tab aliases used for this portal + // need to redirect back to the chosen portal alias and keep the current path. + string wrongAlias = result.HttpAlias; // it's a valid alias, but only for certain tabs + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (primaryAliases != null && result.PortalId > -1) + { + // going to look for the correct alias based on the culture of the request + string requestCultureCode = result.CultureCode; + + // if that didn't work use the default language of the portal + if (requestCultureCode == null) + { + // this might end up in a double redirect if the path of the Url is for a specific language as opposed + // to a path belonging to the default language domain + PortalInfo portal = PortalController.Instance.GetPortal(result.PortalId); + if (portal != null) + { + requestCultureCode = portal.DefaultLanguage; + } + } + + // now that the culture code is known, look up the correct portal alias for this portalid/culture code + var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, requestCultureCode, settings); + if (cpa != null) + { + // if an alias was found that matches the request and the culture code, then run with that + string rightAlias = cpa.HTTPAlias; + + // will cause a redirect to the primary portal alias - we know now that there was no custom alias tab + // found, so it's just a plain wrong alias + ConfigurePortalAliasRedirect(ref result, wrongAlias, rightAlias, true, settings.InternalAliasList, settings); + } + } + } + else + { + // then check to make sure it's the chosen portal alias for this portal + // 627 : don't do it if we're redirecting to the host portal + if (result.RedirectAllowed && result.Reason != RedirectReason.Host_Portal_Used) + { + string primaryAlias; + + // checking again in case the rewriting operation changed the values for the valid portal alias + bool incorrectAlias = this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryAlias); + if (incorrectAlias) + { + RedirectPortalAlias(primaryAlias, ref result, settings); + } + } + } + + // check to see if we have to avoid the core 302 redirect for the portal alias that is in the /defualt.aspx + // for child portals + // exception to that is when a custom alias is used but no rewrite has taken place + if (result.DoRewrite == false && (result.Action == ActionType.Continue + || + (result.Action == ActionType.CheckFor301 && + result.Reason == RedirectReason.Custom_Tab_Alias))) + { + string aliasQuerystring; + bool isChildAliasRootUrl = CheckForChildPortalRootUrl(requestUri.AbsoluteUri, result, out aliasQuerystring); + if (isChildAliasRootUrl) + { + RewriteAsChildAliasRoot(context, result, aliasQuerystring, settings); + } + } + } + + private void SecurityCheck(HttpApplication app) + { + HttpRequest request = app.Request; + HttpServerUtility server = app.Server; + + // 675 : unnecessarily strict url validation + // URL validation + // check for ".." escape characters commonly used by hackers to traverse the folder tree on the server + // the application should always use the exact relative location of the resource it is requesting + var strURL = request.Url.AbsolutePath; + var strDoubleDecodeURL = server.UrlDecode(server.UrlDecode(request.Url.AbsolutePath)) ?? string.Empty; + if (UrlSlashesRegex.Match(strURL).Success || UrlSlashesRegex.Match(strDoubleDecodeURL).Success) + { + throw new HttpException(404, "Not Found"); + } + } + } +} \ No newline at end of file diff --git a/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs b/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs new file mode 100644 index 00000000000..e702a520d42 --- /dev/null +++ b/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs @@ -0,0 +1,9 @@ +using System.Web.Mvc; + +namespace DotNetNuke.Web.MvcPipeline.Integration.Framework +{ + internal interface IMvcServiceFrameworkInternals + { + void RegisterAjaxScript(ControllerContext page); + } +} diff --git a/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs b/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs new file mode 100644 index 00000000000..aeda0af76df --- /dev/null +++ b/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs @@ -0,0 +1,558 @@ +// 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.Framework.JavaScriptLibraries +{ + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Installer.Packages; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Log.EventLog; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.MvcPipeline.Integration.Mvc; + using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Web; + using System.Web.Mvc; + using Globals = DotNetNuke.Common.Globals; + + public class MvcJavaScript + { + private const string ScriptPrefix = "JSL."; + private const string LegacyPrefix = "LEGACY."; + + private const string JQueryUIDebugFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.js"; + private const string JQueryUIMinFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.min.js"; + + /// Initializes a new instance of the class. + protected MvcJavaScript() + { + } + + /// checks whether the script file is a known javascript library. + /// script identifier. + /// if a library with the given name is installed, otherwise . + public static bool IsInstalled(string jsname) + { + JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)); + return library != null; + } + + /// determine whether to use the debug script for a file. + /// whether to use the debug script. + public static bool UseDebugScript() + { + if (Globals.Status != Globals.UpgradeStatus.None) + { + return false; + } + + return HttpContextSource.Current.IsDebuggingEnabled; + } + + /// Returns the version of a javascript library from the database. + /// the library name. + /// The highest version number of the library or . + public static string Version(string jsname) + { + JavaScriptLibrary library = GetHighestVersionLibrary(jsname); + return library != null ? Convert.ToString(library.Version) : string.Empty; + } + + /// Requests a script to be added to the page. + /// the library name. + public static void RequestRegistration(string jsname) + { + // handle case where script has no javascript library + switch (jsname) + { + case CommonJs.jQuery: + // RequestRegistration(CommonJs.jQueryMigrate); + break; + } + + RequestRegistration(jsname, null, SpecificVersion.Latest); + } + + /// Requests a script to be added to the page. + /// the library name. + /// the library's version. + public static void RequestRegistration(string jsname, Version version) + { + RequestRegistration(jsname, version, SpecificVersion.Exact); + } + + /// Requests a script to be added to the page. + /// the library name. + /// the library's version. + /// + /// how much of the to pay attention to. + /// When is passed, ignore the . + /// When is passed, match the major version. + /// When is passed, match the major and minor versions. + /// When is passed, match all parts of the version. + /// + public static void RequestRegistration(string jsname, Version version, SpecificVersion specific) + { + switch (specific) + { + case SpecificVersion.Latest: + RequestHighestVersionLibraryRegistration(jsname); + return; + case SpecificVersion.LatestMajor: + case SpecificVersion.LatestMinor: + if (RequestLooseVersionLibraryRegistration(jsname, version, specific)) + { + return; + } + + break; + case SpecificVersion.Exact: + RequestSpecificVersionLibraryRegistration(jsname, version); + return; + } + + // this should only occur if packages are incorrect or a RequestRegistration call has a typo + LogCollision(string.Format("Missing specific version library - {0},{1},{2}", jsname, version, specific)); + } + + /// method is called once per page event cycle and will load all scripts requested during that page processing cycle. + /// reference to the current page. + public static void Register(ControllerContext page) + { + HandlePreInstallorLegacyItemRequests(page); + IEnumerable scripts = GetScriptVersions(); + IEnumerable finalScripts = ResolveVersionConflicts(scripts); + foreach (JavaScriptLibrary jsl in finalScripts) + { + if (jsl.LibraryName != "jQuery-Migrate") + { + RegisterScript(page, jsl); + } + } + } + + public static string JQueryUIFile(bool getMinFile) + { + string jfile = JQueryUIDebugFile; + if (getMinFile) + { + jfile = JQueryUIMinFile; + } + + return jfile; + } + + public static string GetJQueryScriptReference() + { +#pragma warning disable 618 + string scriptsrc = jQuery.HostedUrl; + if (!jQuery.UseHostedScript) + { + scriptsrc = jQuery.JQueryFile(!jQuery.UseDebugScript); + } + + return scriptsrc; +#pragma warning restore 618 + } + + public static void RegisterClientReference(ControllerContext page, ClientAPI.ClientNamespaceReferences reference) + { + switch (reference) + { + case ClientAPI.ClientNamespaceReferences.dnn: + case ClientAPI.ClientNamespaceReferences.dnn_dom: + if (HttpContextSource.Current.Items.Contains(LegacyPrefix + "dnn.js")) + { + break; + } + + // MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "MicrosoftAjax.js", 10); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "mvc.js", 11); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.js", 12); + HttpContextSource.Current.Items.Add(LegacyPrefix + "dnn.js", true); + + // page.ClientScript.RegisterClientScriptBlock(page.GetType(), "dnn.js", string.Empty); + if (!ClientAPI.BrowserSupportsFunctionality(ClientAPI.ClientFunctionality.SingleCharDelimiters)) + { + MvcClientAPI.RegisterClientVariable("__scdoff", "1", true); + } + + if (!ClientAPI.UseExternalScripts) + { + MvcClientAPI.RegisterEmbeddedResource("dnn.scripts.js", typeof(ClientAPI)); + } + + break; + case ClientAPI.ClientNamespaceReferences.dnn_dom_positioning: + RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.dom.positioning.js", 13); + break; + } + } + + private static void RequestHighestVersionLibraryRegistration(string jsname) + { + var library = GetHighestVersionLibrary(jsname); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + } + else + { + // covers case where upgrading to 7.2.0 and JSL's are not installed + AddPreInstallorLegacyItemRequest(jsname); + } + } + + private static bool RequestLooseVersionLibraryRegistration(string jsname, Version version, SpecificVersion specific) + { + Func isValidLibrary = specific == SpecificVersion.LatestMajor + ? (Func)(l => l.Version.Major == version.Major && l.Version.Minor >= version.Minor) + : l => l.Version.Major == version.Major && l.Version.Minor == version.Minor && l.Version.Build >= version.Build; + var library = JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .FirstOrDefault(isValidLibrary); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + return true; + } + + // unable to find a higher major version + library = GetHighestVersionLibrary(jsname); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + LogCollision("Requested:" + jsname + ":" + version + ":" + specific + ".Resolved:" + library.Version); + return true; + } + + return false; + } + + private static void RequestSpecificVersionLibraryRegistration(string jsname, Version version) + { + JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase) && l.Version == version); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + } + else + { + // this will only occur if a specific library is requested and not available + LogCollision(string.Format("Missing Library request - {0} : {1}", jsname, version)); + } + } + + private static void AddItemRequest(int javaScriptLibraryId) + { + HttpContextSource.Current.Items[ScriptPrefix + javaScriptLibraryId] = true; + } + + private static void AddPreInstallorLegacyItemRequest(string jsl) + { + HttpContextSource.Current.Items[LegacyPrefix + jsl] = true; + } + + private static IEnumerable ResolveVersionConflicts(IEnumerable scripts) + { + var finalScripts = new List(); + foreach (string libraryId in scripts) + { + var processingLibrary = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString(CultureInfo.InvariantCulture) == libraryId); + + var existingLatestLibrary = finalScripts.FindAll(lib => lib.LibraryName.Equals(processingLibrary.LibraryName, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .SingleOrDefault(); + if (existingLatestLibrary != null) + { + // determine previous registration for same JSL + if (existingLatestLibrary.Version > processingLibrary.Version) + { + // skip new library & log + var collisionText = string.Format( + CultureInfo.CurrentCulture, + "{0}-{1} -> {2}-{3}", + existingLatestLibrary.LibraryName, + existingLatestLibrary.Version, + processingLibrary.LibraryName, + processingLibrary.Version); + LogCollision(collisionText); + } + else if (existingLatestLibrary.Version != processingLibrary.Version) + { + finalScripts.Remove(existingLatestLibrary); + finalScripts.Add(processingLibrary); + } + } + else + { + finalScripts.Add(processingLibrary); + } + } + + return finalScripts; + } + + private static JavaScriptLibrary GetHighestVersionLibrary(string jsname) + { + if (Globals.Status == Globals.UpgradeStatus.Install) + { + // if in install process, then do not use JSL but all use the legacy versions. + return null; + } + + try + { + return JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .FirstOrDefault(); + } + catch (Exception) + { + // no library found (install or upgrade) + return null; + } + } + + private static string GetScriptPath(JavaScriptLibrary js, HttpRequest request) + { + if (Host.CdnEnabled) + { + // load custom CDN path setting + var customCdn = HostController.Instance.GetString("CustomCDN_" + js.LibraryName); + if (!string.IsNullOrEmpty(customCdn)) + { + return customCdn; + } + + // cdn enabled but jsl does not have one defined + if (!string.IsNullOrEmpty(js.CDNPath)) + { + var cdnPath = js.CDNPath; + if (cdnPath.StartsWith("//")) + { + cdnPath = $"{(UrlUtils.IsSecureConnectionOrSslOffload(request) ? "https" : "http")}:{cdnPath}"; + } + + return cdnPath; + } + } + + return "~/Resources/libraries/" + js.LibraryName + "/" + Globals.FormatVersion(js.Version, "00", 3, "_") + "/" + js.FileName; + } + + private static string GetScriptLocation(JavaScriptLibrary js) + { + switch (js.PreferredScriptLocation) + { + case ScriptLocation.PageHead: + return "DnnPageHeaderProvider"; + case ScriptLocation.BodyBottom: + return "DnnFormBottomProvider"; + case ScriptLocation.BodyTop: + return "DnnBodyProvider"; + } + + return string.Empty; + } + + private static IEnumerable GetScriptVersions() + { + List orderedScripts = (from object item in HttpContextSource.Current.Items.Keys + where item.ToString().StartsWith(ScriptPrefix) + select item.ToString().Substring(4)).ToList(); + orderedScripts.Sort(); + List finalScripts = orderedScripts.ToList(); + foreach (string libraryId in orderedScripts) + { + // find dependencies + var library = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString() == libraryId); + if (library == null) + { + continue; + } + + foreach (var dependencyLibrary in GetAllDependencies(library).Distinct()) + { + if (HttpContextSource.Current.Items[ScriptPrefix + "." + dependencyLibrary.JavaScriptLibraryID] == null) + { + finalScripts.Add(dependencyLibrary.JavaScriptLibraryID.ToString()); + } + } + } + + return finalScripts; + } + + private static IEnumerable GetAllDependencies(JavaScriptLibrary library) + { + var package = PackageController.Instance.GetExtensionPackage(Null.NullInteger, p => p.PackageID == library.PackageID); + foreach (var dependency in package.Dependencies) + { + var dependencyLibrary = GetHighestVersionLibrary(dependency.PackageName); + yield return dependencyLibrary; + + foreach (var childDependency in GetAllDependencies(dependencyLibrary)) + { + yield return childDependency; + } + } + } + + private static void LogCollision(string collisionText) + { + // need to log an event + EventLogController.Instance.AddLog( + "Javascript Libraries", + collisionText, + PortalController.Instance.GetCurrentPortalSettings(), + UserController.Instance.GetCurrentUserInfo().UserID, + EventLogController.EventLogType.SCRIPT_COLLISION); + string strMessage = Localization.GetString("ScriptCollision", Localization.SharedResourceFile); + /* + var page = HttpContextSource.Current.Handler as Page; + if (page != null) + { + Skin.AddPageMessage(page, string.Empty, strMessage, ModuleMessage.ModuleMessageType.YellowWarning); + } + */ + } + + private static void RegisterScript(ControllerContext page, JavaScriptLibrary jsl) + { + if (string.IsNullOrEmpty(jsl.FileName)) + { + return; + } + + MvcClientResourceManager.RegisterScript(page, GetScriptPath(jsl, HttpContext.Current.Request), GetFileOrder(jsl), GetScriptLocation(jsl), jsl.LibraryName, jsl.Version.ToString(3)); + /* + if (Host.CdnEnabled && !string.IsNullOrEmpty(jsl.ObjectName)) + { + string pagePortion; + switch (jsl.PreferredScriptLocation) + { + case ScriptLocation.PageHead: + + pagePortion = "ClientDependencyHeadJs"; + break; + case ScriptLocation.BodyBottom: + pagePortion = "ClientResourcesFormBottom"; + break; + case ScriptLocation.BodyTop: + pagePortion = "BodySCRIPTS"; + break; + default: + pagePortion = "BodySCRIPTS"; + break; + } + + Control scriptloader = page.FindControl(pagePortion); + var fallback = new DnnJsIncludeFallback(jsl.ObjectName, VirtualPathUtility.ToAbsolute("~/Resources/libraries/" + jsl.LibraryName + "/" + Globals.FormatVersion(jsl.Version, "00", 3, "_") + "/" + jsl.FileName)); + if (scriptloader != null) + { + // add the fallback control after script loader. + var index = scriptloader.Parent.Controls.IndexOf(scriptloader); + scriptloader.Parent.Controls.AddAt(index + 1, fallback); + } + } + */ + } + + private static int GetFileOrder(JavaScriptLibrary jsl) + { + switch (jsl.LibraryName) + { + case CommonJs.jQuery: + return (int)FileOrder.Js.jQuery; + case CommonJs.jQueryMigrate: + return (int)FileOrder.Js.jQueryMigrate; + case CommonJs.jQueryUI: + return (int)FileOrder.Js.jQueryUI; + case CommonJs.HoverIntent: + return (int)FileOrder.Js.HoverIntent; + default: + return jsl.PackageID + (int)FileOrder.Js.DefaultPriority; + } + } + + private static void HandlePreInstallorLegacyItemRequests(ControllerContext page) + { + List legacyScripts = (from object item in HttpContextSource.Current.Items.Keys + where item.ToString().StartsWith(LegacyPrefix) + select item.ToString().Substring(7)).ToList(); +#pragma warning disable 618 + foreach (string legacyScript in legacyScripts) + { + switch (legacyScript) + { + case CommonJs.jQuery: + if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryScriptReference(), + FileOrder.Js.jQuery, + "DnnPageHeaderProvider"); + } + + /* + if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryMigrateScriptReference(), + FileOrder.Js.jQueryMigrate, + "DnnPageHeaderProvider"); + } + */ + break; + case CommonJs.jQueryUI: + // register dependency + if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryScriptReference(), + FileOrder.Js.jQuery, + "DnnPageHeaderProvider"); + } + + /* + if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryMigrateScriptReference(), + FileOrder.Js.jQueryMigrate, + "DnnPageHeaderProvider"); + } + */ + + // actual jqueryui + if (GetHighestVersionLibrary(CommonJs.jQueryUI) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryUIScriptReference(), + FileOrder.Js.jQueryUI, + "DnnPageHeaderProvider"); + } + + break; + } + } +#pragma warning restore 618 + } + } +} \ No newline at end of file diff --git a/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs b/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs new file mode 100644 index 00000000000..0a83c82dbae --- /dev/null +++ b/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs @@ -0,0 +1,119 @@ +namespace DotNetNuke.Web.MvcPipeline.Integration.Framework +{ + using System.Globalization; + using System.Web.Helpers; + using System.Web.Mvc; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Integration.Mvc; + using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; + + internal class MvcServicesFrameworkImpl : IServicesFramework, IMvcServiceFrameworkInternals + { + private const string AntiForgeryKey = "dnnAntiForgeryRequested"; + private const string ScriptKey = "dnnSFAjaxScriptRequested"; + + /// + public bool IsAjaxAntiForgerySupportRequired + { + get { return CheckKey(AntiForgeryKey); } + } + + /// + public bool IsAjaxScriptSupportRequired + { + get { return CheckKey(ScriptKey); } + } + + /// + public void RequestAjaxAntiForgerySupport() + { + this.RequestAjaxScriptSupport(); + SetKey(AntiForgeryKey); + } + + /// + public void RegisterAjaxAntiForgery(Page page) + { + var ctl = page.FindControl("ClientResourcesFormBottom"); + if (ctl != null) + { + ctl.Controls.Add(new LiteralControl(AntiForgery.GetHtml().ToHtmlString())); + } + } + + /// + public void RequestAjaxScriptSupport() + { + JavaScript.RequestRegistration(CommonJs.jQuery); + SetKey(ScriptKey); + } + + /// + public void RegisterAjaxScript(Page page) + { + var path = ServicesFramework.GetServiceFrameworkRoot(); + if (string.IsNullOrEmpty(path)) + { + return; + } + + JavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + ClientAPI.RegisterClientVariable(page, "sf_siteRoot", path, /*overwrite*/ true); + ClientAPI.RegisterClientVariable(page, "sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); + + string scriptPath; + if (HttpContextSource.Current.IsDebuggingEnabled) + { + scriptPath = "~/js/Debug/dnn.servicesframework.js"; + } + else + { + scriptPath = "~/js/dnn.servicesframework.js"; + } + + ClientResourceManager.RegisterScript(page, scriptPath); + } + + public void RegisterAjaxScript(ControllerContext page) + { + var path = ServicesFramework.GetServiceFrameworkRoot(); + if (string.IsNullOrEmpty(path)) + { + return; + } + + MvcJavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + MvcClientAPI.RegisterClientVariable("sf_siteRoot", path, /*overwrite*/ true); + MvcClientAPI.RegisterClientVariable("sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); + + string scriptPath; + if (HttpContextSource.Current.IsDebuggingEnabled) + { + scriptPath = "~/js/Debug/dnn.servicesframework.js"; + } + else + { + scriptPath = "~/js/dnn.servicesframework.js"; + } + + MvcClientResourceManager.RegisterScript(page, scriptPath); + } + + private static void SetKey(string key) + { + HttpContextSource.Current.Items[key] = true; + } + + private static bool CheckKey(string antiForgeryKey) + { + return HttpContextSource.Current.Items.Contains(antiForgeryKey); + } + } +} \ No newline at end of file diff --git a/DNN Platform/Library/Mvc/MvcClientAPI.cs b/DNN Platform/Library/Mvc/MvcClientAPI.cs new file mode 100644 index 00000000000..28086b9893b --- /dev/null +++ b/DNN Platform/Library/Mvc/MvcClientAPI.cs @@ -0,0 +1,63 @@ +// 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.Mvc +{ + using System; + using System.Collections.Generic; + using System.Web; + + public class MvcClientAPI + { + public static Dictionary GetClientVariableList() + { + var dic = HttpContext.Current.Items["CAPIVariableList"] as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + HttpContext.Current.Items["CAPIVariableList"] = dic; + } + + return dic; + } + + public static Dictionary GetClientStartupScriptList() + { + var dic = HttpContext.Current.Items["CAPIStartupScriptList"] as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + HttpContext.Current.Items["CAPIStartupScriptList"] = dic; + } + + return dic; + } + + public static void RegisterClientVariable(string key, string value, bool overwrite) + { + GetClientVariableList().Add(key, value); + } + + public static void RegisterEmbeddedResource(string fileName, Type assemblyType) + { + // RegisterClientVariable(FileName + ".resx", ThePage.ClientScript.GetWebResourceUrl(AssemblyType, FileName), true); + } + + public static void RegisterStartupScript(string key, string value) + { + if (!GetClientStartupScriptList().ContainsKey(key)) + { + GetClientStartupScriptList().Add(key, value); + } + } + + public static void RegisterScript(string key, string value) + { + if (!GetClientStartupScriptList().ContainsKey(key)) + { + GetClientStartupScriptList().Add(key, value); + } + } + } +} diff --git a/DNN Platform/Library/Mvc/MvcUtils.cs b/DNN Platform/Library/Mvc/MvcUtils.cs new file mode 100644 index 00000000000..290a6275319 --- /dev/null +++ b/DNN Platform/Library/Mvc/MvcUtils.cs @@ -0,0 +1,40 @@ +// 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.Mvc +{ + using System.IO; + + using DotNetNuke.Entities.Modules; + + public class MvcUtils + { + public static string GetControlViewName(ModuleInfo module) + { + return GetControlViewName(module, Path.GetFileNameWithoutExtension(module.ModuleControl.ControlSrc)); + } + + public static string GetControlViewName(ModuleInfo module, string viewName) + { + return "~/" + Path.GetDirectoryName(module.ModuleControl.ControlSrc) + "/Views/" + viewName + ".cshtml"; + } + + public static string GetControlControllerName(ModuleInfo module) + { + return GetControlControllerName(module.ModuleControl.ControlSrc); + } + + public static string GetControlControllerName(string controlSrc) + { + if (controlSrc.StartsWith("DesktopModules")) + { + return controlSrc.Replace("DesktopModules/", string.Empty).Replace("/", string.Empty).Replace(".ascx", string.Empty) + "View"; + } + else + { + return Path.GetFileNameWithoutExtension(controlSrc).Replace("/", string.Empty).Replace(".ascx", string.Empty) + "View"; + } + } + } +} diff --git a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs new file mode 100644 index 00000000000..de87540e96d --- /dev/null +++ b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs @@ -0,0 +1,15 @@ +// 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 +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +using DotNetNuke.Application; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: InternalsVisibleTo("DotNetNuke.Web.MvcPipeline")] + diff --git a/DNN_Platform.sln b/DNN_Platform.sln index 40e97c70e7c..a3313b634db 100644 --- a/DNN_Platform.sln +++ b/DNN_Platform.sln @@ -689,6 +689,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dnn.Skins.Aperture", "DNN P EndProject Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "Dnn.ClientSide", "DNN Platform\Dnn.ClientSide\Dnn.ClientSide.esproj", "{549CCB04-6321-4E6B-88C1-06FAC574D061}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.ContentSecurityPolicy", "DNN Platform\DotNetNuke.ContentSecurityPolicy\DotNetNuke.ContentSecurityPolicy.csproj", "{33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Web.MvcPipeline", "DNN Platform\DotNetNuke.Web.MvcPipeline\DotNetNuke.Web.MvcPipeline.csproj", "{AA3EE19B-81A0-3766-E8F4-424C2425A8D3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Cloud_Debug|Any CPU = Cloud_Debug|Any CPU @@ -2409,6 +2413,54 @@ Global {549CCB04-6321-4E6B-88C1-06FAC574D061}.Release-Net45|x86.ActiveCfg = Release|Any CPU {549CCB04-6321-4E6B-88C1-06FAC574D061}.Release-Net45|x86.Build.0 = Release|Any CPU {549CCB04-6321-4E6B-88C1-06FAC574D061}.Release-Net45|x86.Deploy.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Debug|Any CPU.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Debug|Any CPU.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Debug|x86.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Debug|x86.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Release|Any CPU.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Release|Any CPU.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Release|x86.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Cloud_Release|x86.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug|x86.ActiveCfg = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug|x86.Build.0 = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug-Net45|Any CPU.ActiveCfg = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug-Net45|Any CPU.Build.0 = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug-Net45|x86.ActiveCfg = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Debug-Net45|x86.Build.0 = Debug|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release|Any CPU.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release|x86.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release|x86.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release-Net45|Any CPU.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release-Net45|Any CPU.Build.0 = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release-Net45|x86.ActiveCfg = Release|Any CPU + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D}.Release-Net45|x86.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Debug|Any CPU.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Debug|Any CPU.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Debug|x86.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Debug|x86.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Release|Any CPU.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Release|Any CPU.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Release|x86.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Cloud_Release|x86.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug|x86.Build.0 = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug-Net45|Any CPU.ActiveCfg = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug-Net45|Any CPU.Build.0 = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug-Net45|x86.ActiveCfg = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Debug-Net45|x86.Build.0 = Debug|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release|Any CPU.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release|x86.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release|x86.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|Any CPU.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|Any CPU.Build.0 = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|x86.ActiveCfg = Release|Any CPU + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2552,6 +2604,8 @@ Global {4FBA4C58-559B-4CD2-9926-CDA3498FE20E} = {FBD3B3FB-C9A6-43D2-8FE7-6A0A19DF0D0C} {9F20422E-76EB-4B24-B721-B1CAF17407F4} = {04F3856F-18A5-4916-A0EB-D3CFE0858443} {549CCB04-6321-4E6B-88C1-06FAC574D061} = {29273BE6-1AA8-4970-98A0-41BFFEEDA67B} + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46B6A641-57EB-4B19-B199-23E6FC2AB40B} From 806a567f60a2cc5f7224b49490e68f42d154d247 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 12:02:48 +0100 Subject: [PATCH 002/146] Fixes --- .../MvcClientResourceManager.cs | 13 +- .../DotNetNuke.Web.MvcPipeline.csproj | 1 + .../Models/ModuleSettingsModel.cs | 2 - .../PermissionTriStateHelper.cs | 1 - .../Routing/DnnMvcPageHandler.cs | 3 - .../Skins/SkinHelpers.DnnCssInclude.cs | 9 +- .../Skins/SkinHelpers.DnnJsInclude.cs | 4 +- .../Entities/Urls/MvcAdvancedUrlRewriter.cs | 2 +- .../JavaScriptLibraries/MvcJavaScript.cs | 967 +++++++++--------- .../Library/Properties/AssemblyInfoMvc.cs | 6 - 10 files changed, 497 insertions(+), 511 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs index 5da3c5d726c..32f828765df 100644 --- a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs +++ b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs @@ -8,7 +8,6 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement using System; using System.Collections.Generic; using System.IO; - using System.Text.RegularExpressions; using System.Threading; using System.Web; using System.Web.Hosting; @@ -18,7 +17,6 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement using ClientDependency.Core; using ClientDependency.Core.CompositeFiles.Providers; using ClientDependency.Core.Config; - using ClientDependency.Core.Mvc; using DotNetNuke.Instrumentation; using DotNetNuke.Internal.SourceGenerators; @@ -317,8 +315,10 @@ public static void RegisterScript( // include.HtmlAttributes["defer"] = "defer"; include.HtmlAttributes["nonce"] = HttpContext.Current.Items["CSP-NONCE"].ToString(); - var loader = page.GetLoader(); - loader.RegisterDependency(include, include.HtmlAttributes); + + // TODO: fix this as GetLoader is missing + // var loader = page.GetLoader(); + // loader.RegisterDependency(include, include.HtmlAttributes); // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); } @@ -464,8 +464,9 @@ public static void RegisterStyleSheet(ControllerContext page, string filePath, i } } - var loader = page.GetLoader(); - loader.RegisterDependency(include); + // TODO: fix this as GetLoader is missing + // var loader = page.GetLoader(); + // loader.RegisterDependency(include); // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index 593d0ee29ab..47a0f01a043 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -20,6 +20,7 @@ Portable + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs index ef3f5df6dca..aa430ed76ea 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ModuleSettingsModel.cs @@ -9,9 +9,7 @@ namespace DotNetNuke.Web.MvcPipeline.Models using System.Web.Mvc; using DotNetNuke.Entities.Modules; - using DotNetNuke.Entities.Tabs; using DotNetNuke.Security.Permissions; - using DotNetNuke.Services.Mobile; public class ModuleSettingsModel { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs index 814d9cae7b9..6e7aca1450f 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs @@ -16,7 +16,6 @@ namespace DotNetNuke.MvcPipeline using DotNetNuke.Services.Localization; using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client.ClientResourceManagement; - using DotNetNuke.Web.MvcPipeline.Integration.Mvc; public static class PermissionTriStateHelper { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs index 040d71d3f54..52d05d4481c 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/DnnMvcPageHandler.cs @@ -9,12 +9,9 @@ namespace DotNetNuke.Web.MvcPipeline.Routing using System.Web.Routing; using System.Web.SessionState; - using DotNetNuke.ComponentModel; using DotNetNuke.Entities.Portals; using DotNetNuke.HttpModules.Membership; using DotNetNuke.Services.Localization; - using DotNetNuke.UI.Modules; - using DotNetNuke.Web.MvcPipeline.Routing; public class DnnMvcPageHandler : MvcHandler, IHttpHandler, IRequiresSessionState { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs index bc883451e33..1b8a324697d 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs @@ -4,15 +4,10 @@ namespace DotNetNuke.Web.MvcPipeline.Skins { - using System; - using System.Linq; - using System.Net.NetworkInformation; using System.Web; using System.Web.Mvc; using ClientDependency.Core; - using ClientDependency.Core.Mvc; - using DotNetNuke.Web.Client; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Models; @@ -20,7 +15,9 @@ public static partial class SkinHelpers { public static IHtmlString DnnCssInclude(this HtmlHelper helper, string filePath, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") { - helper.RequiresCss(filePath, pathNameAlias, priority); + // TODO: ClientDependency Core is deprecated and will not load. + // helper.RequiresCss(filePath, pathNameAlias, priority); + if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) { return new MvcHtmlString(string.Format("", ClientDependencyType.Css, filePath, forceProvider, priority)); diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs index a71fbd9e406..33cd0a9760a 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs @@ -11,7 +11,6 @@ namespace DotNetNuke.Web.MvcPipeline.Skins using System.Web.Mvc; using ClientDependency.Core; - using ClientDependency.Core.Mvc; using DotNetNuke.Web.MvcPipeline.Models; public static partial class SkinHelpers @@ -26,7 +25,8 @@ public static IHtmlString DnnJsInclude(this HtmlHelper helper, string htmlAttibs.Add("defer", "defer"); } - helper.RequiresJs(filePath, pathNameAlias, priority, htmlAttibs); + // TODO: ClientDependency Core is deprecated + // helper.RequiresJs(filePath, pathNameAlias, priority, htmlAttibs); if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) { diff --git a/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs b/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs index 37f5dc3ac71..9b6f3a9bb7d 100644 --- a/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs +++ b/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs @@ -3221,4 +3221,4 @@ private void SecurityCheck(HttpApplication app) } } } -} \ No newline at end of file +} diff --git a/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs b/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs index aeda0af76df..9db347f34b1 100644 --- a/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs +++ b/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs @@ -3,556 +3,555 @@ // See the LICENSE file in the project root for more information namespace DotNetNuke.Framework.JavaScriptLibraries { - using DotNetNuke.Common; - using DotNetNuke.Common.Utilities; - using DotNetNuke.Entities.Controllers; - using DotNetNuke.Entities.Host; - using DotNetNuke.Entities.Portals; - using DotNetNuke.Entities.Users; - using DotNetNuke.Services.Installer.Packages; - using DotNetNuke.Services.Localization; - using DotNetNuke.Services.Log.EventLog; - using DotNetNuke.UI.Utilities; - using DotNetNuke.Web.Client; - using DotNetNuke.Web.MvcPipeline.Integration.Mvc; - using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Web; - using System.Web.Mvc; - using Globals = DotNetNuke.Common.Globals; - - public class MvcJavaScript - { - private const string ScriptPrefix = "JSL."; - private const string LegacyPrefix = "LEGACY."; - - private const string JQueryUIDebugFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.js"; - private const string JQueryUIMinFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.min.js"; - - /// Initializes a new instance of the class. - protected MvcJavaScript() + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Web; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Installer.Packages; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Log.EventLog; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.MvcPipeline.Integration.Mvc; + using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; + + using Globals = DotNetNuke.Common.Globals; + + public class MvcJavaScript { - } - - /// checks whether the script file is a known javascript library. - /// script identifier. - /// if a library with the given name is installed, otherwise . - public static bool IsInstalled(string jsname) - { - JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)); - return library != null; - } - - /// determine whether to use the debug script for a file. - /// whether to use the debug script. - public static bool UseDebugScript() - { - if (Globals.Status != Globals.UpgradeStatus.None) - { - return false; - } + private const string ScriptPrefix = "JSL."; + private const string LegacyPrefix = "LEGACY."; - return HttpContextSource.Current.IsDebuggingEnabled; - } + private const string JQueryUIDebugFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.js"; + private const string JQueryUIMinFile = "~/Resources/Shared/Scripts/jquery/jquery-ui.min.js"; - /// Returns the version of a javascript library from the database. - /// the library name. - /// The highest version number of the library or . - public static string Version(string jsname) - { - JavaScriptLibrary library = GetHighestVersionLibrary(jsname); - return library != null ? Convert.ToString(library.Version) : string.Empty; - } + /// Initializes a new instance of the class. + protected MvcJavaScript() + { + } - /// Requests a script to be added to the page. - /// the library name. - public static void RequestRegistration(string jsname) - { - // handle case where script has no javascript library - switch (jsname) - { - case CommonJs.jQuery: - // RequestRegistration(CommonJs.jQueryMigrate); - break; - } - - RequestRegistration(jsname, null, SpecificVersion.Latest); - } + /// checks whether the script file is a known javascript library. + /// script identifier. + /// if a library with the given name is installed, otherwise . + public static bool IsInstalled(string jsname) + { + JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)); + return library != null; + } - /// Requests a script to be added to the page. - /// the library name. - /// the library's version. - public static void RequestRegistration(string jsname, Version version) - { - RequestRegistration(jsname, version, SpecificVersion.Exact); - } + /// determine whether to use the debug script for a file. + /// whether to use the debug script. + public static bool UseDebugScript() + { + if (Globals.Status != Globals.UpgradeStatus.None) + { + return false; + } - /// Requests a script to be added to the page. - /// the library name. - /// the library's version. - /// - /// how much of the to pay attention to. - /// When is passed, ignore the . - /// When is passed, match the major version. - /// When is passed, match the major and minor versions. - /// When is passed, match all parts of the version. - /// - public static void RequestRegistration(string jsname, Version version, SpecificVersion specific) - { - switch (specific) - { - case SpecificVersion.Latest: - RequestHighestVersionLibraryRegistration(jsname); - return; - case SpecificVersion.LatestMajor: - case SpecificVersion.LatestMinor: - if (RequestLooseVersionLibraryRegistration(jsname, version, specific)) - { - return; - } - - break; - case SpecificVersion.Exact: - RequestSpecificVersionLibraryRegistration(jsname, version); - return; - } - - // this should only occur if packages are incorrect or a RequestRegistration call has a typo - LogCollision(string.Format("Missing specific version library - {0},{1},{2}", jsname, version, specific)); - } + return HttpContextSource.Current.IsDebuggingEnabled; + } - /// method is called once per page event cycle and will load all scripts requested during that page processing cycle. - /// reference to the current page. - public static void Register(ControllerContext page) - { - HandlePreInstallorLegacyItemRequests(page); - IEnumerable scripts = GetScriptVersions(); - IEnumerable finalScripts = ResolveVersionConflicts(scripts); - foreach (JavaScriptLibrary jsl in finalScripts) - { - if (jsl.LibraryName != "jQuery-Migrate") + /// Returns the version of a javascript library from the database. + /// the library name. + /// The highest version number of the library or . + public static string Version(string jsname) { - RegisterScript(page, jsl); + JavaScriptLibrary library = GetHighestVersionLibrary(jsname); + return library != null ? Convert.ToString(library.Version) : string.Empty; } - } - } - public static string JQueryUIFile(bool getMinFile) - { - string jfile = JQueryUIDebugFile; - if (getMinFile) - { - jfile = JQueryUIMinFile; - } + /// Requests a script to be added to the page. + /// the library name. + public static void RequestRegistration(string jsname) + { + // handle case where script has no javascript library + switch (jsname) + { + case CommonJs.jQuery: + // RequestRegistration(CommonJs.jQueryMigrate); + break; + } - return jfile; - } + RequestRegistration(jsname, null, SpecificVersion.Latest); + } - public static string GetJQueryScriptReference() - { -#pragma warning disable 618 - string scriptsrc = jQuery.HostedUrl; - if (!jQuery.UseHostedScript) - { - scriptsrc = jQuery.JQueryFile(!jQuery.UseDebugScript); - } + /// Requests a script to be added to the page. + /// the library name. + /// the library's version. + public static void RequestRegistration(string jsname, Version version) + { + RequestRegistration(jsname, version, SpecificVersion.Exact); + } - return scriptsrc; -#pragma warning restore 618 - } + /// Requests a script to be added to the page. + /// the library name. + /// the library's version. + /// + /// how much of the to pay attention to. + /// When is passed, ignore the . + /// When is passed, match the major version. + /// When is passed, match the major and minor versions. + /// When is passed, match all parts of the version. + /// + public static void RequestRegistration(string jsname, Version version, SpecificVersion specific) + { + switch (specific) + { + case SpecificVersion.Latest: + RequestHighestVersionLibraryRegistration(jsname); + return; + case SpecificVersion.LatestMajor: + case SpecificVersion.LatestMinor: + if (RequestLooseVersionLibraryRegistration(jsname, version, specific)) + { + return; + } + + break; + case SpecificVersion.Exact: + RequestSpecificVersionLibraryRegistration(jsname, version); + return; + } - public static void RegisterClientReference(ControllerContext page, ClientAPI.ClientNamespaceReferences reference) - { - switch (reference) - { - case ClientAPI.ClientNamespaceReferences.dnn: - case ClientAPI.ClientNamespaceReferences.dnn_dom: - if (HttpContextSource.Current.Items.Contains(LegacyPrefix + "dnn.js")) - { - break; - } - - // MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "MicrosoftAjax.js", 10); - MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "mvc.js", 11); - MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.js", 12); - HttpContextSource.Current.Items.Add(LegacyPrefix + "dnn.js", true); - - // page.ClientScript.RegisterClientScriptBlock(page.GetType(), "dnn.js", string.Empty); - if (!ClientAPI.BrowserSupportsFunctionality(ClientAPI.ClientFunctionality.SingleCharDelimiters)) - { - MvcClientAPI.RegisterClientVariable("__scdoff", "1", true); - } - - if (!ClientAPI.UseExternalScripts) - { - MvcClientAPI.RegisterEmbeddedResource("dnn.scripts.js", typeof(ClientAPI)); - } - - break; - case ClientAPI.ClientNamespaceReferences.dnn_dom_positioning: - RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); - MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.dom.positioning.js", 13); - break; - } - } + // this should only occur if packages are incorrect or a RequestRegistration call has a typo + LogCollision(string.Format("Missing specific version library - {0},{1},{2}", jsname, version, specific)); + } - private static void RequestHighestVersionLibraryRegistration(string jsname) - { - var library = GetHighestVersionLibrary(jsname); - if (library != null) - { - AddItemRequest(library.JavaScriptLibraryID); - } - else - { - // covers case where upgrading to 7.2.0 and JSL's are not installed - AddPreInstallorLegacyItemRequest(jsname); - } - } + /// method is called once per page event cycle and will load all scripts requested during that page processing cycle. + /// reference to the current page. + public static void Register(ControllerContext page) + { + HandlePreInstallorLegacyItemRequests(page); + IEnumerable scripts = GetScriptVersions(); + IEnumerable finalScripts = ResolveVersionConflicts(scripts); + foreach (JavaScriptLibrary jsl in finalScripts) + { + if (jsl.LibraryName != "jQuery-Migrate") + { + RegisterScript(page, jsl); + } + } + } - private static bool RequestLooseVersionLibraryRegistration(string jsname, Version version, SpecificVersion specific) - { - Func isValidLibrary = specific == SpecificVersion.LatestMajor - ? (Func)(l => l.Version.Major == version.Major && l.Version.Minor >= version.Minor) - : l => l.Version.Major == version.Major && l.Version.Minor == version.Minor && l.Version.Build >= version.Build; - var library = JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(l => l.Version) - .FirstOrDefault(isValidLibrary); - if (library != null) - { - AddItemRequest(library.JavaScriptLibraryID); - return true; - } - - // unable to find a higher major version - library = GetHighestVersionLibrary(jsname); - if (library != null) - { - AddItemRequest(library.JavaScriptLibraryID); - LogCollision("Requested:" + jsname + ":" + version + ":" + specific + ".Resolved:" + library.Version); - return true; - } - - return false; - } + public static string JQueryUIFile(bool getMinFile) + { + return GetScriptPath(CommonJs.jQueryUI); + } - private static void RequestSpecificVersionLibraryRegistration(string jsname, Version version) - { - JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase) && l.Version == version); - if (library != null) - { - AddItemRequest(library.JavaScriptLibraryID); - } - else - { - // this will only occur if a specific library is requested and not available - LogCollision(string.Format("Missing Library request - {0} : {1}", jsname, version)); - } - } + public static string GetJQueryScriptReference() + { + return GetScriptPath(CommonJs.jQuery); + } - private static void AddItemRequest(int javaScriptLibraryId) - { - HttpContextSource.Current.Items[ScriptPrefix + javaScriptLibraryId] = true; - } + public static string GetScriptPath(string libraryName) + { + var library = JavaScriptLibraryController.Instance.GetLibrary(jsl => jsl.LibraryName.Equals(libraryName, StringComparison.OrdinalIgnoreCase)); + if (library == null) + { + return null; + } - private static void AddPreInstallorLegacyItemRequest(string jsl) - { - HttpContextSource.Current.Items[LegacyPrefix + jsl] = true; - } + return GetScriptPath(library, HttpContextSource.Current?.Request); + } - private static IEnumerable ResolveVersionConflicts(IEnumerable scripts) - { - var finalScripts = new List(); - foreach (string libraryId in scripts) - { - var processingLibrary = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString(CultureInfo.InvariantCulture) == libraryId); - - var existingLatestLibrary = finalScripts.FindAll(lib => lib.LibraryName.Equals(processingLibrary.LibraryName, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(l => l.Version) - .SingleOrDefault(); - if (existingLatestLibrary != null) + public static void RegisterClientReference(ControllerContext page, ClientAPI.ClientNamespaceReferences reference) { - // determine previous registration for same JSL - if (existingLatestLibrary.Version > processingLibrary.Version) - { - // skip new library & log - var collisionText = string.Format( - CultureInfo.CurrentCulture, - "{0}-{1} -> {2}-{3}", - existingLatestLibrary.LibraryName, - existingLatestLibrary.Version, - processingLibrary.LibraryName, - processingLibrary.Version); - LogCollision(collisionText); - } - else if (existingLatestLibrary.Version != processingLibrary.Version) - { - finalScripts.Remove(existingLatestLibrary); - finalScripts.Add(processingLibrary); - } + switch (reference) + { + case ClientAPI.ClientNamespaceReferences.dnn: + case ClientAPI.ClientNamespaceReferences.dnn_dom: + if (HttpContextSource.Current.Items.Contains(LegacyPrefix + "dnn.js")) + { + break; + } + + // MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "MicrosoftAjax.js", 10); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "mvc.js", 11); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.js", 12); + HttpContextSource.Current.Items.Add(LegacyPrefix + "dnn.js", true); + + // page.ClientScript.RegisterClientScriptBlock(page.GetType(), "dnn.js", string.Empty); + if (!ClientAPI.BrowserSupportsFunctionality(ClientAPI.ClientFunctionality.SingleCharDelimiters)) + { + MvcClientAPI.RegisterClientVariable("__scdoff", "1", true); + } + + if (!ClientAPI.UseExternalScripts) + { + MvcClientAPI.RegisterEmbeddedResource("dnn.scripts.js", typeof(ClientAPI)); + } + + break; + case ClientAPI.ClientNamespaceReferences.dnn_dom_positioning: + RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + MvcClientResourceManager.RegisterScript(page, ClientAPI.ScriptPath + "dnn.dom.positioning.js", 13); + break; + } } - else + + private static void RequestHighestVersionLibraryRegistration(string jsname) { - finalScripts.Add(processingLibrary); + var library = GetHighestVersionLibrary(jsname); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + } + else + { + // covers case where upgrading to 7.2.0 and JSL's are not installed + AddPreInstallorLegacyItemRequest(jsname); + } } - } - return finalScripts; - } + private static bool RequestLooseVersionLibraryRegistration(string jsname, Version version, SpecificVersion specific) + { + Func isValidLibrary = specific == SpecificVersion.LatestMajor + ? (Func)(l => l.Version.Major == version.Major && l.Version.Minor >= version.Minor) + : l => l.Version.Major == version.Major && l.Version.Minor == version.Minor && l.Version.Build >= version.Build; + var library = JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .FirstOrDefault(isValidLibrary); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + return true; + } - private static JavaScriptLibrary GetHighestVersionLibrary(string jsname) - { - if (Globals.Status == Globals.UpgradeStatus.Install) - { - // if in install process, then do not use JSL but all use the legacy versions. - return null; - } - - try - { - return JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(l => l.Version) - .FirstOrDefault(); - } - catch (Exception) - { - // no library found (install or upgrade) - return null; - } - } + // unable to find a higher major version + library = GetHighestVersionLibrary(jsname); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + LogCollision("Requested:" + jsname + ":" + version + ":" + specific + ".Resolved:" + library.Version); + return true; + } - private static string GetScriptPath(JavaScriptLibrary js, HttpRequest request) - { - if (Host.CdnEnabled) - { - // load custom CDN path setting - var customCdn = HostController.Instance.GetString("CustomCDN_" + js.LibraryName); - if (!string.IsNullOrEmpty(customCdn)) + return false; + } + + private static void RequestSpecificVersionLibraryRegistration(string jsname, Version version) { - return customCdn; + JavaScriptLibrary library = JavaScriptLibraryController.Instance.GetLibrary(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase) && l.Version == version); + if (library != null) + { + AddItemRequest(library.JavaScriptLibraryID); + } + else + { + // this will only occur if a specific library is requested and not available + LogCollision(string.Format("Missing Library request - {0} : {1}", jsname, version)); + } } - // cdn enabled but jsl does not have one defined - if (!string.IsNullOrEmpty(js.CDNPath)) + private static void AddItemRequest(int javaScriptLibraryId) { - var cdnPath = js.CDNPath; - if (cdnPath.StartsWith("//")) - { - cdnPath = $"{(UrlUtils.IsSecureConnectionOrSslOffload(request) ? "https" : "http")}:{cdnPath}"; - } + HttpContextSource.Current.Items[ScriptPrefix + javaScriptLibraryId] = true; + } - return cdnPath; + private static void AddPreInstallorLegacyItemRequest(string jsl) + { + HttpContextSource.Current.Items[LegacyPrefix + jsl] = true; } - } - return "~/Resources/libraries/" + js.LibraryName + "/" + Globals.FormatVersion(js.Version, "00", 3, "_") + "/" + js.FileName; - } + private static IEnumerable ResolveVersionConflicts(IEnumerable scripts) + { + var finalScripts = new List(); + foreach (string libraryId in scripts) + { + var processingLibrary = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString(CultureInfo.InvariantCulture) == libraryId); - private static string GetScriptLocation(JavaScriptLibrary js) - { - switch (js.PreferredScriptLocation) - { - case ScriptLocation.PageHead: - return "DnnPageHeaderProvider"; - case ScriptLocation.BodyBottom: - return "DnnFormBottomProvider"; - case ScriptLocation.BodyTop: - return "DnnBodyProvider"; - } - - return string.Empty; - } + var existingLatestLibrary = finalScripts.FindAll(lib => lib.LibraryName.Equals(processingLibrary.LibraryName, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .SingleOrDefault(); + if (existingLatestLibrary != null) + { + // determine previous registration for same JSL + if (existingLatestLibrary.Version > processingLibrary.Version) + { + // skip new library & log + var collisionText = string.Format( + CultureInfo.CurrentCulture, + "{0}-{1} -> {2}-{3}", + existingLatestLibrary.LibraryName, + existingLatestLibrary.Version, + processingLibrary.LibraryName, + processingLibrary.Version); + LogCollision(collisionText); + } + else if (existingLatestLibrary.Version != processingLibrary.Version) + { + finalScripts.Remove(existingLatestLibrary); + finalScripts.Add(processingLibrary); + } + } + else + { + finalScripts.Add(processingLibrary); + } + } - private static IEnumerable GetScriptVersions() - { - List orderedScripts = (from object item in HttpContextSource.Current.Items.Keys - where item.ToString().StartsWith(ScriptPrefix) - select item.ToString().Substring(4)).ToList(); - orderedScripts.Sort(); - List finalScripts = orderedScripts.ToList(); - foreach (string libraryId in orderedScripts) - { - // find dependencies - var library = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString() == libraryId); - if (library == null) - { - continue; + return finalScripts; } - foreach (var dependencyLibrary in GetAllDependencies(library).Distinct()) + private static JavaScriptLibrary GetHighestVersionLibrary(string jsname) { - if (HttpContextSource.Current.Items[ScriptPrefix + "." + dependencyLibrary.JavaScriptLibraryID] == null) - { - finalScripts.Add(dependencyLibrary.JavaScriptLibraryID.ToString()); - } + if (Globals.Status == Globals.UpgradeStatus.Install) + { + // if in install process, then do not use JSL but all use the legacy versions. + return null; + } + + try + { + return JavaScriptLibraryController.Instance.GetLibraries(l => l.LibraryName.Equals(jsname, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(l => l.Version) + .FirstOrDefault(); + } + catch (Exception) + { + // no library found (install or upgrade) + return null; + } } - } - return finalScripts; - } + private static string GetScriptPath(JavaScriptLibrary js, HttpRequest request) + { + if (Host.CdnEnabled) + { + // load custom CDN path setting + var customCdn = HostController.Instance.GetString("CustomCDN_" + js.LibraryName); + if (!string.IsNullOrEmpty(customCdn)) + { + return customCdn; + } + + // cdn enabled but jsl does not have one defined + if (!string.IsNullOrEmpty(js.CDNPath)) + { + var cdnPath = js.CDNPath; + if (cdnPath.StartsWith("//")) + { + cdnPath = $"{(UrlUtils.IsSecureConnectionOrSslOffload(request) ? "https" : "http")}:{cdnPath}"; + } + + return cdnPath; + } + } - private static IEnumerable GetAllDependencies(JavaScriptLibrary library) - { - var package = PackageController.Instance.GetExtensionPackage(Null.NullInteger, p => p.PackageID == library.PackageID); - foreach (var dependency in package.Dependencies) - { - var dependencyLibrary = GetHighestVersionLibrary(dependency.PackageName); - yield return dependencyLibrary; + return "~/Resources/libraries/" + js.LibraryName + "/" + Globals.FormatVersion(js.Version, "00", 3, "_") + "/" + js.FileName; + } - foreach (var childDependency in GetAllDependencies(dependencyLibrary)) + private static string GetScriptLocation(JavaScriptLibrary js) { - yield return childDependency; - } - } - } + switch (js.PreferredScriptLocation) + { + case ScriptLocation.PageHead: + return "DnnPageHeaderProvider"; + case ScriptLocation.BodyBottom: + return "DnnFormBottomProvider"; + case ScriptLocation.BodyTop: + return "DnnBodyProvider"; + } - private static void LogCollision(string collisionText) - { - // need to log an event - EventLogController.Instance.AddLog( - "Javascript Libraries", - collisionText, - PortalController.Instance.GetCurrentPortalSettings(), - UserController.Instance.GetCurrentUserInfo().UserID, - EventLogController.EventLogType.SCRIPT_COLLISION); - string strMessage = Localization.GetString("ScriptCollision", Localization.SharedResourceFile); - /* - var page = HttpContextSource.Current.Handler as Page; - if (page != null) - { - Skin.AddPageMessage(page, string.Empty, strMessage, ModuleMessage.ModuleMessageType.YellowWarning); - } - */ - } + return string.Empty; + } - private static void RegisterScript(ControllerContext page, JavaScriptLibrary jsl) - { - if (string.IsNullOrEmpty(jsl.FileName)) - { - return; - } - - MvcClientResourceManager.RegisterScript(page, GetScriptPath(jsl, HttpContext.Current.Request), GetFileOrder(jsl), GetScriptLocation(jsl), jsl.LibraryName, jsl.Version.ToString(3)); - /* - if (Host.CdnEnabled && !string.IsNullOrEmpty(jsl.ObjectName)) - { - string pagePortion; - switch (jsl.PreferredScriptLocation) - { - case ScriptLocation.PageHead: - - pagePortion = "ClientDependencyHeadJs"; - break; - case ScriptLocation.BodyBottom: - pagePortion = "ClientResourcesFormBottom"; - break; - case ScriptLocation.BodyTop: - pagePortion = "BodySCRIPTS"; - break; - default: - pagePortion = "BodySCRIPTS"; - break; - } - - Control scriptloader = page.FindControl(pagePortion); - var fallback = new DnnJsIncludeFallback(jsl.ObjectName, VirtualPathUtility.ToAbsolute("~/Resources/libraries/" + jsl.LibraryName + "/" + Globals.FormatVersion(jsl.Version, "00", 3, "_") + "/" + jsl.FileName)); - if (scriptloader != null) - { - // add the fallback control after script loader. - var index = scriptloader.Parent.Controls.IndexOf(scriptloader); - scriptloader.Parent.Controls.AddAt(index + 1, fallback); - } - } - */ - } + private static IEnumerable GetScriptVersions() + { + List orderedScripts = (from object item in HttpContextSource.Current.Items.Keys + where item.ToString().StartsWith(ScriptPrefix) + select item.ToString().Substring(4)).ToList(); + orderedScripts.Sort(); + List finalScripts = orderedScripts.ToList(); + foreach (string libraryId in orderedScripts) + { + // find dependencies + var library = JavaScriptLibraryController.Instance.GetLibrary(l => l.JavaScriptLibraryID.ToString() == libraryId); + if (library == null) + { + continue; + } + + foreach (var dependencyLibrary in GetAllDependencies(library).Distinct()) + { + if (HttpContextSource.Current.Items[ScriptPrefix + "." + dependencyLibrary.JavaScriptLibraryID] == null) + { + finalScripts.Add(dependencyLibrary.JavaScriptLibraryID.ToString()); + } + } + } - private static int GetFileOrder(JavaScriptLibrary jsl) - { - switch (jsl.LibraryName) - { - case CommonJs.jQuery: - return (int)FileOrder.Js.jQuery; - case CommonJs.jQueryMigrate: - return (int)FileOrder.Js.jQueryMigrate; - case CommonJs.jQueryUI: - return (int)FileOrder.Js.jQueryUI; - case CommonJs.HoverIntent: - return (int)FileOrder.Js.HoverIntent; - default: - return jsl.PackageID + (int)FileOrder.Js.DefaultPriority; - } - } + return finalScripts; + } - private static void HandlePreInstallorLegacyItemRequests(ControllerContext page) - { - List legacyScripts = (from object item in HttpContextSource.Current.Items.Keys - where item.ToString().StartsWith(LegacyPrefix) - select item.ToString().Substring(7)).ToList(); -#pragma warning disable 618 - foreach (string legacyScript in legacyScripts) - { - switch (legacyScript) + private static IEnumerable GetAllDependencies(JavaScriptLibrary library) { - case CommonJs.jQuery: - if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + var package = PackageController.Instance.GetExtensionPackage(Null.NullInteger, p => p.PackageID == library.PackageID); + foreach (var dependency in package.Dependencies) { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryScriptReference(), - FileOrder.Js.jQuery, - "DnnPageHeaderProvider"); + var dependencyLibrary = GetHighestVersionLibrary(dependency.PackageName); + yield return dependencyLibrary; + + foreach (var childDependency in GetAllDependencies(dependencyLibrary)) + { + yield return childDependency; + } } + } + private static void LogCollision(string collisionText) + { + // need to log an event + EventLogController.Instance.AddLog( + "Javascript Libraries", + collisionText, + PortalController.Instance.GetCurrentPortalSettings(), + UserController.Instance.GetCurrentUserInfo().UserID, + EventLogController.EventLogType.SCRIPT_COLLISION); + string strMessage = Localization.GetString("ScriptCollision", Localization.SharedResourceFile); /* - if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + var page = HttpContextSource.Current.Handler as Page; + if (page != null) { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryMigrateScriptReference(), - FileOrder.Js.jQueryMigrate, - "DnnPageHeaderProvider"); + Skin.AddPageMessage(page, string.Empty, strMessage, ModuleMessage.ModuleMessageType.YellowWarning); } */ - break; - case CommonJs.jQueryUI: - // register dependency - if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + } + + private static void RegisterScript(ControllerContext page, JavaScriptLibrary jsl) + { + if (string.IsNullOrEmpty(jsl.FileName)) { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryScriptReference(), - FileOrder.Js.jQuery, - "DnnPageHeaderProvider"); + return; } + MvcClientResourceManager.RegisterScript(page, GetScriptPath(jsl, HttpContext.Current.Request), GetFileOrder(jsl), GetScriptLocation(jsl), jsl.LibraryName, jsl.Version.ToString(3)); /* - if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + if (Host.CdnEnabled && !string.IsNullOrEmpty(jsl.ObjectName)) { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryMigrateScriptReference(), - FileOrder.Js.jQueryMigrate, - "DnnPageHeaderProvider"); + string pagePortion; + switch (jsl.PreferredScriptLocation) + { + case ScriptLocation.PageHead: + + pagePortion = "ClientDependencyHeadJs"; + break; + case ScriptLocation.BodyBottom: + pagePortion = "ClientResourcesFormBottom"; + break; + case ScriptLocation.BodyTop: + pagePortion = "BodySCRIPTS"; + break; + default: + pagePortion = "BodySCRIPTS"; + break; + } + + Control scriptloader = page.FindControl(pagePortion); + var fallback = new DnnJsIncludeFallback(jsl.ObjectName, VirtualPathUtility.ToAbsolute("~/Resources/libraries/" + jsl.LibraryName + "/" + Globals.FormatVersion(jsl.Version, "00", 3, "_") + "/" + jsl.FileName)); + if (scriptloader != null) + { + // add the fallback control after script loader. + var index = scriptloader.Parent.Controls.IndexOf(scriptloader); + scriptloader.Parent.Controls.AddAt(index + 1, fallback); + } } */ + } - // actual jqueryui - if (GetHighestVersionLibrary(CommonJs.jQueryUI) == null) + private static int GetFileOrder(JavaScriptLibrary jsl) + { + switch (jsl.LibraryName) { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryUIScriptReference(), - FileOrder.Js.jQueryUI, - "DnnPageHeaderProvider"); + case CommonJs.jQuery: + return (int)FileOrder.Js.jQuery; + case CommonJs.jQueryMigrate: + return (int)FileOrder.Js.jQueryMigrate; + case CommonJs.jQueryUI: + return (int)FileOrder.Js.jQueryUI; + case CommonJs.HoverIntent: + return (int)FileOrder.Js.HoverIntent; + default: + return jsl.PackageID + (int)FileOrder.Js.DefaultPriority; } - - break; } - } + + private static void HandlePreInstallorLegacyItemRequests(ControllerContext page) + { + List legacyScripts = (from object item in HttpContextSource.Current.Items.Keys + where item.ToString().StartsWith(LegacyPrefix) + select item.ToString().Substring(7)).ToList(); +#pragma warning disable 618 + foreach (string legacyScript in legacyScripts) + { + switch (legacyScript) + { + case CommonJs.jQuery: + if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryScriptReference(), + FileOrder.Js.jQuery, + "DnnPageHeaderProvider"); + } + + /* + if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryMigrateScriptReference(), + FileOrder.Js.jQueryMigrate, + "DnnPageHeaderProvider"); + } + */ + break; + case CommonJs.jQueryUI: + // register dependency + if (GetHighestVersionLibrary(CommonJs.jQuery) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryScriptReference(), + FileOrder.Js.jQuery, + "DnnPageHeaderProvider"); + } + + /* + if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryMigrateScriptReference(), + FileOrder.Js.jQueryMigrate, + "DnnPageHeaderProvider"); + } + */ + + // actual jqueryui + if (GetHighestVersionLibrary(CommonJs.jQueryUI) == null) + { + MvcClientResourceManager.RegisterScript( + page, + jQuery.GetJQueryUIScriptReference(), + FileOrder.Js.jQueryUI, + "DnnPageHeaderProvider"); + } + + break; + } + } #pragma warning restore 618 + } } - } -} \ No newline at end of file +} diff --git a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs index de87540e96d..f19423773d7 100644 --- a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs +++ b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs @@ -1,15 +1,9 @@ // 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 -using System; -using System.Reflection; using System.Runtime.CompilerServices; -using DotNetNuke.Application; - // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. - [assembly: InternalsVisibleTo("DotNetNuke.Web.MvcPipeline")] - From 18dda02c8b37cbdff36ecd72fd3c7099c29e41fe Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 12:03:31 +0100 Subject: [PATCH 003/146] With previous --- .../DotNetNuke.Web.MvcPipeline.csproj | 2 ++ DNN Platform/Library/DotNetNuke.Library.csproj | 1 + 2 files changed, 3 insertions(+) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index 47a0f01a043..147db1f8f8c 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -35,10 +35,12 @@ + + diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index d0e48f079de..84535829c67 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -307,6 +307,7 @@ + From 2b93b837b5b15b126391c5966e597a23d5258878 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 13:55:18 +0100 Subject: [PATCH 004/146] More moving stuff around --- .../Containers/SkinHelpers.Content.cs | 1 + .../ModuleSettingsViewController.cs | 1 + .../DotNetNuke.Web.MvcPipeline.csproj | 2 + .../IMvcServiceFrameworkInternals.cs | 13 ++ .../Framework/IMvcServicesFramework.cs | 15 +++ .../JavascriptLibraries}/MvcJavaScript.cs | 94 ++----------- .../Framework/MvcServicesFramework.cs | 43 ++++++ .../Framework/MvcServicesFrameworkImpl.cs | 124 ++++++++++++++++++ .../Framework/MvcServicesFrameworkInternal.cs | 14 ++ .../Framework/SkinModelFactory.cs | 1 + .../DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs | 5 +- .../PermissionTriStateHelper.cs | 3 +- .../DotNetNuke.Web.MvcPipeline/Startup.cs | 29 ++-- .../Library/DotNetNuke.Library.csproj | 1 - .../IMvcServiceFrameworkInternals.cs | 9 -- .../Framework/MvcServicesFrameworkImpl.cs | 119 ----------------- 16 files changed, 245 insertions(+), 229 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServiceFrameworkInternals.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServicesFramework.cs rename DNN Platform/{Library/Framework/JavaScriptLibraries => DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries}/MvcJavaScript.cs (83%) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFramework.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkInternal.cs delete mode 100644 DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs delete mode 100644 DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs index 76bc5556ae6..d66db32ea5f 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs @@ -14,6 +14,7 @@ namespace DotNetNuke.Web.MvcPipeline.Containers using DotNetNuke.Entities.Portals; using DotNetNuke.Framework.JavaScriptLibraries; using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; using DotNetNuke.Web.MvcPipeline.Models; public static partial class SkinHelpers diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs index ec41f0bb122..95280befffd 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleSettingsViewController.cs @@ -22,6 +22,7 @@ namespace DotNetNuke.Website.Controllers using DotNetNuke.UI.Skins; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; using DotNetNuke.Web.MvcPipeline.Models; public class ModuleSettingsViewController : ModuleControllerBase diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index 147db1f8f8c..abd557c2d5d 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -2,6 +2,8 @@ DotNetNuke.Web.MvcPipeline net48 + true + latest bin false false diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServiceFrameworkInternals.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServiceFrameworkInternals.cs new file mode 100644 index 00000000000..9398c4e3d3e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServiceFrameworkInternals.cs @@ -0,0 +1,13 @@ +namespace DotNetNuke.Web.MvcPipeline.Framework +{ + using System.Web.Mvc; + + internal interface IMvcServiceFrameworkInternals + { + bool IsAjaxAntiForgerySupportRequired { get; } + + bool IsAjaxScriptSupportRequired { get; } + void RegisterAjaxAntiForgery(ControllerContext page); + void RegisterAjaxScript(ControllerContext page); + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServicesFramework.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServicesFramework.cs new file mode 100644 index 00000000000..cd01a95527f --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/IMvcServicesFramework.cs @@ -0,0 +1,15 @@ +namespace DotNetNuke.Web.MvcPipeline.Framework +{ + /// + /// Do not implement. This interface is only implemented by the DotNetNuke core framework. Outside the framework it should used as a type and for unit test purposes only. + /// There is no guarantee that this interface will not change. + /// + public interface IMvcServicesFramework + { + /// Will cause anti forgery tokens to be included in the current page. + void RequestAjaxAntiForgerySupport(); + + /// Will cause ajax scripts to be included in the current page. + void RequestAjaxScriptSupport(); + } +} diff --git a/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs similarity index 83% rename from DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs rename to DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs index 9db347f34b1..ed5a9a159fa 100644 --- a/DNN Platform/Library/Framework/JavaScriptLibraries/MvcJavaScript.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs @@ -1,13 +1,11 @@ -// 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.Framework.JavaScriptLibraries +namespace DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries { using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web; + using System.Web.Mvc; using System.Web.UI; using DotNetNuke.Common; @@ -16,15 +14,15 @@ namespace DotNetNuke.Framework.JavaScriptLibraries using DotNetNuke.Entities.Host; using DotNetNuke.Entities.Portals; using DotNetNuke.Entities.Users; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Framework; + using DotNetNuke.Mvc; using DotNetNuke.Services.Installer.Packages; using DotNetNuke.Services.Localization; using DotNetNuke.Services.Log.EventLog; using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client; - using DotNetNuke.Web.MvcPipeline.Integration.Mvc; - using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; - - using Globals = DotNetNuke.Common.Globals; + using DotNetNuke.Web.Client.ClientResourceManagement; public class MvcJavaScript { @@ -52,7 +50,7 @@ public static bool IsInstalled(string jsname) /// whether to use the debug script. public static bool UseDebugScript() { - if (Globals.Status != Globals.UpgradeStatus.None) + if (DotNetNuke.Common.Globals.Status != DotNetNuke.Common.Globals.UpgradeStatus.None) { return false; } @@ -130,7 +128,6 @@ public static void RequestRegistration(string jsname, Version version, SpecificV /// reference to the current page. public static void Register(ControllerContext page) { - HandlePreInstallorLegacyItemRequests(page); IEnumerable scripts = GetScriptVersions(); IEnumerable finalScripts = ResolveVersionConflicts(scripts); foreach (JavaScriptLibrary jsl in finalScripts) @@ -304,7 +301,7 @@ private static IEnumerable ResolveVersionConflicts(IEnumerabl private static JavaScriptLibrary GetHighestVersionLibrary(string jsname) { - if (Globals.Status == Globals.UpgradeStatus.Install) + if (DotNetNuke.Common.Globals.Status == DotNetNuke.Common.Globals.UpgradeStatus.Install) { // if in install process, then do not use JSL but all use the legacy versions. return null; @@ -323,7 +320,7 @@ private static JavaScriptLibrary GetHighestVersionLibrary(string jsname) } } - private static string GetScriptPath(JavaScriptLibrary js, HttpRequest request) + private static string GetScriptPath(JavaScriptLibrary js, HttpRequestBase request) { if (Host.CdnEnabled) { @@ -347,7 +344,7 @@ private static string GetScriptPath(JavaScriptLibrary js, HttpRequest request) } } - return "~/Resources/libraries/" + js.LibraryName + "/" + Globals.FormatVersion(js.Version, "00", 3, "_") + "/" + js.FileName; + return "~/Resources/libraries/" + js.LibraryName + "/" + DotNetNuke.Common.Globals.FormatVersion(js.Version, "00", 3, "_") + "/" + js.FileName; } private static string GetScriptLocation(JavaScriptLibrary js) @@ -434,7 +431,7 @@ private static void RegisterScript(ControllerContext page, JavaScriptLibrary jsl return; } - MvcClientResourceManager.RegisterScript(page, GetScriptPath(jsl, HttpContext.Current.Request), GetFileOrder(jsl), GetScriptLocation(jsl), jsl.LibraryName, jsl.Version.ToString(3)); + MvcClientResourceManager.RegisterScript(page, GetScriptPath(jsl, HttpContextSource.Current?.Request), GetFileOrder(jsl), GetScriptLocation(jsl), jsl.LibraryName, jsl.Version.ToString(3)); /* if (Host.CdnEnabled && !string.IsNullOrEmpty(jsl.ObjectName)) { @@ -484,74 +481,5 @@ private static int GetFileOrder(JavaScriptLibrary jsl) return jsl.PackageID + (int)FileOrder.Js.DefaultPriority; } } - - private static void HandlePreInstallorLegacyItemRequests(ControllerContext page) - { - List legacyScripts = (from object item in HttpContextSource.Current.Items.Keys - where item.ToString().StartsWith(LegacyPrefix) - select item.ToString().Substring(7)).ToList(); -#pragma warning disable 618 - foreach (string legacyScript in legacyScripts) - { - switch (legacyScript) - { - case CommonJs.jQuery: - if (GetHighestVersionLibrary(CommonJs.jQuery) == null) - { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryScriptReference(), - FileOrder.Js.jQuery, - "DnnPageHeaderProvider"); - } - - /* - if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) - { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryMigrateScriptReference(), - FileOrder.Js.jQueryMigrate, - "DnnPageHeaderProvider"); - } - */ - break; - case CommonJs.jQueryUI: - // register dependency - if (GetHighestVersionLibrary(CommonJs.jQuery) == null) - { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryScriptReference(), - FileOrder.Js.jQuery, - "DnnPageHeaderProvider"); - } - - /* - if (GetHighestVersionLibrary(CommonJs.jQueryMigrate) == null) - { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryMigrateScriptReference(), - FileOrder.Js.jQueryMigrate, - "DnnPageHeaderProvider"); - } - */ - - // actual jqueryui - if (GetHighestVersionLibrary(CommonJs.jQueryUI) == null) - { - MvcClientResourceManager.RegisterScript( - page, - jQuery.GetJQueryUIScriptReference(), - FileOrder.Js.jQueryUI, - "DnnPageHeaderProvider"); - } - - break; - } - } -#pragma warning restore 618 - } } } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFramework.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFramework.cs new file mode 100644 index 00000000000..500f390e19b --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFramework.cs @@ -0,0 +1,43 @@ +namespace DotNetNuke.Web.MvcPipeline.Framework +{ + using System; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework; + + /// Enables modules to support Services Framework features. + public class MvcServicesFramework : ServiceLocator + { + public static string GetServiceFrameworkRoot() + { + var portalSettings = PortalSettings.Current; + if (portalSettings == null) + { + return string.Empty; + } + + var path = portalSettings.PortalAlias.HTTPAlias; + var index = path.IndexOf('/'); + if (index > 0) + { + path = path.Substring(index); + if (!path.EndsWith("/")) + { + path += "/"; + } + } + else + { + path = "/"; + } + + return path; + } + + /// + protected override Func GetFactory() + { + return () => new MvcServicesFrameworkImpl(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs new file mode 100644 index 00000000000..087ec228eeb --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs @@ -0,0 +1,124 @@ +namespace DotNetNuke.Web.MvcPipeline.Framework +{ + using System.Globalization; + using System.Web.Helpers; + using System.Web.Mvc; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Mvc; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + + internal class MvcServicesFrameworkImpl : IMvcServicesFramework, IMvcServiceFrameworkInternals + { + private const string AntiForgeryKey = "dnnAntiForgeryRequested"; + private const string ScriptKey = "dnnSFAjaxScriptRequested"; + + /// + public bool IsAjaxAntiForgerySupportRequired + { + get { return CheckKey(AntiForgeryKey); } + } + + /// + public bool IsAjaxScriptSupportRequired + { + get { return CheckKey(ScriptKey); } + } + + /// + public void RequestAjaxAntiForgerySupport() + { + this.RequestAjaxScriptSupport(); + SetKey(AntiForgeryKey); + } + + /// + public void RegisterAjaxAntiForgery(Page page) + { + var ctl = page.FindControl("ClientResourcesFormBottom"); + if (ctl != null) + { + ctl.Controls.Add(new LiteralControl(AntiForgery.GetHtml().ToHtmlString())); + } + } + + /// + public void RequestAjaxScriptSupport() + { + JavaScript.RequestRegistration(CommonJs.jQuery); + SetKey(ScriptKey); + } + + /// + public void RegisterAjaxScript(Page page) + { + var path = ServicesFramework.GetServiceFrameworkRoot(); + if (string.IsNullOrEmpty(path)) + { + return; + } + + JavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + ClientAPI.RegisterClientVariable(page, "sf_siteRoot", path, /*overwrite*/ true); + ClientAPI.RegisterClientVariable(page, "sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); + + string scriptPath; + if (HttpContextSource.Current.IsDebuggingEnabled) + { + scriptPath = "~/js/Debug/dnn.servicesframework.js"; + } + else + { + scriptPath = "~/js/dnn.servicesframework.js"; + } + + ClientResourceManager.RegisterScript(page, scriptPath); + } + + public void RegisterAjaxScript(ControllerContext page) + { + var path = ServicesFramework.GetServiceFrameworkRoot(); + if (string.IsNullOrEmpty(path)) + { + return; + } + + MvcJavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); + MvcClientAPI.RegisterClientVariable("sf_siteRoot", path, /*overwrite*/ true); + MvcClientAPI.RegisterClientVariable("sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); + + string scriptPath; + if (HttpContextSource.Current.IsDebuggingEnabled) + { + scriptPath = "~/js/Debug/dnn.servicesframework.js"; + } + else + { + scriptPath = "~/js/dnn.servicesframework.js"; + } + + MvcClientResourceManager.RegisterScript(page, scriptPath); + } + + private static void SetKey(string key) + { + HttpContextSource.Current.Items[key] = true; + } + + private static bool CheckKey(string antiForgeryKey) + { + return HttpContextSource.Current.Items.Contains(antiForgeryKey); + } + + public void RegisterAjaxAntiForgery(ControllerContext page) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkInternal.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkInternal.cs new file mode 100644 index 00000000000..e3f28756e6c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkInternal.cs @@ -0,0 +1,14 @@ +namespace DotNetNuke.Web.MvcPipeline.Framework +{ + using System; + using DotNetNuke.Framework; + + internal class MvcServicesFrameworkInternal : ServiceLocator + { + /// + protected override Func GetFactory() + { + return () => new MvcServicesFrameworkImpl(); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs index e08607bb535..278bd48b961 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs @@ -34,6 +34,7 @@ namespace DotNetNuke.Web.MvcPipeline.Framework using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Controllers; using DotNetNuke.Web.MvcPipeline.Exceptions; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; using DotNetNuke.Web.MvcPipeline.Models; using Microsoft.Extensions.DependencyInjection; diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs index 6180b98f980..2febc2fa534 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs @@ -21,6 +21,7 @@ namespace DotNetNuke.Web.MvcPipeline using DotNetNuke.Mvc; using DotNetNuke.UI.Modules; using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework; using DotNetNuke.Web.MvcPipeline.Models; public static partial class HtmlHelpers @@ -100,9 +101,9 @@ public static IHtmlString CspNonce(this HtmlHelper htmlHelper) public static IHtmlString RegisterAjaxScriptIfRequired(this HtmlHelper htmlHelper) { - if (ServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) + if (MvcServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) { - ServicesFrameworkInternal.Instance.RegisterAjaxScript(htmlHelper.ViewContext.Controller.ControllerContext); + MvcServicesFrameworkInternal.Instance.RegisterAjaxScript(htmlHelper.ViewContext.Controller.ControllerContext); } return new MvcHtmlString(string.Empty); diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs index 6e7aca1450f..b208139056b 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs @@ -16,8 +16,9 @@ namespace DotNetNuke.MvcPipeline using DotNetNuke.Services.Localization; using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; - public static class PermissionTriStateHelper + public static class PermissionTriStateHelper { public static MvcHtmlString PermissionTriState( this HtmlHelper helper, diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs index b282091a9e2..a1665141a14 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs @@ -4,26 +4,27 @@ namespace DotNetNuke.Web.MvcPipeline { - using DotNetNuke.Common; - using DotNetNuke.ContentSecurityPolicy; - using DotNetNuke.DependencyInjection; - using DotNetNuke.Web.Mvc.Extensions; - using Microsoft.Extensions.DependencyInjection; - using System.Web.Mvc; + using DotNetNuke.Common; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.DependencyInjection; + using DotNetNuke.Web.Mvc.Extensions; + using DotNetNuke.Web.MvcPipeline.Framework; + using Microsoft.Extensions.DependencyInjection; + using System.Web.Mvc; - public class Startup : IDnnStartup - { - /// - public void ConfigureServices(IServiceCollection services) + public class Startup : IDnnStartup { - services.AddMvcControllers(); + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddMvcControllers(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - DependencyResolver.SetResolver(new DnnMvcPipelineDependencyResolver(Globals.DependencyProvider)); - services.AddScoped(); + DependencyResolver.SetResolver(new DnnMvcPipelineDependencyResolver(Globals.DependencyProvider)); + services.AddScoped(); + } } - } } diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 84535829c67..d0e48f079de 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -307,7 +307,6 @@ - diff --git a/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs b/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs deleted file mode 100644 index e702a520d42..00000000000 --- a/DNN Platform/Library/Framework/IMvcServiceFrameworkInternals.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Web.Mvc; - -namespace DotNetNuke.Web.MvcPipeline.Integration.Framework -{ - internal interface IMvcServiceFrameworkInternals - { - void RegisterAjaxScript(ControllerContext page); - } -} diff --git a/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs b/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs deleted file mode 100644 index 0a83c82dbae..00000000000 --- a/DNN Platform/Library/Framework/MvcServicesFrameworkImpl.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace DotNetNuke.Web.MvcPipeline.Integration.Framework -{ - using System.Globalization; - using System.Web.Helpers; - using System.Web.Mvc; - using System.Web.UI; - - using DotNetNuke.Common; - using DotNetNuke.Entities.Portals; - using DotNetNuke.Framework; - using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.UI.Utilities; - using DotNetNuke.Web.Client.ClientResourceManagement; - using DotNetNuke.Web.MvcPipeline.Integration.Mvc; - using DotNetNuke.Web.MvcPipeline.Integration.Web.Client.ClientResourceManagement; - - internal class MvcServicesFrameworkImpl : IServicesFramework, IMvcServiceFrameworkInternals - { - private const string AntiForgeryKey = "dnnAntiForgeryRequested"; - private const string ScriptKey = "dnnSFAjaxScriptRequested"; - - /// - public bool IsAjaxAntiForgerySupportRequired - { - get { return CheckKey(AntiForgeryKey); } - } - - /// - public bool IsAjaxScriptSupportRequired - { - get { return CheckKey(ScriptKey); } - } - - /// - public void RequestAjaxAntiForgerySupport() - { - this.RequestAjaxScriptSupport(); - SetKey(AntiForgeryKey); - } - - /// - public void RegisterAjaxAntiForgery(Page page) - { - var ctl = page.FindControl("ClientResourcesFormBottom"); - if (ctl != null) - { - ctl.Controls.Add(new LiteralControl(AntiForgery.GetHtml().ToHtmlString())); - } - } - - /// - public void RequestAjaxScriptSupport() - { - JavaScript.RequestRegistration(CommonJs.jQuery); - SetKey(ScriptKey); - } - - /// - public void RegisterAjaxScript(Page page) - { - var path = ServicesFramework.GetServiceFrameworkRoot(); - if (string.IsNullOrEmpty(path)) - { - return; - } - - JavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); - ClientAPI.RegisterClientVariable(page, "sf_siteRoot", path, /*overwrite*/ true); - ClientAPI.RegisterClientVariable(page, "sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); - - string scriptPath; - if (HttpContextSource.Current.IsDebuggingEnabled) - { - scriptPath = "~/js/Debug/dnn.servicesframework.js"; - } - else - { - scriptPath = "~/js/dnn.servicesframework.js"; - } - - ClientResourceManager.RegisterScript(page, scriptPath); - } - - public void RegisterAjaxScript(ControllerContext page) - { - var path = ServicesFramework.GetServiceFrameworkRoot(); - if (string.IsNullOrEmpty(path)) - { - return; - } - - MvcJavaScript.RegisterClientReference(page, ClientAPI.ClientNamespaceReferences.dnn); - MvcClientAPI.RegisterClientVariable("sf_siteRoot", path, /*overwrite*/ true); - MvcClientAPI.RegisterClientVariable("sf_tabId", PortalSettings.Current.ActiveTab.TabID.ToString(CultureInfo.InvariantCulture), /*overwrite*/ true); - - string scriptPath; - if (HttpContextSource.Current.IsDebuggingEnabled) - { - scriptPath = "~/js/Debug/dnn.servicesframework.js"; - } - else - { - scriptPath = "~/js/dnn.servicesframework.js"; - } - - MvcClientResourceManager.RegisterScript(page, scriptPath); - } - - private static void SetKey(string key) - { - HttpContextSource.Current.Items[key] = true; - } - - private static bool CheckKey(string antiForgeryKey) - { - return HttpContextSource.Current.Items.Contains(antiForgeryKey); - } - } -} \ No newline at end of file From e66429e72d0dc3208f04746cd1ddc20bcad297dd Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 14:42:36 +0100 Subject: [PATCH 005/146] Moving more classes to Mvc Pipeline project --- .../Controllers/ModuleControllerBase.cs | 512 ++- .../Controllers/ModuleViewControllerBase.cs | 15 +- .../DotNetNuke.Web.MvcPipeline.csproj | 1 + .../Entities/Urls/MvcAdvancedUrlRewriter.cs | 3220 ++++++++++++++++ .../JavascriptLibraries/MvcJavaScript.cs | 4 +- .../Framework/MvcServicesFrameworkImpl.cs | 2 +- .../Framework/PageModelFactory.cs | 2 +- .../Framework/SkinModelFactory.cs | 5 +- .../DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs | 8 +- .../Modules/ModuleHelpers.DnnLabel.cs | 15 +- .../Modules/ModuleHelpers.TextEditor.cs | 10 - .../PermissionTriStateHelper.cs | 7 +- .../ModulePermissionsGridController.cs | 5 +- .../UI/Utilities/MvcClientAPI.cs | 59 + .../UI/Utilities}/MvcUtils.cs | 6 +- .../Library/DotNetNuke.Library.csproj | 3 - .../Entities/Urls/MvcAdvancedUrlRewriter.cs | 3224 ----------------- DNN Platform/Library/Mvc/MvcClientAPI.cs | 63 - 18 files changed, 3541 insertions(+), 3620 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcClientAPI.cs rename DNN Platform/{Library/Mvc => DotNetNuke.Web.MvcPipeline/UI/Utilities}/MvcUtils.cs (83%) delete mode 100644 DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs delete mode 100644 DNN Platform/Library/Mvc/MvcClientAPI.cs diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs index 3e46b6676ac..8e0c64a1f1b 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleControllerBase.cs @@ -5,374 +5,362 @@ namespace DotNetNuke.Web.MvcPipeline.Controllers { using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Web; using System.Web.Mvc; - using DotNetNuke.Common; - using DotNetNuke.Entities.Modules; - using DotNetNuke.Entities.Modules.Actions; - using DotNetNuke.Entities.Portals; - using DotNetNuke.Entities.Users; - using DotNetNuke.Instrumentation; - using DotNetNuke.Mvc; - using DotNetNuke.Services.Localization; - using DotNetNuke.UI.Modules; - - using DotNetNuke.Web.MvcPipeline.Routing; - - public class ModuleControllerBase : Controller, IMvcController - { - private readonly Lazy activeModule; - /* - private readonly ILog tracelLogger = LoggerSource.Instance.GetLogger("DNN.Trace"); - private readonly Lazy serviceScopeContainer = new Lazy(ServiceScopeContainer.GetRequestOrCreateScope); - private string localResourceFile; - private ModuleInstanceContext moduleContext; - private DesktopModuleInfo desktopModule; - */ - - public ModuleControllerBase() - { - this.activeModule = new Lazy(this.InitModuleInfo); - } - - public PortalSettings PortalSettings - { - get - { - return PortalController.Instance.GetCurrentPortalSettings(); - } - } + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Web.MvcPipeline.Routing; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; - /// Gets userInfo for the current user. - public UserInfo UserInfo + public class ModuleControllerBase : Controller, IMvcController { - get { return this.PortalSettings.UserInfo; } - } + private readonly Lazy activeModule; + /* + private readonly ILog tracelLogger = LoggerSource.Instance.GetLogger("DNN.Trace"); + private readonly Lazy serviceScopeContainer = new Lazy(ServiceScopeContainer.GetRequestOrCreateScope); + private string localResourceFile; + private ModuleInstanceContext moduleContext; + private DesktopModuleInfo desktopModule; + */ - public ModuleInfo ActiveModule - { - get { return this.activeModule.Value; } - } - - protected ActionResult View(ModuleInfo module, object model) - { - return this.View(MvcUtils.GetControlViewName(module), model); - } - - protected ActionResult PartialView(ModuleInfo module, string viewName, object model) - { - return this.View(MvcUtils.GetControlViewName(module, viewName), model); - } - - private ModuleInfo InitModuleInfo() - { - return this.HttpContext.Request.FindModuleInfo(); - } - - /* - public bool IsHostMenu - { - get + public ModuleControllerBase() { - return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + this.activeModule = new Lazy(this.InitModuleInfo); } - } - public PortalSettings PortalSettings - { - get + public PortalSettings PortalSettings { - return PortalController.Instance.GetCurrentPortalSettings(); + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } } - } - /// - /// Gets a value indicating whether the EditMode property is used to determine whether the user is in the - /// Administrator role - /// Cache. - /// - public bool EditMode - { - get + /// Gets userInfo for the current user. + public UserInfo UserInfo { - return this.ModuleContext.EditMode; + get { return this.PortalSettings.UserInfo; } } - } - public bool IsEditable - { - get + public ModuleInfo ActiveModule { - return this.ModuleContext.IsEditable; + get { return this.activeModule.Value; } } - } - public int PortalId - { - get + protected ActionResult View(ModuleInfo module, object model) { - return this.ModuleContext.PortalId; + return this.View(MvcUtils.GetControlViewName(module), model); } - } - public int TabId - { - get + protected ActionResult PartialView(ModuleInfo module, string viewName, object model) { - return this.ModuleContext.TabId; + return this.View(MvcUtils.GetControlViewName(module, viewName), model); } - } - public UserInfo UserInfo - { - get + private ModuleInfo InitModuleInfo() { - return this.PortalSettings.UserInfo; + return this.HttpContext.Request.FindModuleInfo(); } - } - public int UserId - { - get + /* + public bool IsHostMenu { - return this.PortalSettings.UserId; + get + { + return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + } } - } - public PortalAliasInfo PortalAlias - { - get + public PortalSettings PortalSettings { - return this.PortalSettings.PortalAlias; + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } } - } - public Hashtable Settings - { - get + /// + /// Gets a value indicating whether the EditMode property is used to determine whether the user is in the + /// Administrator role + /// Cache. + /// + public bool EditMode { - return this.ModuleContext.Settings; + get + { + return this.ModuleContext.EditMode; + } } - } - /// Gets the Path for this control (used primarily for UserControls). - /// A String. - public string ControlPath - { - get + public bool IsEditable { - return "/" + Path.GetDirectoryName(this.ModuleConfiguration.ModuleControl.ControlSrc); + get + { + return this.ModuleContext.IsEditable; + } } - } - /// Gets the Name for this control. - /// A String. - public string ControlName - { - get + public int PortalId { - return this.GetType().Name.Replace("_", "."); + get + { + return this.ModuleContext.PortalId; + } } - } - /// Gets the Module Context for this control. - /// A ModuleInstanceContext. - public ModuleInstanceContext ModuleContext - { - get + public int TabId { - if (this.moduleContext == null) + get { - this.moduleContext = new ModuleInstanceContext() - { - Configuration = this.ActiveModule, - }; + return this.ModuleContext.TabId; } - - return this.moduleContext; } - } - public DesktopModuleInfo DesktopModule - { - get + public UserInfo UserInfo { - if (this.desktopModule == null) + get { - this.desktopModule = DesktopModuleControllerAdapter.Instance.GetDesktopModule(this.ActiveModule.DesktopModuleID, this.ActiveModule.PortalID); + return this.PortalSettings.UserInfo; } - - return this.desktopModule; } - } - public ModuleActionCollection Actions - { - get + public int UserId { - return this.ModuleContext.Actions; + get + { + return this.PortalSettings.UserId; + } } - set + public PortalAliasInfo PortalAlias { - this.ModuleContext.Actions = value; + get + { + return this.PortalSettings.PortalAlias; + } } - } - public string HelpURL - { - get + public Hashtable Settings { - return this.ModuleContext.HelpURL; + get + { + return this.ModuleContext.Settings; + } } - set + /// Gets the Path for this control (used primarily for UserControls). + /// A String. + public string ControlPath { - this.ModuleContext.HelpURL = value; + get + { + return "/" + Path.GetDirectoryName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } } - } - public ModuleInfo ModuleConfiguration - { - get + /// Gets the Name for this control. + /// A String. + public string ControlName { - return this.ModuleContext.Configuration; + get + { + return this.GetType().Name.Replace("_", "."); + } } - set + /// Gets the Module Context for this control. + /// A ModuleInstanceContext. + public ModuleInstanceContext ModuleContext { - this.ModuleContext.Configuration = value; + get + { + if (this.moduleContext == null) + { + this.moduleContext = new ModuleInstanceContext() + { + Configuration = this.ActiveModule, + }; + } + + return this.moduleContext; + } } - } - public int TabModuleId - { - get + public DesktopModuleInfo DesktopModule { - return this.ModuleContext.TabModuleId; + get + { + if (this.desktopModule == null) + { + this.desktopModule = DesktopModuleControllerAdapter.Instance.GetDesktopModule(this.ActiveModule.DesktopModuleID, this.ActiveModule.PortalID); + } + + return this.desktopModule; + } } - set + public ModuleActionCollection Actions { - this.ModuleContext.TabModuleId = value; + get + { + return this.ModuleContext.Actions; + } + + set + { + this.ModuleContext.Actions = value; + } } - } - public int ModuleId - { - get + public string HelpURL { - return this.ModuleContext.ModuleId; + get + { + return this.ModuleContext.HelpURL; + } + + set + { + this.ModuleContext.HelpURL = value; + } } - set + public ModuleInfo ModuleConfiguration { - this.ModuleContext.ModuleId = value; + get + { + return this.ModuleContext.Configuration; + } + + set + { + this.ModuleContext.Configuration = value; + } } - } - public string ID - { - get + public int TabModuleId { - return Path.GetFileName(this.ModuleConfiguration.ModuleControl.ControlSrc); + get + { + return this.ModuleContext.TabModuleId; + } + + set + { + this.ModuleContext.TabModuleId = value; + } } - } - /// Gets or sets the local resource file for this control. - /// A String. - public string LocalResourceFile - { - get + public int ModuleId { - string fileRoot; - if (string.IsNullOrEmpty(this.localResourceFile)) + get { - fileRoot = Path.Combine(this.ControlPath, Localization.LocalResourceDirectory + "/" + this.ID); + return this.ModuleContext.ModuleId; } - else + + set { - fileRoot = this.localResourceFile; + this.ModuleContext.ModuleId = value; } + } - return fileRoot; + public string ID + { + get + { + return Path.GetFileName(this.ModuleConfiguration.ModuleControl.ControlSrc); + } } - set + /// Gets or sets the local resource file for this control. + /// A String. + public string LocalResourceFile { - this.localResourceFile = value; + get + { + string fileRoot; + if (string.IsNullOrEmpty(this.localResourceFile)) + { + fileRoot = Path.Combine(this.ControlPath, Localization.LocalResourceDirectory + "/" + this.ID); + } + else + { + fileRoot = this.localResourceFile; + } + + return fileRoot; + } + + set + { + this.localResourceFile = value; + } } - } - /// - /// Gets the Dependency Provider to resolve registered - /// services with the container. - /// - /// - /// The Dependency Service. - /// - protected IServiceProvider DependencyProvider => this.serviceScopeContainer.Value.ServiceScope.ServiceProvider; + /// + /// Gets the Dependency Provider to resolve registered + /// services with the container. + /// + /// + /// The Dependency Service. + /// + protected IServiceProvider DependencyProvider => this.serviceScopeContainer.Value.ServiceScope.ServiceProvider; - public string EditUrl() - { - return this.ModuleContext.EditUrl(); - } + public string EditUrl() + { + return this.ModuleContext.EditUrl(); + } - public string EditUrl(string controlKey) - { - return this.ModuleContext.EditUrl(controlKey); - } + public string EditUrl(string controlKey) + { + return this.ModuleContext.EditUrl(controlKey); + } - public string EditUrl(string keyName, string keyValue) - { - return this.ModuleContext.EditUrl(keyName, keyValue); - } + public string EditUrl(string keyName, string keyValue) + { + return this.ModuleContext.EditUrl(keyName, keyValue); + } - public string EditUrl(string keyName, string keyValue, string controlKey) - { - return this.ModuleContext.EditUrl(keyName, keyValue, controlKey); - } + public string EditUrl(string keyName, string keyValue, string controlKey) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey); + } - public string EditUrl(string keyName, string keyValue, string controlKey, params string[] additionalParameters) - { - return this.ModuleContext.EditUrl(keyName, keyValue, controlKey, additionalParameters); - } + public string EditUrl(string keyName, string keyValue, string controlKey, params string[] additionalParameters) + { + return this.ModuleContext.EditUrl(keyName, keyValue, controlKey, additionalParameters); + } - public string EditUrl(int tabID, string controlKey, bool pageRedirect, params string[] additionalParameters) - { - return this.ModuleContext.NavigateUrl(tabID, controlKey, pageRedirect, additionalParameters); - } + public string EditUrl(int tabID, string controlKey, bool pageRedirect, params string[] additionalParameters) + { + return this.ModuleContext.NavigateUrl(tabID, controlKey, pageRedirect, additionalParameters); + } - public int GetNextActionID() - { - return this.ModuleContext.GetNextActionID(); - } + public int GetNextActionID() + { + return this.ModuleContext.GetNextActionID(); + } - /// - public override void Dispose() - { - base.Dispose(); - if (this.serviceScopeContainer.IsValueCreated) + /// + public override void Dispose() { - this.serviceScopeContainer.Value.Dispose(); + base.Dispose(); + if (this.serviceScopeContainer.IsValueCreated) + { + this.serviceScopeContainer.Value.Dispose(); + } } - } - protected string LocalizeString(string key) - { - return Localization.GetString(key, this.LocalResourceFile); - } + protected string LocalizeString(string key) + { + return Localization.GetString(key, this.LocalResourceFile); + } - protected string LocalizeSafeJsString(string key) - { - return Localization.GetSafeJSString(key, this.LocalResourceFile); + protected string LocalizeSafeJsString(string key) + { + return Localization.GetSafeJSString(key, this.LocalResourceFile); + } + */ } - */ - } } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs index 1b3989f5f29..95aa6075430 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/ModuleViewControllerBase.cs @@ -4,26 +4,13 @@ namespace DotNetNuke.Web.MvcPipeline.Controllers { - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.IO; - using System.Linq; - using System.Web; using System.Web.Mvc; - using DotNetNuke.Common; using DotNetNuke.Entities.Modules; - using DotNetNuke.Entities.Modules.Actions; using DotNetNuke.Entities.Portals; using DotNetNuke.Entities.Users; - using DotNetNuke.Instrumentation; - using DotNetNuke.Mvc; - using DotNetNuke.Services.Localization; - using DotNetNuke.UI.Modules; using DotNetNuke.Web.MvcPipeline.Models; - using DotNetNuke.Web.MvcPipeline.Routing; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; public abstract class ModuleViewControllerBase : Controller, IMvcController { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index abd557c2d5d..cd8239fb087 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -43,6 +43,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs new file mode 100644 index 00000000000..24cfbc4cdce --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs @@ -0,0 +1,3220 @@ +namespace DotNetNuke.Web.MvcPipeline.Entities.Urls +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Principal; + using System.Text.RegularExpressions; + using System.Threading; + using System.Web; + using System.Web.Configuration; + using System.Web.Security; + + using DotNetNuke.Application; + using DotNetNuke.Common; + using DotNetNuke.Common.Internal; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Controllers; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Entities.Urls; + using DotNetNuke.Framework; + using DotNetNuke.Services.EventQueue; + + public class MvcAdvancedUrlRewriter : UrlRewriterBase + { + private const string ProductName = "AdvancedUrlRewriter"; + private static readonly Regex DefaultPageRegex = new Regex(@"(?.[^&]+)=$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); + private static readonly Regex UrlSlashesRegex = new Regex("[\\\\/]\\.\\.[\\\\/]", RegexOptions.Compiled); + private static readonly Regex AliasUrlRegex = new Regex(@"(?:^(?http[s]{0,1}://){0,1})(?:(?_ALIAS_)(?$|\?[\w]*|/[\w]*))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); + private FriendlyUrlSettings settings; + + public void ProcessTestRequestWithContext( + HttpContext context, + Uri requestUri, + bool useFriendlyUrls, + UrlAction result, + FriendlyUrlSettings settings) + { + Guid parentTraceId = Guid.Empty; + this.settings = settings; + this.ProcessRequest( + context, + requestUri, + useFriendlyUrls, + result, + settings, + false, + parentTraceId); + } + + internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) + { + bool mvcCtl = false; + var skinSrc = string.Empty; + + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; + if (string.IsNullOrEmpty(skinSrc)) + { + skinSrc = PortalSettings.Current.DefaultPortalSkin; + } + } + } + + if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) + { + var tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + skinSrc = tab.SkinSrc; + } + } + + if (!string.IsNullOrEmpty(skinSrc)) + { + mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); + } + + /* + var mvcCtls = new[] { "Module", "Terms", "Privacy" }; + bool mvcSkin = false; + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + mvcSkin = !string.IsNullOrEmpty(PortalSettings.Current.ActiveTab.SkinSrc) && + PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); + } + } + + if (result.RewritePath.Contains("&ctl=")) + { + foreach (var item in mvcCtls) + { + mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); + } + + if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); + } + } + else + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = result.RawUrl.EndsWith("mvc"); + } + + mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; + mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; + */ + + return mvcCtl; + } + + internal static void RewriteAsChildAliasRoot( + HttpContext context, + UrlAction result, + string aliasQueryString, + FriendlyUrlSettings settings) + { + string culture = null; + + // look for specific alias to rewrite language parameter + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (result.PortalId > -1 && result.HttpAlias != null) + { + culture = primaryAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); + } + + if (string.IsNullOrEmpty(culture)) + { + // 732 : when no culture returned can be "" as well as null : no culture causes no rewrite, which results in redirect to parent alias + // set the default culture code here + // 735 : switch to custom method for getting portal + PortalInfo pi = CacheController.GetPortal(result.PortalId, false); + if (pi != null) + { + culture = pi.DefaultLanguage; + } + } + + if (!string.IsNullOrEmpty(culture)) + { + // a culture was identified for the alias root + if (RewriteController.AddLanguageCodeToRewritePath(ref aliasQueryString, culture)) + { + result.CultureCode = culture; + } + + result.DoRewrite = true; + result.RewritePath = "~/" + Globals.glbDefaultPage + aliasQueryString; + + // the expected /default.aspx path (defaultPageUrl) matches the requested Url (/default.aspx) + if (context != null) + { + // only do if not testing + RewriterUtils.RewriteUrl(context, result.RewritePath); + } + } + } + + internal static bool CheckForChildPortalRootUrl(string requestUrl, UrlAction result, out string aliasQueryString) + { + bool isChildPortalRootUrl = false; + + // what we are going to test for here is that if this is a child portal request, for the /default.aspx of the child portal + // then we are going to avoid the core 302 redirect to ?alias=portalALias by rewriting to the /default.aspx of the site root + // 684 : don't convert querystring items to lower case + // do the check by constructing what a child alias url would look like and compare it with the requested urls + // 912 : when requested without a valid portal alias, portalALias is null. Refuse and return false. + aliasQueryString = null; + if (result.PortalAlias != null && result.PortalAlias.HTTPAlias != null) + { + string defaultPageUrl = result.Scheme + result.PortalAlias.HTTPAlias + "/" + + Globals.glbDefaultPage.ToLowerInvariant(); // child alias Url with /default.aspx + + // 660 : look for a querystring on the site root for a child portal, and handle it if so + if (string.CompareOrdinal(requestUrl.ToLowerInvariant(), defaultPageUrl) == 0) + { + // exact match : that's the alias root + isChildPortalRootUrl = true; + aliasQueryString = string.Empty; + } + + if (!isChildPortalRootUrl && requestUrl.Contains("?")) + { + // is we didn't get an exact match but there is a querystring, then investigate + string[] requestUrlParts = requestUrl.Split('?'); + if (requestUrlParts.GetUpperBound(0) > 0) + { + string rootPart = requestUrlParts[0]; + string queryString = requestUrlParts[1]; + if (string.Compare(rootPart, defaultPageUrl, StringComparison.OrdinalIgnoreCase) == 0) + { + // rewrite, but put in the querystring on the rewrite path + isChildPortalRootUrl = true; + aliasQueryString = "?" + queryString; + + // 674: check for 301 if this value is a tabid/xx - otherwise the url will just evaluate as is + if (queryString.ToLowerInvariant().StartsWith("tabid=")) + { + result.Action = ActionType.CheckFor301; + } + } + } + } + } + + return isChildPortalRootUrl; + } + + /// Make sure any redirect to the site root doesn't append the nasty /default.aspx on the end. + /// + /// + /// without at the end. + internal static string CheckForSiteRootRedirect(string alias, string destUrl) + { + // 540 - don't append /default.aspx onto the end of a site root redirect. + if (destUrl.EndsWith(alias + "/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // this is just the portal alias root + /defualt.aspx. + // we don't want that, just the portalAliasRoot + "/" + string aliasPlusSlash = alias + "/"; + + // get everything up to the end of the portal alias + destUrl = destUrl.Substring(0, destUrl.IndexOf(aliasPlusSlash, StringComparison.Ordinal) + aliasPlusSlash.Length); + } + + return destUrl; + } + + /// + internal override void RewriteUrl(object sender, EventArgs e) + { + Guid parentTraceId = Guid.Empty; + const bool debug = true; + bool failedInitialization = false; + bool ignoreForInstall = false; + var app = (HttpApplication)sender; + try + { + // 875 : completely ignore install/upgrade requests immediately + ignoreForInstall = IgnoreRequestForInstall(app.Request); + + if (ignoreForInstall == false) + { + this.settings = new FriendlyUrlSettings(-1); + + this.SecurityCheck(app); + } + } + catch (Exception ex) + { + // exception handling for advanced Url Rewriting requests + failedInitialization = true; + DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); + if (app.Context != null) + { + ShowDebugData(app.Context, app.Request.Url.AbsoluteUri, null, ex); + var action = new UrlAction(app.Request) { Action = ActionType.Output404 }; + Handle404OrException(this.settings, app.Context, ex, action, false, debug); + } + else + { + throw; + } + } + + if (!failedInitialization && !ignoreForInstall) + { + // if made it through there and not installing, go to next call. Not in exception catch because it implements it's own top-level exception handling + var request = app.Context.Request; + + // 829 : change constructor to stop using physical path + var result = new UrlAction(request) + { + IsSecureConnection = request.IsSecureConnection, + IsSSLOffloaded = UrlUtils.IsSslOffloadEnabled(request), + RawUrl = request.RawUrl, + }; + this.ProcessRequest( + app.Context, + app.Context.Request.Url, + Host.UseFriendlyUrls, + result, + this.settings, + true, + parentTraceId); + } + } + + protected bool IsPortalAliasIncorrect( + HttpContext context, + HttpRequest request, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + Guid parentTraceId, + out string httpAlias) + { + // now check to make sure it's the primary portal alias for this portal/language/browser + bool incorrectAlias = false; + httpAlias = null; + + // if (result.RedirectAllowed && result.PortalId > -1) + if (result.PortalId > -1) + { + // portal has been identified + var portalAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + // if we're not on the primary alias, and portalaliasmapping is set to redirect, we might need to be redirected + var redirectToPrimary = !result.PortalAlias.IsPrimary && result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect; + + // forceAlias used in querystring? + var forceAliasInQueryString = queryStringCol != null && queryStringCol["forceAlias"] != null && queryStringCol["forceAlias"] != "true"; + if (redirectToPrimary || forceAliasInQueryString) + { + if (portalAliases.Count > 0) + { + string checkAlias = result.HttpAlias; + bool continueLoop = true; + bool triedWWW = false; + while (httpAlias == null && continueLoop) + { + if (portalAliases.ContainsAlias(result.PortalId, checkAlias)) + { + if (portalAliases.Count > 0) + { + // var cpa = portalAliases.GetAliasByPortalIdAndSettings(result); + string url = requestUri.ToString(); + RewriteController.CheckLanguageMatch(ref url, result); + var cpa = portalAliases + .Where(a => a.IsPrimary || result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) + .GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, result.BrowserType); + + if (cpa != null) + { + httpAlias = cpa.HTTPAlias; + continueLoop = false; + } + + if (string.IsNullOrEmpty(result.CultureCode) && cpa == null) + { + // if there is a specific culture for this portal alias, then check that + string culture = portalAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); + + // if this matches the alias of the request, then we know we have the correct alias because it is a specific culture + if (!string.IsNullOrEmpty(culture)) + { + continueLoop = false; + } + } + } + } + + // check whether to still go on or not + if (continueLoop) + { + // this alias doesn't exist in the list + // check if it has a www on it - if not, try adding, if it does, try removing + if (!triedWWW) + { + triedWWW = true; // now tried adding/removing www + if (checkAlias.StartsWith("www.", StringComparison.InvariantCultureIgnoreCase)) + { + checkAlias = checkAlias.Substring(4); + } + else + { + checkAlias = "www." + checkAlias; + } + } + else + { + // last thing to try, get the default language and see if there is a portal alias for that + // thus, any aliases not identified as belonging to a language are redirected back to the + // alias named for the default language + continueLoop = false; + + // 735 : switch to custom method for getting portal + PortalInfo pi = CacheController.GetPortal(result.PortalId, false); + if (pi != null) + { + string cultureCode = pi.DefaultLanguage; + if (!string.IsNullOrEmpty(cultureCode)) + { + var primaryPortalAlias = portalAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, cultureCode, settings); + if (primaryPortalAlias != null) + { + httpAlias = primaryPortalAlias.HTTPAlias; + } + } + } + } + } + } + } + + // check to see if it is a custom tab alais - in that case, it is allowed to be requested for the tab + if (CheckIfAliasIsCustomTabAlias(ref result, httpAlias, settings)) + { + // change the primary alias to the custom tab alias that has been requested. + result.PrimaryAlias = result.PortalAlias; + } + else + if (httpAlias != null && string.Compare(httpAlias, result.HttpAlias, StringComparison.OrdinalIgnoreCase) != 0) + { + incorrectAlias = true; + } + } + } + + return incorrectAlias; + } + + private static void ShowDebugData(HttpContext context, string requestUri, UrlAction result, Exception ex) + { + if (context != null) + { + HttpResponse response = context.Response; + + // handle null responses wherever they might be found - this routine must be tolerant to all kinds of invalid inputs + if (requestUri == null) + { + requestUri = "null Uri"; + } + + string finalUrl = "null final Url"; + string rewritePath = "null rewrite path"; + string action = "null action"; + if (result != null) + { + finalUrl = result.FinalUrl; + action = result.Action.ToString(); + rewritePath = result.RewritePath; + } + + // format up the error message to show + const string debugMsg = "{0}, {1}, {2}, {3}, {4}, {5}, {6}"; + string productVer = DotNetNukeContext.Current.Application.Version.ToString(); + string portalSettings = string.Empty; + string browser = "Unknown"; + + // 949 : don't rely on 'result' being non-null + if (result != null) + { + browser = result.BrowserType.ToString(); + } + + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + portalSettings = ps.PortalId.ToString(); + if (ps.PortalAlias != null) + { + portalSettings += ":" + ps.PortalAlias.HTTPAlias; + } + } + } + + response.AppendHeader( + "X-" + ProductName + "-Debug", + string.Format( + debugMsg, + requestUri, + finalUrl, + rewritePath, + action, + productVer, + portalSettings, + browser)); + int msgNum = 1; + if (result != null) + { + foreach (string msg in result.DebugMessages) + { + response.AppendHeader("X-" + ProductName + "-Debug-" + msgNum.ToString("00"), msg); + msgNum++; + } + } + + if (ex != null) + { + response.AppendHeader("X-" + ProductName + "-Ex", ex.Message); + } + } + } + + private static void Handle404OrException(FriendlyUrlSettings settings, HttpContext context, Exception ex, UrlAction result, bool transfer, bool showDebug) + { + // handle Auto-Add Alias + if (result.Action == ActionType.Output404 && CanAutoAddPortalAlias()) + { + // Need to determine if this is a real 404 or a possible new alias. + var portalId = Host.HostPortalID; + if (portalId > Null.NullInteger) + { + if (string.IsNullOrEmpty(result.DomainName)) + { + result.DomainName = Globals.GetDomainName(context.Request); // parse the domain name out of the request + } + + // Get all the existing aliases + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(portalId).ToList(); + + bool autoaddAlias; + bool isPrimary = false; + if (!aliases.Any()) + { + autoaddAlias = true; + isPrimary = true; + } + else + { + autoaddAlias = true; + foreach (var alias in aliases) + { + if (result.DomainName.ToLowerInvariant().IndexOf(alias.HTTPAlias, StringComparison.Ordinal) == 0 + && result.DomainName.Length >= alias.HTTPAlias.Length) + { + autoaddAlias = false; + break; + } + } + } + + if (autoaddAlias) + { + var portalAliasInfo = new PortalAliasInfo + { + PortalID = portalId, + HTTPAlias = result.DomainName, + IsPrimary = isPrimary, + }; + PortalAliasController.Instance.AddPortalAlias(portalAliasInfo); + + context.Response.Redirect(context.Request.Url.ToString(), true); + } + } + } + + if (context != null) + { + HttpRequest request = context.Request; + HttpResponse response = context.Response; + HttpServerUtility server = context.Server; + + const string errorPageHtmlHeader = @"{0}"; + const string errorPageHtmlFooter = @""; + var errorPageHtml = new StringWriter(); + CustomErrorsSection ceSection = null; + + // 876 : security catch for custom error reading + try + { + ceSection = (CustomErrorsSection)WebConfigurationManager.GetSection("system.web/customErrors"); + } + + // ReSharper disable once EmptyGeneralCatchClause + catch (Exception) + { + // on some medium trust environments, this will throw an exception for trying to read the custom Errors + // do nothing + } + + /* 454 new 404/500 error handling routine */ + bool useDNNTab = false; + int errTabId = -1; + string errUrl = null; + string status = string.Empty; + bool isPostback = false; + if (settings != null) + { + if (request.RequestType == "POST") + { + isPostback = true; + } + + if (result != null && ex != null) + { + result.DebugMessages.Add("Exception: " + ex.Message); + result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); + if (ex.InnerException != null) + { + result.DebugMessages.Add("Inner Ex : " + ex.InnerException.Message); + result.DebugMessages.Add("Stack Trace: " + ex.InnerException.StackTrace); + } + else + { + result.DebugMessages.Add("Inner Ex : null"); + } + } + + string errRH; + string errRV; + int statusCode; + if (result != null && result.Action != ActionType.Output404) + { + // output everything but 404 (usually 500) + if (settings.TabId500 > -1) + { + // tabid specified for 500 error page, use that + useDNNTab = true; + errTabId = settings.TabId500; + } + + errUrl = settings.Url500; + errRH = "X-UrlRewriter-500"; + errRV = "500 Rewritten to {0} : {1}"; + statusCode = 500; + status = "500 Internal Server Error"; + } + else + { + // output 404 error + // if the tabid is specified for a 404 page, then use that + if (settings.TabId404 > -1) + { + useDNNTab = true; + errTabId = settings.TabId404; + } + + // with 404 errors, there's an option to catch certain urls and use an external url for extra processing. + if (!string.IsNullOrEmpty(settings.Regex404)) + { + try + { + // 944 : check the original Url in case the requested Url has been rewritten before discovering it's a 404 error + string requestedUrl = request.Url.ToString(); + if (result != null && !string.IsNullOrEmpty(result.OriginalPath)) + { + requestedUrl = result.OriginalPath; + } + + if (Regex.IsMatch(requestedUrl, settings.Regex404, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + useDNNTab = false; + + // if we have a match in the 404 regex value, then don't use the tabid + } + } + catch (Exception regexEx) + { + // .some type of exception : output in response header, and go back to using the tabid + response.AppendHeader("X-UrlRewriter-404Exception", regexEx.Message); + } + } + + errUrl = settings.Url404; + errRH = "X-UrlRewriter-404"; + errRV = "404 Rewritten to {0} : {1} : Reason {2}"; + status = "404 Not Found"; + statusCode = 404; + } + + // check for 404 logging + if (result == null || result.Action == ActionType.Output404) + { + // Log 404 errors to Event Log + UrlRewriterUtils.Log404(request, settings, result); + } + + // 912 : use unhandled 404 switch + string reason404 = null; + bool unhandled404 = true; + if (useDNNTab && errTabId > -1) + { + unhandled404 = false; // we're handling it here + TabInfo errTab = TabController.Instance.GetTab(errTabId, result.PortalId, true); + if (errTab != null) + { + bool redirect = false; + + // ok, valid tabid. what we're going to do is to load up this tab via a rewrite of the url, and then change the output status + string reason = "Not Found"; + if (result != null) + { + reason = result.Reason.ToString(); + } + + response.AppendHeader( + errRH, + string.Format( + errRV, + "DNN Tab", + errTab.TabName + "(Tabid:" + errTabId.ToString() + ")", + reason)); + + // show debug messages even if in debug mode + if (context != null && response != null && result != null && showDebug) + { + ShowDebugData(context, result.OriginalPath, result, null); + } + + if (!isPostback) + { + response.ClearContent(); + response.StatusCode = statusCode; + response.Status = status; + } + else + { + redirect = true; + + // redirect postbacks as you can't postback successfully to a server.transfer + } + + errUrl = Globals.glbDefaultPage + TabIndexController.CreateRewritePath(errTab.TabID, string.Empty); + + // have to update the portal settings with the new tabid + PortalSettings ps = null; + if (context != null && context.Items != null) + { + if (context.Items.Contains("PortalSettings")) + { + ps = (PortalSettings)context.Items["PortalSettings"]; + context.Items.Remove("PortalSettings"); // nix it from the context + } + } + + if (ps != null && ps.PortalAlias != null) + { + ps = new PortalSettings(errTabId, ps.PortalAlias); + } + else + { + if (result.HttpAlias != null && result.PortalId > -1) + { + PortalAliasInfo pa = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias, result.PortalId); + ps = new PortalSettings(errTabId, pa); + } + else + { + // 912 : handle 404 when no valid portal can be identified + // results when iis is configured to handle portal alias, but + // DNN isn't. This always returns 404 because a multi-portal site + // can't just show the 404 page of the host site. + ArrayList portals = PortalController.Instance.GetPortals(); + if (portals != null && portals.Count == 1) + { + // single portal install, load up portal settings for this portal + var singlePortal = (PortalInfo)portals[0]; + + // list of aliases from database + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(singlePortal.PortalID).ToList(); + + // list of aliases from Advanced Url settings + List chosen = aliases.GetAliasesForPortalId(singlePortal.PortalID); + PortalAliasInfo useFor404 = null; + + // go through all aliases and either get the first valid one, or the first + // as chosen in the advanced url management settings + foreach (var pa in aliases) + { + if (useFor404 == null) + { + useFor404 = pa; // first one by default + } + + // matching? + if (chosen != null && chosen.Count > 0) + { + if (chosen.Contains(pa.HTTPAlias)) + { + useFor404 = pa; + } + } + else + { + break; // no further checking + } + } + + // now configure that as the portal settings + if (useFor404 != null) + { + // create portal settings context for identified portal alias in single portal install + ps = new PortalSettings(errTabId, useFor404); + } + } + else + { + reason404 = "Requested domain name is not configured as valid website"; + unhandled404 = true; + } + } + } + + if (ps != null) + { + // re-add the context items portal settings back in + context.Items.Add("PortalSettings", ps); + } + + if (redirect) + { + errUrl = TestableGlobals.Instance.NavigateURL(); + response.Redirect(errUrl, true); // redirect and end response. + + // It will mean the user will have to postback again, but it will work the second time + } + else + { + if (transfer) + { + // execute a server transfer to the default.aspx?tabid=xx url + // 767 : object not set error on extensionless 404 errors + if (context.User == null) + { + context.User = GetCurrentPrincipal(context); + } + + response.TrySkipIisCustomErrors = true; + + // 881 : spoof the basePage object so that the client dependency framework + // is satisfied it's working with a page-based handler + IHttpHandler spoofPage = new CDefault(); + context.Handler = spoofPage; + server.Transfer("~/" + errUrl, true); + } + else + { + context.RewritePath("~/Default.aspx", false); + response.TrySkipIisCustomErrors = true; + response.Status = "404 Not Found"; + response.StatusCode = 404; + } + } + } + } + + // 912 : change to new if statement to handle cases where the TabId404 couldn't be handled correctly + if (unhandled404) + { + // proces the error on the external Url by rewriting to the external url + if (!string.IsNullOrEmpty(errUrl)) + { + response.ClearContent(); + response.TrySkipIisCustomErrors = true; + string reason = "Not Found"; + if (result != null) + { + reason = result.Reason.ToString(); + } + + response.AppendHeader(errRH, string.Format(errRV, "Url", errUrl, reason)); + if (reason404 != null) + { + response.AppendHeader("X-Url-Master-404-Data", reason404); + } + + response.StatusCode = statusCode; + response.Status = status; + server.Transfer("~/" + errUrl, true); + } + else + { + errorPageHtml.Write(status + "
    The requested Url does not return any valid content."); + if (reason404 != null) + { + errorPageHtml.Write(status + "
    " + reason404); + } + + errorPageHtml.Write("
    Administrators
    "); + errorPageHtml.Write("
    Change this message by configuring a specific 404 Error Page or Url for this website.
    "); + + // output a reason for the 404 + string reason = string.Empty; + if (result != null) + { + reason = result.Reason.ToString(); + } + + if (!string.IsNullOrEmpty(errRH) && !string.IsNullOrEmpty(reason)) + { + response.AppendHeader(errRH, reason); + } + + response.StatusCode = statusCode; + response.Status = status; + } + } + } + else + { + // fallback output if not valid settings + if (result != null && result.Action == ActionType.Output404) + { + // don't restate the requested Url to prevent cross site scripting + errorPageHtml.Write("404 Not Found
    The requested Url does not return any valid content."); + response.StatusCode = 404; + response.Status = "404 Not Found"; + } + else + { + // error, especially if invalid result object + errorPageHtml.Write("500 Server Error
    An error occured during processing : if possible, check the event log of the server
    "); + response.StatusCode = 500; + response.Status = "500 Internal Server Error"; + if (result != null) + { + result.Action = ActionType.Output500; + } + } + } + + if (ex != null) + { + if (context != null) + { + if (context.Items.Contains("UrlRewrite:Exception") == false) + { + context.Items.Add("UrlRewrite:Exception", ex.Message); + context.Items.Add("UrlRewrite:StackTrace", ex.StackTrace); + } + } + + if (ceSection != null && ceSection.Mode == CustomErrorsMode.Off) + { + errorPageHtml.Write(errorPageHtmlHeader); + errorPageHtml.Write("
    Exception:
    " + ex.Message + "
    "); + errorPageHtml.Write("
    Stack Trace:
    " + ex.StackTrace + "
    "); + errorPageHtml.Write("
    Administrators
    "); + errorPageHtml.Write("
    You can see this exception because the customErrors attribute in the web.config is set to 'off'. Change this value to 'on' or 'RemoteOnly' to show Error Handling
    "); + try + { + if (errUrl != null && errUrl.StartsWith("~")) + { + errUrl = VirtualPathUtility.ToAbsolute(errUrl); + } + } + finally + { + if (errUrl != null) + { + errorPageHtml.Write("
    The error handling would have shown this page : " + errUrl + "
    "); + } + else + { + errorPageHtml.Write("
    The error handling could not determine the correct page to show.
    "); + } + } + } + } + + string errorPageHtmlBody = errorPageHtml.ToString(); + if (errorPageHtmlBody.Length > 0) + { + response.Write(errorPageHtmlHeader); + response.Write(errorPageHtmlBody); + response.Write(errorPageHtmlFooter); + } + + if (ex != null) + { + UrlRewriterUtils.LogExceptionInRequest(ex, status, result); + } + } + } + + private static IPrincipal GetCurrentPrincipal(HttpContext context) + { + // Extract the forms authentication cookie + var authCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName]; + var currentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), new string[0]); + + try + { + if (authCookie != null) + { + var authTicket = FormsAuthentication.Decrypt(authCookie.Value); + if (authTicket != null && !authTicket.Expired) + { + var roles = authTicket.UserData.Split('|'); + var id = new FormsIdentity(authTicket); + currentPrincipal = new GenericPrincipal(id, roles); + } + } + } + catch (Exception) + { + // do nothing here. + } + + return currentPrincipal; + } + + private static bool CheckForDebug(HttpRequest request, NameValueCollection queryStringCol, bool debugEnabled) + { + string debugValue = string.Empty; + bool retVal = false; + + if (debugEnabled) + { + const string debugToken = "_aumdebug"; + if (queryStringCol != null && queryStringCol[debugToken] != null) + { + debugValue = queryStringCol[debugToken]; + } + else + { + if (request != null) + { + debugValue = request.Params.Get("HTTP_" + debugToken.ToUpper()); + } + + if (debugValue == null) + { + debugValue = "false"; + } + } + } + + switch (debugValue.ToLowerInvariant()) + { + case "true": + retVal = true; + break; + } + + return retVal; + } + + private static bool CheckForTabExternalForwardOrRedirect( + HttpContext context, + ref UrlAction result, + HttpResponse response, + FriendlyUrlSettings settings, + Guid parentTraceId) + { + bool finished = false; + HttpRequest request = null; + if (context != null) + { + request = context.Request; + } + + try + { + // check for external forwarding or a permanent redirect request + // 592 : check for permanent redirect (823 : moved location from 'checkForRedirects') + if (result.TabId > -1 && result.PortalId > -1 && + (settings.ForwardExternalUrlsType != DNNPageForwardType.NoForward || + result.Reason == RedirectReason.Tab_Permanent_Redirect)) + { + bool allowRedirect = !(result.RewritePath != null && result.RewritePath.ToLowerInvariant().Contains("&ctl=tab")); + + // 594 : do not redirect settings pages for external urls + if (allowRedirect) + { + TabInfo tab; + allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, false, out tab, settings); + if (allowRedirect) + { + // 772 : not redirecting file type Urls when requested. + bool permanentRedirect = false; + string redirectUrl = null; + string cleanPath = null; + bool doRedirect = false; + switch (tab.TabType) + { + case TabType.File: + // have to fudge in a portal settings object for this to work - shortcoming of LinkClick URl generation + var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); + if (context != null) + { + context.Items.Add("PortalSettings", portalSettings); + result.Reason = RedirectReason.File_Url; + string fileUrl = Globals.LinkClick(tab.Url, tab.TabID, -1); + context.Items.Remove("PortalSettings"); + + // take back out again, because it will be done further downstream + // do a check to make sure we're not repeating the Url again, because the tabid is set but we don't want to touch + // a linkclick url + if (!result.OriginalPathNoAlias.EndsWith(HttpUtility.UrlDecode(fileUrl), true, CultureInfo.InvariantCulture)) + { + redirectUrl = fileUrl; + } + } + + if (redirectUrl != null) + { + doRedirect = true; + } + + break; + case TabType.Url: + result.Reason = RedirectReason.Tab_External_Url; + redirectUrl = tab.Url; + if (redirectUrl != null) + { + doRedirect = true; + if (tab.PermanentRedirect) + { + result.Action = ActionType.Redirect301; + } + else + { + result.Action = ActionType.Redirect302; + } + } + + break; + case TabType.Tab: + // get the redirect path of the specific tab, as long as we have a valid request to work from + if (request != null) + { + // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider + cleanPath = RewriteController.GetRewriteOrRequestedPath(result, request.Url); + + // 727 prevent redirectLoop with do301 in querystring + if (result.Action == ActionType.Redirect301 || + result.Action == ActionType.Redirect302) + { + cleanPath = RedirectTokens.RemoveAnyRedirectTokens( + cleanPath, + request.QueryString); + } + + // get the redirect Url from the friendly url provider using the tab, path and settings + redirectUrl = RedirectController.GetTabRedirectUrl( + tab, + settings, + cleanPath, + result, + out permanentRedirect, + parentTraceId); + } + + // check to make sure there isn't a blank redirect Url + if (redirectUrl == null) + { + // problem : no redirect Url to redirect to + // solution : cancel the redirect + string message = "Permanent Redirect chosen for Tab " + + tab.TabPath.Replace("//", "/") + + " but forwarding Url was not valid"; + RedirectController.CancelRedirect(ref result, context, settings, message); + } + else + { + // if there was a redirect Url, set the redirect action and set the type of redirect going to use + doRedirect = true; + if (permanentRedirect) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Tab_Permanent_Redirect; + } + else + { + // not a permanent redirect, check if the page forwarding is set + result.Action = ActionType.Redirect302; + result.Reason = RedirectReason.Tab_Temporary_Redirect; + } + + // should be already set, anyway + result.RewritePath = cleanPath; + } + + break; + default: + // only concern here is if permanent redirect is requested, but there is no external url specified + if (result.Reason == RedirectReason.Tab_Permanent_Redirect) + { + bool permRedirect = tab.PermanentRedirect; + if (permRedirect) + { + // problem : permanent redirect marked, but no forwarding url supplied + // solution : cancel redirect + string message = "Permanent Redirect chosen for Tab " + + tab.TabPath.Replace("//", "/") + + " but no forwarding Url Supplied"; + RedirectController.CancelRedirect(ref result, context, settings, message); + } + } + + break; + } + + // do the redirect we have specified + if (doRedirect && + (result.Action == ActionType.Redirect301 || result.Action == ActionType.Redirect302)) + { + result.FinalUrl = redirectUrl; + if (result.Action == ActionType.Redirect301) + { + if (response != null) + { + // perform a 301 redirect to the external url of the tab + response.AppendHeader( + "X-Redirect-Reason", + result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + } + } + else + { + if (result.Action == ActionType.Redirect302) + { + if (response != null) + { + // perform a 301 redirect to the external url of the tab + response.AppendHeader( + "X-Redirect-Reason", + result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl); + } + } + } + + finished = true; + } + } + } + } + } + catch (ThreadAbortException) + { + // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest + // level try/catch block, so we handle it here. + } + + return finished; + } + + /// Redirects an alias if that is allowed by the settings. + /// + /// + /// + /// if the is a redirect, otherwise . + private static bool RedirectPortalAlias(string httpAlias, ref UrlAction result, FriendlyUrlSettings settings) + { + bool redirected = false; + + // redirect to primary alias + if (result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect && result.RedirectAllowed) + { + if (result.Reason == RedirectReason.Wrong_Portal_Alias_For_Browser_Type || result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture || + result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) + { + redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, result.Reason, settings.InternalAliasList, settings); + } + else + { + redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, settings.InternalAliasList, settings); + } + } + + return redirected; + } + + private static bool ConfigurePortalAliasRedirect( + ref UrlAction result, + string wrongAlias, + string rightAlias, + bool ignoreCustomAliasTabs, + List internalAliases, + FriendlyUrlSettings settings) + { + return ConfigurePortalAliasRedirect( + ref result, + wrongAlias, + rightAlias, + ignoreCustomAliasTabs, + RedirectReason.Wrong_Portal_Alias, + internalAliases, + settings); + } + + /// Checks to see whether the specified alias is a customTabAlias. + /// + /// + /// + /// if the alias is a custom tab alias, otherwise . + private static bool CheckIfAliasIsCustomTabAlias(ref UrlAction result, string httpAlias, FriendlyUrlSettings settings) + { + List customAliasesForTabs = TabIndexController.GetCustomPortalAliases(settings); + bool isACustomTabAlias = false; + if (customAliasesForTabs != null && customAliasesForTabs.Count > 0) + { + // remove any customAliases that are also primary aliases. + foreach (var cpa in PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId)) + { + if (cpa.IsPrimary == true && customAliasesForTabs.Contains(cpa.HTTPAlias)) + { + customAliasesForTabs.Remove(cpa.HTTPAlias); + } + } + + isACustomTabAlias = customAliasesForTabs.Contains(httpAlias.ToLowerInvariant()); + } + + return isACustomTabAlias; + } + + /// Checks to see whether the specified alias is a customTabAlias for the TabId in result. + /// + /// + /// if the the current alias is a custom tab alias, otherwise . + private static bool CheckIfAliasIsCurrentTabCustomTabAlias(ref UrlAction result, FriendlyUrlSettings settings) + { + var customAliasesForTab = TabController.Instance.GetCustomAliases(result.TabId, result.PortalId); + bool isCurrentTabCustomTabAlias = false; + if (customAliasesForTab != null && customAliasesForTab.Count > 0) + { + // see if we have a customAlias for the current CultureCode + if (customAliasesForTab.ContainsKey(result.CultureCode)) + { + // if it is for the current culture, we need to know if it's a primary alias + var tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(customAliasesForTab[result.CultureCode]); + if (tabPortalAlias != null && !tabPortalAlias.IsPrimary) + { + // it's not a primary alias, so must be a custom tab alias + isCurrentTabCustomTabAlias = true; + } + } + } + + // if it's not a custom alias for the current tab, we'll need to change the result + if (!isCurrentTabCustomTabAlias) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Wrong_Portal_Alias; + } + + return isCurrentTabCustomTabAlias; + } + + /// Configures the result object to set the correct Alias redirect parameters and destination URL. + /// + /// + /// + /// + /// + /// + /// + /// if the is a redirect, otherwise . + private static bool ConfigurePortalAliasRedirect( + ref UrlAction result, + string wrongAlias, + string rightAlias, + bool ignoreCustomAliasTabs, + RedirectReason redirectReason, + List internalAliases, + FriendlyUrlSettings settings) + { + // wrong alias for the portal + // check to see if the wrong portal alias could be a custom alias for a tab + bool doRedirect; + if (ignoreCustomAliasTabs == false) + { + // check out custom alias tabs collection + // if an alias is a custom tab alias for a specific tab, then don't redirect + // if we have the TabId, we'll need to check if the alias is valid for the current tab + if (result.TabId > 0 && CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) + { + doRedirect = false; + } + else if (result.TabId < 0 && CheckIfAliasIsCustomTabAlias(ref result, wrongAlias, settings)) + { + doRedirect = false; + } + else + { + doRedirect = true; + } + } + else + { + doRedirect = true; // do redirect, ignore custom alias entries for tabs + } + + // check to see if it is an internal alias. These are used to block redirects + // to allow for reverse proxy requests, which must change the rewritten alias + // while leaving the requested alias + bool internalAliasFound = false; + if (doRedirect && internalAliases != null && internalAliases.Count > 0) + { + if (internalAliases.Any(ia => string.Compare(ia.HttpAlias, wrongAlias, StringComparison.OrdinalIgnoreCase) == 0)) + { + internalAliasFound = true; + doRedirect = false; + } + } + + // if still need to do redirect, then set the settings that will cause the redirect (redirect not done here) + if (doRedirect) + { + result.Action = ActionType.Redirect301; + result.Reason = redirectReason; + var destUrl = result.OriginalPath; + if (result.OriginalPath.Contains(wrongAlias)) + { + destUrl = result.OriginalPath.Replace(wrongAlias, rightAlias); + } + else if (result.OriginalPath.ToLowerInvariant().Contains(wrongAlias)) + { + destUrl = result.OriginalPath.ToLowerInvariant().Replace(wrongAlias, rightAlias); + } + + if (redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture || + redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) + { + destUrl = destUrl.Replace("/language/" + result.CultureCode, string.Empty); + } + + destUrl = CheckForSiteRootRedirect(rightAlias, destUrl); + result.FinalUrl = destUrl; + } + else + { + // 838 : don't overwrite the reason if already have checkfor301 + // and don't do a check on the basis that an internal alias was found + if (result.Action != ActionType.CheckFor301 && internalAliasFound == false) + { + // set status to 'check for redirect' + result.Action = ActionType.CheckFor301; + result.Reason = RedirectReason.Custom_Tab_Alias; + } + } + + return doRedirect; + } + + private static string MakeUrlWithAlias(Uri requestUri, string httpAlias) + { + return requestUri.AbsoluteUri.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase) + ? "https://" + httpAlias.Replace("*.", string.Empty) + "/" + : "http://" + httpAlias.Replace("*.", string.Empty) + "/"; + } + + private static string MakeUrlWithAlias(Uri requestUri, PortalAliasInfo alias) + { + return MakeUrlWithAlias(requestUri, alias.HTTPAlias); + } + + /// Determines if this is a request from an install / upgrade url. + /// + /// + /// + /// + /// if the request is for an install URL, otherwise . + /// + /// //875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1. + /// + private static bool IgnoreRequestForInstall(string physicalPath, string refererPath, string requestedDomain, string refererDomain) + { + if (physicalPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) + || physicalPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) + || physicalPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture) + || Globals.Status == Globals.UpgradeStatus.Install + || Globals.Status == Globals.UpgradeStatus.Upgrade) + { + return true; + } + + // 954 : DNN 7.0 compatibility + // check for /default.aspx which is default Url launched from the Upgrade/Install wizard page + // 961 : check domain as well as path for the referer + if (physicalPath.EndsWith(Globals.glbDefaultPage, true, CultureInfo.InvariantCulture) == false + && refererPath != null + && string.Compare(requestedDomain, refererDomain, StringComparison.OrdinalIgnoreCase) == 0 + && (refererPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) + || refererPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) + || refererPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + private static bool IgnoreRequestForWebServer(string requestedPath) + { + // Should standardize comparison methods + if (requestedPath.IndexOf("synchronizecache.aspx", StringComparison.OrdinalIgnoreCase) > 1 + || requestedPath.EndsWith("keepalive.aspx", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Get the root + var rootPath = requestedPath.Substring(0, requestedPath.LastIndexOf("/", StringComparison.Ordinal)); + rootPath = rootPath.Substring(rootPath.IndexOf("://", StringComparison.Ordinal) + 3); + + // Check if this is a WebServer and not a portalalias. + // if can auto add portal alias enabled, then return false, alias will add later. + var alias = PortalAliasController.Instance.GetPortalAlias(rootPath); + if (alias != null || CanAutoAddPortalAlias()) + { + return false; + } + + // Check if this is a WebServer + var server = ServerController.GetEnabledServers().SingleOrDefault(s => s.Url == rootPath); + if (server != null) + { + return true; + } + + return false; + } + + private static bool IgnoreRequestForInstall(HttpRequest request) + { + try + { + string physicalPath = request.PhysicalPath; + string requestedDomain = request.Url.Host; + string refererPath = null, refererDomain = null; + if (request.UrlReferrer != null) + { + refererDomain = request.UrlReferrer.Host; + refererPath = request.UrlReferrer.LocalPath; + } + + return IgnoreRequestForInstall(physicalPath, refererPath, requestedDomain, refererDomain); + } + catch (PathTooLongException) + { + // catch and handle this exception, caused by an excessively long file path based on the + // mapped virtual url + return false; + } + catch (ArgumentException) + { + // catch and handle this exception, caused by an invalid character in the file path based on the + // mapped virtual url + return false; + } + catch (UriFormatException) + { + // catch and handle this exception, caused by an invalid hostname in the referrer + return false; + } + } + + private static bool IgnoreRequest(UrlAction result, string requestedPath, string ignoreRegex, HttpRequest request) + { + bool retVal = false; + + // check if we are upgrading/installing + // 829 : use result physical path instead of requset physical path + // 875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1 + if (request != null && (IgnoreRequestForInstall(request) || IgnoreRequestForWebServer(requestedPath))) + { + // ignore all install requests + retVal = true; + } + else if (request != null && request.Path.EndsWith("imagechallenge.captcha.aspx", StringComparison.InvariantCultureIgnoreCase)) + { + retVal = true; + } + else + { + try + { + if (ignoreRegex.Length > 0) + { + if (Regex.IsMatch(requestedPath, ignoreRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + retVal = true; + } + } + } + catch (Exception ex) + { + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + result.Ex = ex; + } + } + + return retVal; + } + + private static void CheckForRewrite( + string fullUrl, + string querystring, + UrlAction result, + bool useFriendlyUrls, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + out bool isPhysicalResource, + Guid parentTraceId) + { + bool checkForRewrites; + + // just check to make sure it isn't a physical resource on the server + RewriteController.IdentifyByPhysicalResource( + result.PhysicalPath, + fullUrl, + queryStringCol, + ref result, + useFriendlyUrls, + settings, + out isPhysicalResource, + out checkForRewrites, + parentTraceId); + + if (checkForRewrites && RewriteController.CanRewriteRequest(result, fullUrl, settings)) + { + bool doSiteUrlProcessing = false; + + // 728 new regex expression to pass values straight onto the siteurls.config file + if (!string.IsNullOrEmpty(settings.UseSiteUrlsRegex)) + { + doSiteUrlProcessing = Regex.IsMatch(fullUrl, settings.UseSiteUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + // if a virtual request, and not starting with the siteUrls.config file, go on to find the rewritten path + if (!doSiteUrlProcessing) + { + // looks up the page index to find the correct Url + bool doRewrite = RewriteController.IdentifyByTabPathEx(fullUrl, querystring, result, queryStringCol, settings, parentTraceId); + if (!doRewrite) + { + doSiteUrlProcessing = true; + } + } + + if (doSiteUrlProcessing) + { + // 728 : compare requests against the siteurls.config file, either if no other match was found, or if we want to skip the rest of the processing + // the standard DNN way of rewriting, using expressions found in the siteurls.config file + RewriteController.IdentifyByRegEx(fullUrl, querystring, result.ApplicationPath, ref result, settings, parentTraceId); + } + } + } + + private static bool CheckForRedirects( + Uri requestUri, + string fullUrl, + NameValueCollection queryStringCol, + UrlAction result, + string requestType, + FriendlyUrlSettings settings, + int portalHomeTabId) + { + bool redirected = false; + if (queryStringCol["error"] == null && queryStringCol["message"] == null && requestType != "POST") + { + // if the / is missing from an extension-less request, then check for a 301 redirect + if (settings.PageExtensionUsageType == PageExtensionUsageType.Never) + { + // 575 check on absolutePath instead of absoluteUri : this ignores query strings and fragments like # + // 610 don't always end with '/' - reverses previous setting + // 687 don't double-check 301 redirects. 'CheckFor301' is less concise than 'Redirect301' + // DNN-21906: if the redirect is for splash page, then we should continue the 302 redirect. + if (requestUri.AbsolutePath.EndsWith("/") && result.Action != ActionType.Redirect301 && result.Reason != RedirectReason.Requested_SplashPage) + { + result.Action = ActionType.CheckFor301; + } + } + + if (settings.RedirectWrongCase && result.Action == ActionType.Continue) + { + result.Action = ActionType.CheckFor301; + } + + string scheme = requestUri.Scheme + Uri.SchemeDelimiter; + bool queryStringHas301Parm = queryStringCol["do301"] != null; + + // 727 : keep a bool value if there is a do301 request in the querystring + // check for a 301 request in the query string, or an explicit 301 or 302 request + // 2.0 - check for explicit do301=true instead of just do301 key + string do301Val = queryStringCol["do301"]; + if (result.TabId > -1 + && (result.Action == ActionType.Redirect301 + || (do301Val != null && do301Val == "true") + || result.Action == ActionType.Redirect302)) + { + // valid tab, specific 301 redirect, rewrite hint for specific 301 redirect, or specific 302 redirect + // we have ordered a 301 redirect earlier in the code + // get the url for redirection by re-submitting the path into the Friendly Url Provider + string pathOnly = RewriteController.GetRewriteOrRequestedPath(result, requestUri); + + // 727 prevent redirectLoop with do301 in querystring + if (result.Action == ActionType.Redirect301 || queryStringHas301Parm || result.Action == ActionType.Redirect302) + { + pathOnly = RedirectTokens.RemoveAnyRedirectTokens(pathOnly, queryStringCol); + } + + // check for exclusion by regex for this url + if (result.RedirectAllowed) + { + // get the tab so we know where to go + TabInfo tab; + bool checkRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + + if (checkRedirect) + { + if ((result.Reason == RedirectReason.Deleted_Page || result.Reason == RedirectReason.Disabled_Page) + && portalHomeTabId > 0 + && settings.DeletedTabHandlingType == DeletedTabHandlingType.Do301RedirectToPortalHome) + { + // redirecting to home page + TabInfo homeTab = TabController.Instance.GetTab(portalHomeTabId, result.PortalId, false); + if (homeTab != null) + { + string homePageUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + homeTab, + pathOnly, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + result.Action = ActionType.Redirect301; + result.FinalUrl = homePageUrl; + result.RewritePath = pathOnly; + redirected = true; + } + } + else + { + // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider + string cleanPath = RewriteController.GetRewriteOrRequestedPath(result, requestUri); + + // 727 prevent redirectLoop with do301 in querystring + // also check for existing in path of do301 token + if (result.Action == ActionType.Redirect301 || do301Val != null || result.Action == ActionType.Redirect302) + { + cleanPath = RedirectTokens.RemoveAnyRedirectTokens(cleanPath, queryStringCol); + } + + // get best friendly url from friendly url provider + string bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + + // get what the friendly Url for this tab should be and stick it in as the redirect + // 727 : using boolean because we wanted to get rid of the do301 before calculating the correct url + if (queryStringHas301Parm) + { + result.Action = ActionType.Redirect301; + if (result.Reason == RedirectReason.Not_Redirected) + { + result.Reason = RedirectReason.Unfriendly_Url_1; + } + } + + result.FinalUrl = bestFriendlyUrl; + result.RewritePath = pathOnly; + redirected = true; // mark as redirected + } + } + else + { + // redirect disallowed + // 618: dont' clear if 302 redirect selected + if (result.Action != ActionType.Redirect302Now || result.Action != ActionType.Redirect302) + { + RedirectController.CancelRedirect(ref result, null, settings, "Redirect requested but cancelled because disallowed"); + } + } + } + } + else if (result.TabId > -1 && result.RedirectAllowed && result.Action == ActionType.CheckFor301) + { + // 301 check was requested in earlier processing + // get the tab controller and retrieve the tab the request is for + // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab + if (settings.RedirectUnfriendly) + { + TabInfo tab; + bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + if (allowRedirect && tab != null) + { + // remove the http alias from the url. Do this by putting the url back together from the request and removing the alias + string rewritePathOnly; + if (result.DoRewrite) + { + rewritePathOnly = result.RewritePath; + var pos = rewritePathOnly.IndexOf("default.aspx", StringComparison.OrdinalIgnoreCase); + if (pos > Null.NullInteger) + { + rewritePathOnly = rewritePathOnly.Substring(pos); + } + } + else + { + rewritePathOnly = requestUri.Host + requestUri.PathAndQuery; + } + + // remove the http alias from the path + var pathAliasEnd = rewritePathOnly.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase); + var queryStringIndex = rewritePathOnly.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); + if (pathAliasEnd > Null.NullInteger && (queryStringIndex == Null.NullInteger || pathAliasEnd < queryStringIndex)) + { + rewritePathOnly = rewritePathOnly.Substring(pathAliasEnd + result.PortalAlias.HTTPAlias.Length); + } + + // now check to see if need to remove /default.aspx from the end of the requested Url + string requestedUrl = fullUrl; + int requestedUrlAliasEnd = requestedUrl.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase) + + (result.PortalAlias.HTTPAlias + "/").Length; + if (requestedUrlAliasEnd > Null.NullInteger) + { + // 818 : when a site root is used for a custom page Url, then check for max length within bounds + if ((requestedUrl.Length - requestedUrlAliasEnd) >= 12 && requestedUrl.Substring(requestedUrlAliasEnd).Equals("default.aspx", StringComparison.InvariantCultureIgnoreCase)) + { + requestedUrl = requestedUrl.Substring(0, requestedUrl.Length - 12); + + // 12 = default.aspx length + } + } + + // what happens here is that the request is reverse-engineered to see if it matches what the friendly Url shoudl have been + // get what the friendly Url for this tab should be + string bestFriendlyUrl; + + // 819 : leaving /do301/check in Url because not using cleanPath to remove from + string cleanPath = RedirectTokens.RemoveAnyRedirectTokensAndReasons(rewritePathOnly); + + // string cleanPath = rewritePathOnly.Replace("&do301=check","");//remove check parameter if it exists + // cleanPath = cleanPath.Replace("&do301=true", "");//don't pass through internal redirect check parameter + cleanPath = cleanPath.Replace("&_aumdebug=true", string.Empty); // remove debug parameter if it exists + + Match match = RewritePathRx.Match(rewritePathOnly ?? string.Empty); + if (match.Success) + { + // when the pathOnly value ends with '=' it means there is a query string pair with a key and no value + // make the assumption that this was passed in as a page name OTHER than default page + string pageName = match.Groups["parm"].Value; // get the last parameter in the list + + cleanPath = cleanPath.Replace(match.Value, string.Empty); + + // remove the last parameter from the path + + // generate teh friendly URl name with the last parameter as the page name, not a query string parameter + bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + pageName + settings.PageExtension, + result.HttpAlias, + false, + settings, + Guid.Empty); + } + else + { + bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( + tab, + cleanPath, + Globals.glbDefaultPage, + result.HttpAlias, + false, + settings, + Guid.Empty); + } + + // if the incoming request doesn't match the 'most friendly' url, a 301 Moved Permanently status is returned, along with the friendly url + // check the bestFriendlyUrl against either the url, or rawUrl (with and without host) + // in each case, the aumdebug parameter will be searched for and replaced + var urlDecode = HttpUtility.UrlDecode(requestedUrl); + if (urlDecode != null) + { + string rawUrlWithHost = StripDebugParameter(urlDecode.ToLowerInvariant()); + + // string rawUrlWithHost = StripDebugParameter(System.Web.HttpUtility.UrlDecode(scheme + requestUri.Host + requestUri.PathAndQuery).ToLowerInvariant()); + string rawUrlWithHostNoScheme = StripDebugParameter(rawUrlWithHost.Replace(scheme, string.Empty)); + string bestFriendlyNoScheme = StripDebugParameter(bestFriendlyUrl.ToLowerInvariant().Replace(scheme, string.Empty)); + string requestedPathNoScheme = StripDebugParameter(requestUri.AbsoluteUri.Replace(scheme, string.Empty).ToLowerInvariant()); + string rawUrlLowerCase = StripDebugParameter(requestUri.AbsoluteUri.ToLowerInvariant()); + + // check to see if just an alias redirect of an internal alias + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + if (settings.InternalAliasList != null && settings.InternalAliasList.Count > 0 && primaryAliases.Count > 0) + { + var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result); + if (cpa != null) + { + string chosenAlias = cpa.HTTPAlias.ToLowerInvariant(); + foreach (InternalAlias ia in settings.InternalAliasList) + { + string internalAlias = ia.HttpAlias.ToLowerInvariant(); + if (requestedPathNoScheme.Contains(internalAlias)) + { + // an internal alias has been used. + // replace this in the comparison charts to do a 'fair' comparison + requestedPathNoScheme = requestedPathNoScheme.Replace(internalAlias, chosenAlias); + rawUrlWithHost = rawUrlWithHost.Replace(scheme + internalAlias, scheme + chosenAlias); + rawUrlWithHostNoScheme = rawUrlWithHostNoScheme.Replace(internalAlias, chosenAlias); + rawUrlLowerCase = rawUrlLowerCase.Replace(internalAlias, chosenAlias); + break; + } + } + } + } + + // DNN-9158: prevent SSL Offloading infinite redirects + if (!result.IsSecureConnection && result.IsSSLOffloaded && bestFriendlyNoScheme.StartsWith("https")) + { + bestFriendlyNoScheme = $"http://{bestFriendlyNoScheme.Substring(8)}"; + } + + if (!(bestFriendlyNoScheme == requestedPathNoScheme + || bestFriendlyNoScheme == rawUrlWithHost + || HttpUtility.UrlDecode(bestFriendlyNoScheme) == rawUrlWithHost + || bestFriendlyNoScheme == rawUrlWithHostNoScheme + || bestFriendlyNoScheme == HttpUtility.UrlDecode(requestedPathNoScheme) + || HttpUtility.UrlDecode(bestFriendlyNoScheme) == HttpUtility.UrlDecode(requestedPathNoScheme) + || bestFriendlyNoScheme == rawUrlLowerCase)) + { + redirected = true; + result.Action = ActionType.Redirect301; + result.FinalUrl = bestFriendlyUrl; + if (result.Reason != RedirectReason.Custom_Tab_Alias && + result.Reason != RedirectReason.Deleted_Page && + result.Reason != RedirectReason.Disabled_Page) + { + result.Reason = RedirectReason.Unfriendly_Url_2; + } + + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + requestedPathNoScheme + " [requested with no scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHost + " [requested with host and scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHostNoScheme + " [requested with host, no scheme]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + HttpUtility.UrlDecode(requestedPathNoScheme) + " [requested and decoded]"); + result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlLowerCase + " [requested raw Url]"); + } + } + } + } + } + + if (result.RedirectAllowed && settings.RedirectWrongCase) + { + // check for redirects where a redirectToSubDomain is specified, + // redirect for Wrong case is specified, and there is a valid tab and it's not already redirected somewhere else + bool doRedirect = false; + string redirectPath = redirected ? result.FinalUrl : requestUri.AbsoluteUri; + string redirectPathOnly = redirectPath; + if (redirectPathOnly.Contains("?")) + { + redirectPathOnly = redirectPathOnly.Substring(0, redirectPathOnly.IndexOf("?", StringComparison.Ordinal)); + } + + // Thanks Etienne for the fix for Diacritic Characters Terminal Loop! + // if the url contains url encoded characters, they appear here uppercase -> %C3%83%C2 + // decode the url to get back the original character and do proper casing comparison + string urlDecodedRedirectPath = HttpUtility.UrlDecode(redirectPathOnly); + + // check for wrong case redirection + if (urlDecodedRedirectPath != null && (settings.RedirectWrongCase && string.CompareOrdinal(urlDecodedRedirectPath, urlDecodedRedirectPath.ToLowerInvariant()) != 0)) + { + TabInfo tab; + bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); + + if (allowRedirect && !string.IsNullOrEmpty(settings.ForceLowerCaseRegex)) + { + // don't allow redirect if excluded from redirecting in the force lower case regex pattern (606) + allowRedirect = !Regex.IsMatch(redirectPath, settings.ForceLowerCaseRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + if (allowRedirect) + { + // special case : when IIS automatically places /default.aspx on the end of the string, + // then don't try and redirect to the lower case /default.aspx, just let it through. + // we don't know whether IIS appended /Default.aspx on the end, however, we can guess + // if the redirectDefault.aspx is turned on (511) + if (settings.RedirectDefaultPage == false && redirectPathOnly.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // ignore this, because it's just a redirect of the /Default.aspx to /default.aspx + } + else + { + redirectPath = redirectPath.Replace(redirectPathOnly, redirectPathOnly.ToLowerInvariant()); + doRedirect = true; + result.Reason = RedirectReason.Not_Lower_Case; + } + } + } + + if (doRedirect) + { + result.Action = ActionType.Redirect301; + result.FinalUrl = CheckForSiteRootRedirect(result.PortalAlias.HTTPAlias, redirectPath); + redirected = true; + } + } + } + + return redirected; + } + + private static string StripDebugParameter(string url) + { + return AumDebugRegex.Replace(url, string.Empty); + } + + private static bool CheckFor301RedirectExclusion(int tabId, int portalId, bool checkBaseUrls, out TabInfo tab, FriendlyUrlSettings settings) + { + bool doRedirect = false; + tab = TabController.Instance.GetTab(tabId, portalId, false); + + // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab + if (tab != null && tab.IsSuperTab == false && !tab.DoNotRedirect) + { + if (checkBaseUrls) + { + // no redirect for friendly url purposes if the tab is in the 'base friendly urls' section + doRedirect = !RewriteController.IsExcludedFromFriendlyUrls(tab, settings, true); + } + else + { + doRedirect = true; + } + } + + return doRedirect; + } + + private PortalAliasInfo GetPortalAlias(FriendlyUrlSettings settings, string requestUrl, out bool redirectAlias, out bool isPrimaryAlias, out string wrongAlias) + { + PortalAliasInfo aliasInfo = null; + redirectAlias = false; + wrongAlias = null; + isPrimaryAlias = false; + OrderedDictionary portalAliases = TabIndexController.GetPortalAliases(settings); + foreach (string alias in portalAliases.Keys) + { + var urlToMatch = requestUrl; + + // in fact, requested url should contain alias + // for better performance, need to check whether we want to proceed with a whole url matching or not + // if alias is not a part of url -> let's proceed to the next iteration + var aliasIndex = urlToMatch.IndexOf(alias, StringComparison.InvariantCultureIgnoreCase); + if (aliasIndex < 0) + { + continue; + } + else + { + // we do not accept URL if the first occurence of alias is presented somewhere in the query string + var queryIndex = urlToMatch.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); + if (queryIndex >= 0 && queryIndex < aliasIndex) + { + // alias is in the query string, go to the next alias + continue; + } + + // we are fine here, lets prepare URL to be validated in regex + urlToMatch = urlToMatch.ReplaceIgnoreCase(alias, "_ALIAS_"); + } + + // check whether requested URL has the right URL format containing existing alias + // i.e. url is http://dnndev.me/site1/query?string=test, alias is dnndev.me/site1 + // in the below expression we will validate following value http://_ALIAS_/query?string=test + var aliasMatch = AliasUrlRegex.Match(urlToMatch); + if (aliasMatch.Success) + { + // check for mobile browser and matching + var aliasEx = (PortalAliasInfo)portalAliases[alias]; + redirectAlias = aliasEx.Redirect; + if (redirectAlias) + { + wrongAlias = alias; + } + + isPrimaryAlias = aliasEx.IsPrimary; + aliasInfo = aliasEx; + break; + } + } + + return aliasInfo; + } + + private void ProcessRequest( + HttpContext context, + Uri requestUri, + bool useFriendlyUrls, + UrlAction result, + FriendlyUrlSettings settings, + bool allowSettingsChange, + Guid parentTraceId) + { + bool finished = false; + bool showDebug = false; + bool postRequest = false; + + HttpRequest request = context.Request; + HttpResponse response = context.Response; + string requestType = request.RequestType; + NameValueCollection queryStringCol = request.QueryString; + + try + { + string fullUrl, querystring; + + // 699: get the full url based on the request and the quersytring, rather than the requestUri.ToString() + // there is a difference in encoding, which can corrupt results when an encoded value is in the querystring + RewriteController.GetUrlWithQuerystring(request, requestUri, out fullUrl, out querystring); + + showDebug = CheckForDebug(request, queryStringCol, settings.AllowDebugCode); + string ignoreRegex = settings.IgnoreRegex; + bool ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); + bool redirectAlias = false; + if (!ignoreRequest) + { + // set original path + context.Items["UrlRewrite:OriginalUrl"] = requestUri.AbsoluteUri; + + // set the path of the result object, and determine if a redirect is allowed on this request + result.SetOriginalPath(requestUri.ToString(), settings); + + // 737 : set the mobile browser + result.SetBrowserType(request, response, settings); + + // add to context + context.Items["UrlRewrite:BrowserType"] = result.BrowserType.ToString(); + + // 839 : split out this check + result.SetRedirectAllowed(result.OriginalPath, settings); + + // find the portal alias first + string wrongAlias; + bool isPrimaryAlias; + var requestedAlias = this.GetPortalAlias(settings, fullUrl, out redirectAlias, out isPrimaryAlias, out wrongAlias); + + if (requestedAlias != null) + { + // 827 : now get the correct settings for this portal (if not a test request) + // 839 : separate out redirect check as well and move above first redirect test (ConfigurePortalAliasRedirect) + if (allowSettingsChange) + { + settings = new FriendlyUrlSettings(requestedAlias.PortalID); + result.SetRedirectAllowed(result.OriginalPath, settings); + } + + result.PortalAlias = requestedAlias; + result.PrimaryAlias = requestedAlias; // this is the primary alias + result.PortalId = requestedAlias.PortalID; + result.CultureCode = requestedAlias.CultureCode; + + // get the portal alias mapping for this portal + result.PortalAliasMapping = PortalSettingsController.Instance().GetPortalAliasMappingMode(requestedAlias.PortalID); + + // if requested alias wasn't the primary, we have a replacement, redirects are allowed and the portal alias mapping mode is redirect + // then do a redirect based on the wrong portal + if ((redirectAlias && wrongAlias != null) && result.RedirectAllowed && result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) + { + // this is the alias, we are going to enforce it as the primary alias + result.PortalAlias = requestedAlias; + result.PrimaryAlias = requestedAlias; + + // going to redirect this alias because it is incorrect + // or do we just want to mark as 'check for 301??' + redirectAlias = ConfigurePortalAliasRedirect( + ref result, + wrongAlias, + requestedAlias.HTTPAlias, + false, + settings.InternalAliasList, + settings); + } + else + { + // do not redirect the wrong alias, but set the primary alias value + if (wrongAlias != null) + { + // get the portal alias info for the requested alias (which is the wrong one) + // and set that as the alias, but also set the found alias as the primary + PortalAliasInfo wrongAliasInfo = PortalAliasController.Instance.GetPortalAlias(wrongAlias); + if (wrongAliasInfo != null) + { + result.PortalAlias = wrongAliasInfo; + result.PrimaryAlias = requestedAlias; + } + } + } + } + } + + ignoreRegex = settings.IgnoreRegex; + ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); + if (!ignoreRequest) + { + // check to see if a post request + if (request.RequestType == "POST") + { + postRequest = true; + } + + // check the portal alias again. This time, in more depth now that the portal Id is known + // this check handles browser types/language specific aliases & mobile aliases + string primaryHttpAlias; + if (!redirectAlias && this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryHttpAlias)) + { + // it was an incorrect alias + PortalAliasInfo primaryAlias = PortalAliasController.Instance.GetPortalAlias(primaryHttpAlias); + if (primaryAlias != null) + { + result.PrimaryAlias = primaryAlias; + } + + // try and redirect the alias if the settings allow it + redirectAlias = RedirectPortalAlias(primaryHttpAlias, ref result, settings); + } + + if (redirectAlias) + { + // not correct alias for portal : will be redirected + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + + if (!finished) + { + // Check to see if this to be rewritten into default.aspx?tabId=nn format + // this call is the main rewriting matching call. It makes the decision on whether it is a + // physical file, whether it is toe be rewritten or redirected by way of a stored rule + + // Check if we have a standard url + var uri = new Uri(fullUrl); + if (uri.PathAndQuery.StartsWith("/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + result.DoRewrite = true; + result.Action = ActionType.CheckFor301; + result.RewritePath = Globals.glbDefaultPage + uri.Query; + } + else + { + bool isPhysicalResource; + CheckForRewrite(fullUrl, querystring, result, useFriendlyUrls, queryStringCol, settings, out isPhysicalResource, parentTraceId); + } + + // return 404 if there is no portal alias for a rewritten request + if (result.DoRewrite && result.PortalAlias == null) + { + // 882 : move this logic in from where it was before to here + // so that non-rewritten requests don't trip over it + // no portal alias found for the request : that's a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.No_Portal_Alias; + + Handle404OrException(settings, context, null, result, false, showDebug); + finished = true; // cannot fulfil request unless correct portal alias specified + } + } + + // now we may know the TabId. If the current alias is not the same as the primary alias, + // we should check if the current alias is indeed a valid custom alias for the current tab. + if (result.TabId > 0 && result.HttpAlias != result.PrimaryAlias.HTTPAlias && !CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) + { + // it was an incorrect alias + // try and redirect the alias if the settings allow it + if (RedirectPortalAlias(result.PrimaryAlias.HTTPAlias, ref result, settings)) + { + // not correct alias for tab : will be redirected + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + } + + if (!finished && result.DoRewrite) + { + // check the identified portal alias details for any extra rewrite information required + // this includes the culture and the skin, which can be placed into the rewrite path + // This logic is here because it will catch any Urls which are excluded from rewriting + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + + if (result.PortalId > -1 && result.HttpAlias != null) + { + string culture; + string skin; + BrowserTypes browserType; + primaryAliases.GetSettingsByPortalIdAndAlias( + result.PortalId, + result.HttpAlias, + out culture, + out browserType, + out skin); + + // add language code to path if it exists (not null) and if it's not already there + string rewritePath = result.RewritePath; + if (RewriteController.AddLanguageCodeToRewritePath(ref rewritePath, culture)) + { + result.CultureCode = culture; + } + + // 852: add skinSrc to path if it exists and if it's not already there + string debugMessage; + RewriteController.AddSkinToRewritePath(result.TabId, result.PortalId, ref rewritePath, skin, out debugMessage); + result.RewritePath = rewritePath; // reset back from ref temp var + if (debugMessage != null) + { + result.DebugMessages.Add(debugMessage); + } + } + } + + if (!finished && result.DoRewrite) + { + // if so, do the rewrite + if (result.RewritePath.StartsWith(result.Scheme) || result.RewritePath.StartsWith(Globals.glbDefaultPage) == false) + { + if (result.RewritePath.Contains(Globals.glbDefaultPage) == false) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + else + { + // if there is no TabId and we have the domain + if (!result.RewritePath.ToLowerInvariant().Contains("tabId=")) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + else + { + RewriterUtils.RewriteUrl(context, result.RewritePath); + } + } + } + else + { + if (IsMvc(result, queryStringCol, context, result.TabId, result.PortalId)) + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath.Replace(Globals.glbDefaultPage, "DesktopModules/Default/Page/" + result.TabId + "/" + result.CultureCode)); + } + else + { + RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); + } + } + } + + // confirm which portal the request is for + if (!finished) + { + this.IdentifyPortalAlias(context, request, requestUri, result, queryStringCol, settings, parentTraceId); + if (result.Action == ActionType.Redirect302Now) + { + // performs a 302 redirect if requested + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl, false); + finished = true; + } + else + { + if (result.Action == ActionType.Redirect301 && !string.IsNullOrEmpty(result.FinalUrl)) + { + finished = true; + + // perform a 301 redirect if one has already been found + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + } + } + } + + if (!finished) + { + // check to see if this tab has an external url that should be forwared or not + finished = CheckForTabExternalForwardOrRedirect(context, ref result, response, settings, parentTraceId); + } + + // check for a parameter redirect (we had to do all the previous processing to know we are on the right portal and identify the tabid) + // if the CustomParmRewrite flag is set, it means we already rewrote these parameters, so they have to be correct, and aren't subject to + // redirection. The only reason to do a custom parm rewrite is to interpret already-friendly parameters + if (!finished + && !postRequest /* either request is null, or it's not a post - 551 */ + && result.HttpAlias != null /* must have a http alias */ + && !result.CustomParmRewrite && /* not custom rewritten parms */ + ((settings.EnableCustomProviders && + RedirectController.CheckForModuleProviderRedirect(requestUri, ref result, queryStringCol, settings, parentTraceId)) + + // 894 : allow disable of all custom providers + || + RedirectController.CheckForParameterRedirect(requestUri, ref result, queryStringCol, settings))) + { + // 301 redirect to new location based on parameter match + if (response != null) + { + switch (result.Action) + { + case ActionType.Redirect301: + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + break; + case ActionType.Redirect302: + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl); + break; + case ActionType.Output404: + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + Handle404OrException(settings, context, null, result, true, showDebug); + break; + } + } + + finished = true; + } + + // shifted until after the 301 redirect code to allow redirects to be checked for pages which have no rewrite value + // look for a 404 result from the rewrite, because of a deleted page or rule + if (!finished && result.Action == ActionType.Output404) + { + if (result.OriginalPath.Equals(result.HttpAlias, StringComparison.InvariantCultureIgnoreCase) + && result.PortalAlias != null + && result.Reason != RedirectReason.Deleted_Page + && result.Reason != RedirectReason.Disabled_Page) + { + // Request for domain with no page identified (and no home page set in Site Settings) + result.Action = ActionType.Continue; + } + else + { + finished = true; + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, null); + } + + // show the 404 page if configured + result.Reason = RedirectReason.Requested_404; + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + + if (!finished) + { + // add the portal settings to the app context if the portal alias has been found and is correct + if (result.PortalId != -1 && result.PortalAlias != null) + { + // for invalid tab id other than -1, show the 404 page + TabInfo tabInfo = TabController.Instance.GetTab(result.TabId, result.PortalId, false); + if (tabInfo == null && result.TabId > -1) + { + finished = true; + + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, null); + } + + // show the 404 page if configured + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Requested_404; + response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); + Handle404OrException(settings, context, null, result, true, showDebug); + } + else + { + Globals.SetApplicationName(result.PortalId); + + // load the PortalSettings into current context + var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); + + // set the primary alias if one was specified + if (result.PrimaryAlias != null) + { + portalSettings.PrimaryAlias = result.PrimaryAlias; + } + + if (result.CultureCode != null && fullUrl.Contains(result.CultureCode) && + portalSettings.DefaultLanguage == result.CultureCode) + { + // when the request culture code is the same as the portal default, check for a 301 redirect, because we try and remove the language from the url where possible + result.Action = ActionType.CheckFor301; + } + + int portalHomeTabId = portalSettings.HomeTabId; + if (context != null && portalSettings != null && !context.Items.Contains("PortalSettings")) + { + context.Items.Add("PortalSettings", portalSettings); + + // load PortalSettings and HostSettings dictionaries into current context + // specifically for use in DotNetNuke.Web.Client, which can't reference DotNetNuke.dll to get settings the normal way + context.Items.Add("PortalSettingsDictionary", PortalController.Instance.GetPortalSettings(portalSettings.PortalId)); + context.Items.Add("HostSettingsDictionary", HostController.Instance.GetSettingsDictionary()); + } + + // check if a secure redirection is needed + // this would be done earlier in the piece, but need to know the portal settings, tabid etc before processing it + bool redirectSecure = this.CheckForSecureRedirect(portalSettings, requestUri, result, queryStringCol, settings); + if (redirectSecure) + { + if (response != null) + { + // 702 : don't check final url until checked for null reference first + if (result.FinalUrl != null) + { + if (result.FinalUrl.StartsWith("https://")) + { + if (showDebug) + { + /* + string debugMsg = "{0}, {1}, {2}, {3}, {4}"; + string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); + response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); + */ + ShowDebugData(context, fullUrl, result, null); + } + + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + finished = true; + } + else + { + if (settings.SSLClientRedirect) + { + // redirect back to http version, use client redirect + response.Clear(); + + // add a refresh header to the response + response.AddHeader("Refresh", "0;URL=" + result.FinalUrl); + + // add the clientside javascript redirection script + var finalUrl = HttpUtility.HtmlEncode(result.FinalUrl); + response.Write(""); + response.Write(@""); + response.Write(""); + if (showDebug) + { + /* + string debugMsg = "{0}, {1}, {2}, {3}, {4}"; + string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); + response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); + */ + ShowDebugData(context, fullUrl, result, null); + } + + // send the response + // 891 : reinstate the response.end to stop the entire page loading + response.End(); + finished = true; + } + else + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl); + finished = true; + } + } + } + } + } + else + { + // check for, and do a 301 redirect if required + if (CheckForRedirects(requestUri, fullUrl, queryStringCol, result, requestType, settings, portalHomeTabId)) + { + if (response != null) + { + if (result.Action == ActionType.Redirect301) + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.RedirectPermanent(result.FinalUrl, false); + finished = true; + } + else if (result.Action == ActionType.Redirect302) + { + response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); + response.Redirect(result.FinalUrl, false); + finished = true; + } + } + } + else + { + // 612 : Don't clear out a 302 redirect if set + if (result.Action != ActionType.Redirect302 && + result.Action != ActionType.Redirect302Now) + { + result.Reason = RedirectReason.Not_Redirected; + result.FinalUrl = null; + } + } + } + } + } + else + { + // alias does not exist in database + // and all attempts to find another have failed + // this should only happen if the HostPortal does not have any aliases + result.Action = ActionType.Output404; + if (response != null) + { + if (showDebug) + { + ShowDebugData(context, fullUrl, result, null); + } + + result.Reason = RedirectReason.Requested_404; + + // 912 : change 404 type to transfer to allow transfer to main portal in single-portal installs + Handle404OrException(settings, context, null, result, true, showDebug); + finished = true; + } + } + } + + // 404 page ?? + if (settings.TabId404 > 0 && settings.TabId404 == result.TabId) + { + string status = queryStringCol["status"]; + if (status == "404") + { + // respond with a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Requested_404_In_Url; + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + else + { + if (result.DoRewrite == false && result.CanRewrite != StateBoolean.False && !finished && + result.Action == ActionType.Continue) + { + // 739 : catch no-extension 404 errors + string pathWithNoQs = result.OriginalPath; + if (pathWithNoQs.Contains("?")) + { + pathWithNoQs = pathWithNoQs.Substring(0, pathWithNoQs.IndexOf("?", StringComparison.Ordinal)); + } + + if (!pathWithNoQs.Substring(pathWithNoQs.Length - 5, 5).Contains(".")) + { + // no page extension, output a 404 if the Url is not found + // 766 : check for physical path before passing off as a 404 error + // 829 : change to use action physical path + // 893 : filter by regex pattern to exclude urls which are valid, but show up as extensionless + if ((request != null && Directory.Exists(result.PhysicalPath)) + || + Regex.IsMatch(pathWithNoQs, settings.ValidExtensionlessUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) + { + // do nothing : it's a request for a valid physical path, maybe including a default document + result.VirtualPath = StateBoolean.False; + } + else + { + if (!Globals.ServicesFrameworkRegex.IsMatch(context.Request.RawUrl)) + { + // no physical path, intercept the request and hand out a 404 error + result.Action = ActionType.Output404; + result.Reason = RedirectReason.Page_404; + result.VirtualPath = StateBoolean.True; + + // add in a message to explain this 404, becaue it can be cryptic + result.DebugMessages.Add("404 Reason : Not found and no extension"); + Handle404OrException(settings, context, null, result, true, showDebug); + } + } + } + } + } + + // show debug messages after extensionless-url special 404 handling + if (showDebug) + { + ShowDebugData(context, fullUrl, result, null); + } + } + } + catch (ThreadAbortException) + { + // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest + // level try/catch block, so we handle it here. + Thread.ResetAbort(); + } + catch (Exception ex) + { + if (showDebug) + { + Services.Exceptions.Exceptions.LogException(ex); + } + + if (response != null) + { + if (showDebug) + { + ShowDebugData(context, requestUri.AbsoluteUri, result, ex); + } + + if (result != null) + { + result.Ex = ex; + result.Reason = RedirectReason.Exception; + } + + Handle404OrException(settings, context, ex, result, false, showDebug); + } + else + { + if (result != null && result.DebugMessages != null) + { + result.DebugMessages.Add("Exception: " + ex.Message); + result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); + } + + throw; + } + } + finally + { + // 809 : add in new code copied from urlRewrite class in standard Url Rewrite module + if (context != null && context.Items["FirstRequest"] != null) + { + context.Items.Remove("FirstRequest"); + + // process any messages in the eventQueue for the Application_Start_FIrstRequest event + EventQueueController.ProcessMessages("Application_Start_FirstRequest"); + } + } + } + + private bool CheckForSecureRedirect( + PortalSettings portalSettings, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings) + { + bool redirectSecure = false; + string url = requestUri.ToString(); + + // 889 : don't run secure redirect code for physical resources or requests that aren't a rewritten Url + if (result.IsPhysicalResource == false && result.TabId >= 0) + { + // no secure redirection for physical resources, only tab-specific requests can be redirected for ssl connections + if (portalSettings.ActiveTab != null) + { + result.DebugMessages.Add("ActiveTab: " + portalSettings.ActiveTab.TabID.ToString() + "/" + + portalSettings.ActiveTab.TabName + " IsSecure: " + + portalSettings.ActiveTab.IsSecure.ToString()); + + switch (portalSettings.SSLSetup) + { + case Abstractions.Security.SiteSslSetup.On: + if (!result.IsSecureConnection) + { + redirectSecure = true; + url = url.Replace("http://", "https://"); + } + + break; + case Abstractions.Security.SiteSslSetup.Advanced: + // 717 : check page is secure, connection is not secure + // 952 : support SSl Offloading in DNN 6.2+ + if (portalSettings.ActiveTab.IsSecure && !result.IsSecureConnection && !result.IsSSLOffloaded) + { + redirectSecure = true; + string stdUrl = portalSettings.STDURL; + string sslUrl = portalSettings.SSLURL; + if (string.IsNullOrEmpty(result.HttpAlias) == false) + { + stdUrl = result.HttpAlias; + } + + url = url.Replace("http://", "https://"); + url = this.ReplaceDomainName(url, stdUrl, sslUrl); + } + + if (portalSettings.SSLEnforced) + { + // Prevent browser's mixed-content error in case we open a secure PopUp or a secure iframe + // from an unsecure page + if (!portalSettings.ActiveTab.IsSecure && + result.IsSecureConnection && + !UrlUtils.IsPopUp(url)) + { + // has connection already been forced to secure? + if (queryStringCol["ssl"] == null) + { + // no? well this page shouldn't be secure + string stdUrl = portalSettings.STDURL; + string sslUrl = portalSettings.SSLURL; + url = url.Replace("https://", "http://"); + url = this.ReplaceDomainName(url, sslUrl, stdUrl); + redirectSecure = true; + } + } + } + + break; + } + } + + if (redirectSecure) + { + // now check to see if excluded. Why now? because less requests are made to redirect secure, + // so we don't have to check the exclusion as often. + bool exclude = false; + string doNotRedirectSecureRegex = settings.DoNotRedirectSecureRegex; + if (!string.IsNullOrEmpty(doNotRedirectSecureRegex)) + { + // match the raw url + exclude = Regex.IsMatch(result.RawUrl, doNotRedirectSecureRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + if (!exclude) + { + result.Action = ActionType.Redirect302Now; + result.Reason = RedirectReason.Secure_Page_Requested; + + // 760 : get the culture specific home page tabid for a redirect comparison + int homePageTabId = portalSettings.HomeTabId; + homePageTabId = TabPathHelper.GetHomePageTabIdForCulture( + portalSettings.DefaultLanguage, + portalSettings.PortalId, + result.CultureCode, + homePageTabId); + if (result.TabId == homePageTabId) + { + // replace the /default.aspx in the Url if it was found + url = DefaultPageRegex.Replace(url, "/"); + } + + result.FinalUrl = url; + } + else + { + // 702 : change return value if exclusion has occured + redirectSecure = false; + } + } + } + + return redirectSecure; + } + + private string ReplaceDomainName(string url, string replaceDomain, string withDomain) + { + if (replaceDomain != string.Empty && withDomain != string.Empty) + { + // 951 : change find/replace routine to regex for more accurate replacement + // (previous method gives false positives if the SSL Url is contained within the STD url) + string find = @"(?<=https?://)" + Regex.Escape(withDomain); + if (Regex.IsMatch(url, find, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) == false) + { + string replaceFind = @"(?<=https?://)" + Regex.Escape(replaceDomain); + url = Regex.Replace(url, replaceFind, withDomain, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + } + + return url; + } + + private void IdentifyPortalAlias( + HttpContext context, + HttpRequest request, + Uri requestUri, + UrlAction result, + NameValueCollection queryStringCol, + FriendlyUrlSettings settings, + Guid parentTraceId) + { + // get the domain name of the request, if it isn't already supplied + if (request != null && string.IsNullOrEmpty(result.DomainName)) + { + result.DomainName = Globals.GetDomainName(request); // parse the domain name out of the request + } + + // get tabId from querystring ( this is mandatory for maintaining portal context for child portals ) + if (queryStringCol["tabid"] != null) + { + string raw = queryStringCol["tabid"]; + int tabId; + if (int.TryParse(raw, out tabId)) + { + result.TabId = tabId; + } + else + { + // couldn't parse tab id + // split in two? + string[] tabids = raw.Split(','); + if (tabids.GetUpperBound(0) > 0) + { + // hmm more than one tabid + if (int.TryParse(tabids[0], out tabId)) + { + result.TabId = tabId; + + // but we want to warn against this! + var ex = + new Exception( + "Illegal request exception : Two TabId parameters provided in a single request: " + + requestUri); + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + + result.Ex = ex; + } + else + { + // yeah, nothing, divert to 404 + result.Action = ActionType.Output404; + var ex = + new Exception( + "Illegal request exception : TabId parameters in query string, but invalid TabId requested : " + + requestUri); + UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); + result.Ex = ex; + } + } + } + } + + // get PortalId from querystring ( this is used for host menu options as well as child portal navigation ) + if (queryStringCol["portalid"] != null) + { + string raw = queryStringCol["portalid"]; + int portalId; + if (int.TryParse(raw, out portalId)) + { + // 848 : if portal already found is different to portal id in querystring, then load up different alias + // this is so the portal settings will be loaded correctly. + if (result.PortalId != portalId) + { + // portal id different to what we expected + result.PortalId = portalId; + + // check the loaded portal alias, because it might be wrong + if (result.PortalAlias != null && result.PortalAlias.PortalID != portalId) + { + // yes, the identified portal alias is wrong. Find the correct alias for this portal + PortalAliasInfo pa = TabIndexController.GetPortalAliasByPortal(portalId, result.DomainName); + if (pa != null) + { + // note: sets portal id and portal alias + result.PortalAlias = pa; + } + } + } + } + } + else + { + // check for a portal alias if there's no portal Id in the query string + // check for absence of captcha value, because the captcha string re-uses the alias querystring value + if (queryStringCol["alias"] != null && queryStringCol["captcha"] == null) + { + string alias = queryStringCol["alias"]; + PortalAliasInfo portalAlias = PortalAliasController.Instance.GetPortalAlias(alias); + if (portalAlias != null) + { + // ok the portal alias was found by the alias name + // check if the alias contains the domain name + if (alias.Contains(result.DomainName) == false) + { + // replaced to the domain defined in the alias + if (request != null) + { + string redirectDomain = Globals.GetPortalDomainName(alias, request, true); + + // retVal.Url = redirectDomain; + result.FinalUrl = redirectDomain; + result.Action = ActionType.Redirect302Now; + result.Reason = RedirectReason.Alias_In_Url; + } + } + else + { + // the alias is the same as the current domain + result.HttpAlias = portalAlias.HTTPAlias; + result.PortalAlias = portalAlias; + result.PortalId = portalAlias.PortalID; + + // don't use this crap though - we don't want ?alias=portalAlias in our Url + if (result.RedirectAllowed) + { + string redirect = requestUri.Scheme + Uri.SchemeDelimiter + result.PortalAlias.HTTPAlias + + "/"; + result.Action = ActionType.Redirect301; + result.FinalUrl = redirect; + result.Reason = RedirectReason.Unfriendly_Url_Child_Portal; + } + } + } + } + } + + // first try and identify the portal using the tabId, but only if we identified this tab by looking up the tabid + // from the original url + // 668 : error in child portal redirects to child portal home page because of mismatch in tab/domain name + if (result.TabId != -1 && result.FriendlyRewrite == false) + { + // get the alias from the tabid, but only if it is for a tab in that domain + // 2.0 : change to compare retrieved alias to the already-set httpAlias + string httpAliasFromTab = PortalAliasController.GetPortalAliasByTab(result.TabId, result.DomainName); + if (httpAliasFromTab != null) + { + // 882 : account for situation when portalAlias is null. + if ((result.PortalAlias != null && string.Compare(result.PortalAlias.HTTPAlias, httpAliasFromTab, StringComparison.OrdinalIgnoreCase) != 0) + || result.PortalAlias == null) + { + // 691 : change logic to force change in portal alias context rather than force back. + // This is because the tabid in the query string should take precedence over the portal alias + // to handle parent.com/default.aspx?tabid=xx where xx lives in parent.com/child/ + var tab = TabController.Instance.GetTab(result.TabId, Null.NullInteger, false); + + // when result alias is null or result alias is different from tab-identified portalAlias + if (tab != null && (result.PortalAlias == null || tab.PortalID != result.PortalAlias.PortalID)) + { + // the tabid is different to the identified portalid from the original alias identified + // so get a new alias + PortalAliasInfo tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(httpAliasFromTab, tab.PortalID); + if (tabPortalAlias != null) + { + result.PortalId = tabPortalAlias.PortalID; + result.PortalAlias = tabPortalAlias; + result.Action = ActionType.CheckFor301; + result.Reason = RedirectReason.Wrong_Portal; + } + } + } + } + } + + // if no alias, try and set by using the identified http alias or domain name + if (result.PortalAlias == null) + { + if (!string.IsNullOrEmpty(result.HttpAlias)) + { + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias); + } + else + { + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); + if (result.PortalAlias == null && result.DomainName.EndsWith("/")) + { + result.DomainName = result.DomainName.TrimEnd('/'); + result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); + } + } + } + + if (result.PortalId == -1) + { + if (!requestUri.LocalPath.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) + { + // allows requests for aspx pages in custom folder locations to be processed + return; + } + + // the domain name was not found so try using the host portal's first alias + if (Host.HostPortalID != -1) + { + result.PortalId = Host.HostPortalID; + + // use the host portal, but replaced to the host portal home page + var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (aliases.Count > 0) + { + string alias = null; + + // get the alias as the chosen portal alias for the host portal based on the result culture code + var cpa = aliases.GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, settings); + if (cpa != null) + { + alias = cpa.HTTPAlias; + } + + if (alias != null) + { + result.Action = ActionType.Redirect301; + result.Reason = RedirectReason.Host_Portal_Used; + string destUrl = MakeUrlWithAlias(requestUri, alias); + destUrl = CheckForSiteRootRedirect(alias, destUrl); + result.FinalUrl = destUrl; + } + else + { + // Get the first Alias for the host portal + result.PortalAlias = aliases[result.PortalId]; + string url = MakeUrlWithAlias(requestUri, result.PortalAlias); + if (result.TabId != -1) + { + url += requestUri.Query; + } + + result.FinalUrl = url; + result.Reason = RedirectReason.Host_Portal_Used; + result.Action = ActionType.Redirect302Now; + } + } + } + } + + // double check to make sure we still have the correct alias now that all other information is known (ie tab, portal, culture) + // 770 : redirect alias based on tab id when custom alias used + if (result.TabId == -1 && result.Action == ActionType.CheckFor301 && + result.Reason == RedirectReason.Custom_Tab_Alias) + { + // here because the portal alias matched, but no tab was found, and because there are custom tab aliases used for this portal + // need to redirect back to the chosen portal alias and keep the current path. + string wrongAlias = result.HttpAlias; // it's a valid alias, but only for certain tabs + var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); + if (primaryAliases != null && result.PortalId > -1) + { + // going to look for the correct alias based on the culture of the request + string requestCultureCode = result.CultureCode; + + // if that didn't work use the default language of the portal + if (requestCultureCode == null) + { + // this might end up in a double redirect if the path of the Url is for a specific language as opposed + // to a path belonging to the default language domain + PortalInfo portal = PortalController.Instance.GetPortal(result.PortalId); + if (portal != null) + { + requestCultureCode = portal.DefaultLanguage; + } + } + + // now that the culture code is known, look up the correct portal alias for this portalid/culture code + var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, requestCultureCode, settings); + if (cpa != null) + { + // if an alias was found that matches the request and the culture code, then run with that + string rightAlias = cpa.HTTPAlias; + + // will cause a redirect to the primary portal alias - we know now that there was no custom alias tab + // found, so it's just a plain wrong alias + ConfigurePortalAliasRedirect(ref result, wrongAlias, rightAlias, true, settings.InternalAliasList, settings); + } + } + } + else + { + // then check to make sure it's the chosen portal alias for this portal + // 627 : don't do it if we're redirecting to the host portal + if (result.RedirectAllowed && result.Reason != RedirectReason.Host_Portal_Used) + { + string primaryAlias; + + // checking again in case the rewriting operation changed the values for the valid portal alias + bool incorrectAlias = this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryAlias); + if (incorrectAlias) + { + RedirectPortalAlias(primaryAlias, ref result, settings); + } + } + } + + // check to see if we have to avoid the core 302 redirect for the portal alias that is in the /defualt.aspx + // for child portals + // exception to that is when a custom alias is used but no rewrite has taken place + if (result.DoRewrite == false && (result.Action == ActionType.Continue + || + (result.Action == ActionType.CheckFor301 && + result.Reason == RedirectReason.Custom_Tab_Alias))) + { + string aliasQuerystring; + bool isChildAliasRootUrl = CheckForChildPortalRootUrl(requestUri.AbsoluteUri, result, out aliasQuerystring); + if (isChildAliasRootUrl) + { + RewriteAsChildAliasRoot(context, result, aliasQuerystring, settings); + } + } + } + + private void SecurityCheck(HttpApplication app) + { + HttpRequest request = app.Request; + HttpServerUtility server = app.Server; + + // 675 : unnecessarily strict url validation + // URL validation + // check for ".." escape characters commonly used by hackers to traverse the folder tree on the server + // the application should always use the exact relative location of the resource it is requesting + var strURL = request.Url.AbsolutePath; + var strDoubleDecodeURL = server.UrlDecode(server.UrlDecode(request.Url.AbsolutePath)) ?? string.Empty; + if (UrlSlashesRegex.Match(strURL).Success || UrlSlashesRegex.Match(strDoubleDecodeURL).Success) + { + throw new HttpException(404, "Not Found"); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs index ed5a9a159fa..989f6da77e8 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/JavascriptLibraries/MvcJavaScript.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Web; using System.Web.Mvc; - using System.Web.UI; using DotNetNuke.Common; using DotNetNuke.Common.Utilities; @@ -15,14 +14,13 @@ using DotNetNuke.Entities.Portals; using DotNetNuke.Entities.Users; using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Framework; - using DotNetNuke.Mvc; using DotNetNuke.Services.Installer.Packages; using DotNetNuke.Services.Localization; using DotNetNuke.Services.Log.EventLog; using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client; using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; public class MvcJavaScript { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs index 087ec228eeb..e9df345f48e 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/MvcServicesFrameworkImpl.cs @@ -9,10 +9,10 @@ using DotNetNuke.Entities.Portals; using DotNetNuke.Framework; using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; internal class MvcServicesFrameworkImpl : IMvcServicesFramework, IMvcServiceFrameworkInternals { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs index 9c09b6d9c09..a4fb00ead9b 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/PageModelFactory.cs @@ -85,7 +85,7 @@ public PageModel CreatePageModel(DnnPageController page) if (UrlUtils.InPopUp()) { var strTitle = new StringBuilder(page.PortalSettings.PortalName); - var slaveModule = UI.UIUtilities.GetSlaveModule(page.PortalSettings.ActiveTab.TabID); + var slaveModule = DotNetNuke.UI.UIUtilities.GetSlaveModule(page.PortalSettings.ActiveTab.TabID); // Skip is popup is just a tab (no slave module) if (slaveModule.DesktopModuleID != Null.NullInteger) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs index 278bd48b961..76756fdd6bf 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs @@ -20,7 +20,6 @@ namespace DotNetNuke.Web.MvcPipeline.Framework using DotNetNuke.Entities.Tabs; using DotNetNuke.Entities.Tabs.TabVersions; using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; using DotNetNuke.Security.Permissions; using DotNetNuke.Services.Exceptions; using DotNetNuke.Services.FileSystem; @@ -31,11 +30,11 @@ namespace DotNetNuke.Web.MvcPipeline.Framework using DotNetNuke.UI.Skins; using DotNetNuke.UI.Skins.Controls; using DotNetNuke.Web.Client; - using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Controllers; using DotNetNuke.Web.MvcPipeline.Exceptions; using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; using DotNetNuke.Web.MvcPipeline.Models; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; using Microsoft.Extensions.DependencyInjection; public class SkinModelFactory : ISkinModelFactory @@ -584,7 +583,7 @@ private bool CheckExpired(PortalSettings portalSettings) return blnExpired; } - private void EnsureContentItemForTab(Entities.Tabs.TabInfo tabInfo) + private void EnsureContentItemForTab(TabInfo tabInfo) { // If tab exists but ContentItem not, then we create it if (tabInfo.ContentItemId == Null.NullInteger && tabInfo.TabID != Null.NullInteger) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs index 2febc2fa534..11c27acaafe 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/HtmlHelpers.cs @@ -5,24 +5,18 @@ namespace DotNetNuke.Web.MvcPipeline { using System; - using System.IO; using System.Web; using System.Web.Helpers; using System.Web.Mvc; using System.Web.Mvc.Html; - using DotNetNuke.Common; - using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Modules; using DotNetNuke.Framework; - using DotNetNuke.Framework.JavaScriptLibraries; // using DotNetNuke.Framework.Models; - using DotNetNuke.Mvc; - using DotNetNuke.UI.Modules; - using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Framework; using DotNetNuke.Web.MvcPipeline.Models; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; public static partial class HtmlHelpers { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs index 4c887dea842..f5e0d1f0bd5 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.DnnLabel.cs @@ -6,26 +6,13 @@ namespace DotNetNuke.Web.MvcPipeline.Modules { using System; using System.Collections.Generic; - using System.Collections.Specialized; - using System.Linq; using System.Linq.Expressions; - using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; - using System.Web.UI; - using DotNetNuke.Common; - using DotNetNuke.Common.Utilities; - using DotNetNuke.Entities.Host; - using DotNetNuke.Entities.Portals; - using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; - using DotNetNuke.Security.Roles; - using DotNetNuke.Services.FileSystem; using DotNetNuke.Services.Localization; - using DotNetNuke.Web.Client.ClientResourceManagement; - + public static partial class ModuleHelpers { public static IHtmlString DnnLabelFor(this HtmlHelper htmlHelper, Expression> expression, string resourceFile) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs index c21dda6b7a6..f78b1b5105f 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Modules/ModuleHelpers.TextEditor.cs @@ -6,25 +6,15 @@ namespace DotNetNuke.Web.MvcPipeline.Containers { using System; using System.Collections.Generic; - using System.Collections.Specialized; - using System.Linq; using System.Linq.Expressions; using System.Text; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; - using System.Web.UI; - // using DNNConnect.CKEditorProvider.Constants; - // using DNNConnect.CKEditorProvider.Utilities; using DotNetNuke.Common; - using DotNetNuke.Common.Utilities; - using DotNetNuke.Entities.Host; using DotNetNuke.Entities.Portals; using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; - using DotNetNuke.Security.Roles; - using DotNetNuke.Services.FileSystem; using DotNetNuke.Web.Client.ClientResourceManagement; public static partial class ModuleHelpers diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs index b208139056b..57d058d6e91 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/PermissionTriStateHelper.cs @@ -4,19 +4,14 @@ namespace DotNetNuke.MvcPipeline { - using System; - using System.Runtime.CompilerServices; using System.Web.Mvc; - using System.Web.UI; using DotNetNuke.Entities.Icons; - using DotNetNuke.Framework; using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; using DotNetNuke.Services.Localization; - using DotNetNuke.UI.Utilities; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; public static class PermissionTriStateHelper { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs index 50d9b128a10..45c0715ec06 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Security/Controllers/ModulePermissionsGridController.cs @@ -4,8 +4,6 @@ namespace DotNetNuke.Web.MvcPipeline.Security.Controllers { - using System; - using System.Collections; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; @@ -15,13 +13,12 @@ namespace DotNetNuke.Web.MvcPipeline.Security.Controllers using DotNetNuke.Entities.Modules; using DotNetNuke.Entities.Users; using DotNetNuke.Framework; - using DotNetNuke.Framework.JavaScriptLibraries; - using DotNetNuke.Mvc; using DotNetNuke.Security.Permissions; using DotNetNuke.Security.Roles; using DotNetNuke.Web.Client; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Security.Models; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; public class ModulePermissionsGridController : PermissionsGridController { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcClientAPI.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcClientAPI.cs new file mode 100644 index 00000000000..71d2e1cb7ee --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcClientAPI.cs @@ -0,0 +1,59 @@ +namespace DotNetNuke.Web.MvcPipeline.UI.Utilities +{ + using System; + using System.Collections.Generic; + using System.Web; + + public class MvcClientAPI + { + public static Dictionary GetClientVariableList() + { + var dic = HttpContext.Current.Items["CAPIVariableList"] as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + HttpContext.Current.Items["CAPIVariableList"] = dic; + } + + return dic; + } + + public static Dictionary GetClientStartupScriptList() + { + var dic = HttpContext.Current.Items["CAPIStartupScriptList"] as Dictionary; + if (dic == null) + { + dic = new Dictionary(); + HttpContext.Current.Items["CAPIStartupScriptList"] = dic; + } + + return dic; + } + + public static void RegisterClientVariable(string key, string value, bool overwrite) + { + GetClientVariableList().Add(key, value); + } + + public static void RegisterEmbeddedResource(string fileName, Type assemblyType) + { + // RegisterClientVariable(FileName + ".resx", ThePage.ClientScript.GetWebResourceUrl(AssemblyType, FileName), true); + } + + public static void RegisterStartupScript(string key, string value) + { + if (!GetClientStartupScriptList().ContainsKey(key)) + { + GetClientStartupScriptList().Add(key, value); + } + } + + public static void RegisterScript(string key, string value) + { + if (!GetClientStartupScriptList().ContainsKey(key)) + { + GetClientStartupScriptList().Add(key, value); + } + } + } +} diff --git a/DNN Platform/Library/Mvc/MvcUtils.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcUtils.cs similarity index 83% rename from DNN Platform/Library/Mvc/MvcUtils.cs rename to DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcUtils.cs index 290a6275319..75c9bbe867f 100644 --- a/DNN Platform/Library/Mvc/MvcUtils.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/UI/Utilities/MvcUtils.cs @@ -1,8 +1,4 @@ -// 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.Mvc +namespace DotNetNuke.Web.MvcPipeline.UI.Utilities { using System.IO; diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index d0e48f079de..1010d01e050 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -276,7 +276,6 @@ - @@ -308,8 +307,6 @@ - - diff --git a/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs b/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs deleted file mode 100644 index 9b6f3a9bb7d..00000000000 --- a/DNN Platform/Library/Entities/Urls/MvcAdvancedUrlRewriter.cs +++ /dev/null @@ -1,3224 +0,0 @@ -// 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.Web.MvcPipeline.Integration.Entities.Urls -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.CompilerServices; - using System.Security.Principal; - using System.Text.RegularExpressions; - using System.Threading; - using System.Web; - using System.Web.Configuration; - using System.Web.Security; - - using DotNetNuke.Application; - using DotNetNuke.Common; - using DotNetNuke.Common.Internal; - using DotNetNuke.Common.Utilities; - using DotNetNuke.Entities.Controllers; - using DotNetNuke.Entities.Host; - using DotNetNuke.Entities.Portals; - using DotNetNuke.Entities.Tabs; - using DotNetNuke.Entities.Urls; - using DotNetNuke.Framework; - using DotNetNuke.Services.EventQueue; - - public class MvcAdvancedUrlRewriter : UrlRewriterBase - { - private const string ProductName = "AdvancedUrlRewriter"; - private static readonly Regex DefaultPageRegex = new Regex(@"(?.[^&]+)=$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); - private static readonly Regex UrlSlashesRegex = new Regex("[\\\\/]\\.\\.[\\\\/]", RegexOptions.Compiled); - private static readonly Regex AliasUrlRegex = new Regex(@"(?:^(?http[s]{0,1}://){0,1})(?:(?_ALIAS_)(?$|\?[\w]*|/[\w]*))", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); - private FriendlyUrlSettings settings; - - public void ProcessTestRequestWithContext( - HttpContext context, - Uri requestUri, - bool useFriendlyUrls, - UrlAction result, - FriendlyUrlSettings settings) - { - Guid parentTraceId = Guid.Empty; - this.settings = settings; - this.ProcessRequest( - context, - requestUri, - useFriendlyUrls, - result, - settings, - false, - parentTraceId); - } - - internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) - { - bool mvcCtl = false; - var skinSrc = string.Empty; - - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; - if (string.IsNullOrEmpty(skinSrc)) - { - skinSrc = PortalSettings.Current.DefaultPortalSkin; - } - } - } - - if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) - { - var tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - skinSrc = tab.SkinSrc; - } - } - - if (!string.IsNullOrEmpty(skinSrc)) - { - mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); - } - - /* - var mvcCtls = new[] { "Module", "Terms", "Privacy" }; - bool mvcSkin = false; - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - mvcSkin = !string.IsNullOrEmpty(PortalSettings.Current.ActiveTab.SkinSrc) && - PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); - } - } - - if (result.RewritePath.Contains("&ctl=")) - { - foreach (var item in mvcCtls) - { - mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); - } - - if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) - { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) - { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - mvcCtl = tab.GetTags().Contains("mvc"); - } - } - - // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); - } - } - else - { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) - { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - mvcCtl = tab.GetTags().Contains("mvc"); - } - } - - // mvcCtl = result.RawUrl.EndsWith("mvc"); - } - - mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; - mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; - */ - - return mvcCtl; - } - - internal static void RewriteAsChildAliasRoot( - HttpContext context, - UrlAction result, - string aliasQueryString, - FriendlyUrlSettings settings) - { - string culture = null; - - // look for specific alias to rewrite language parameter - var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - if (result.PortalId > -1 && result.HttpAlias != null) - { - culture = primaryAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); - } - - if (string.IsNullOrEmpty(culture)) - { - // 732 : when no culture returned can be "" as well as null : no culture causes no rewrite, which results in redirect to parent alias - // set the default culture code here - // 735 : switch to custom method for getting portal - PortalInfo pi = CacheController.GetPortal(result.PortalId, false); - if (pi != null) - { - culture = pi.DefaultLanguage; - } - } - - if (!string.IsNullOrEmpty(culture)) - { - // a culture was identified for the alias root - if (RewriteController.AddLanguageCodeToRewritePath(ref aliasQueryString, culture)) - { - result.CultureCode = culture; - } - - result.DoRewrite = true; - result.RewritePath = "~/" + Globals.glbDefaultPage + aliasQueryString; - - // the expected /default.aspx path (defaultPageUrl) matches the requested Url (/default.aspx) - if (context != null) - { - // only do if not testing - RewriterUtils.RewriteUrl(context, result.RewritePath); - } - } - } - - internal static bool CheckForChildPortalRootUrl(string requestUrl, UrlAction result, out string aliasQueryString) - { - bool isChildPortalRootUrl = false; - - // what we are going to test for here is that if this is a child portal request, for the /default.aspx of the child portal - // then we are going to avoid the core 302 redirect to ?alias=portalALias by rewriting to the /default.aspx of the site root - // 684 : don't convert querystring items to lower case - // do the check by constructing what a child alias url would look like and compare it with the requested urls - // 912 : when requested without a valid portal alias, portalALias is null. Refuse and return false. - aliasQueryString = null; - if (result.PortalAlias != null && result.PortalAlias.HTTPAlias != null) - { - string defaultPageUrl = result.Scheme + result.PortalAlias.HTTPAlias + "/" + - Globals.glbDefaultPage.ToLowerInvariant(); // child alias Url with /default.aspx - - // 660 : look for a querystring on the site root for a child portal, and handle it if so - if (string.CompareOrdinal(requestUrl.ToLowerInvariant(), defaultPageUrl) == 0) - { - // exact match : that's the alias root - isChildPortalRootUrl = true; - aliasQueryString = string.Empty; - } - - if (!isChildPortalRootUrl && requestUrl.Contains("?")) - { - // is we didn't get an exact match but there is a querystring, then investigate - string[] requestUrlParts = requestUrl.Split('?'); - if (requestUrlParts.GetUpperBound(0) > 0) - { - string rootPart = requestUrlParts[0]; - string queryString = requestUrlParts[1]; - if (string.Compare(rootPart, defaultPageUrl, StringComparison.OrdinalIgnoreCase) == 0) - { - // rewrite, but put in the querystring on the rewrite path - isChildPortalRootUrl = true; - aliasQueryString = "?" + queryString; - - // 674: check for 301 if this value is a tabid/xx - otherwise the url will just evaluate as is - if (queryString.ToLowerInvariant().StartsWith("tabid=")) - { - result.Action = ActionType.CheckFor301; - } - } - } - } - } - - return isChildPortalRootUrl; - } - - /// Make sure any redirect to the site root doesn't append the nasty /default.aspx on the end. - /// - /// - /// without at the end. - internal static string CheckForSiteRootRedirect(string alias, string destUrl) - { - // 540 - don't append /default.aspx onto the end of a site root redirect. - if (destUrl.EndsWith(alias + "/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) - { - // this is just the portal alias root + /defualt.aspx. - // we don't want that, just the portalAliasRoot + "/" - string aliasPlusSlash = alias + "/"; - - // get everything up to the end of the portal alias - destUrl = destUrl.Substring(0, destUrl.IndexOf(aliasPlusSlash, StringComparison.Ordinal) + aliasPlusSlash.Length); - } - - return destUrl; - } - - /// - internal override void RewriteUrl(object sender, EventArgs e) - { - Guid parentTraceId = Guid.Empty; - const bool debug = true; - bool failedInitialization = false; - bool ignoreForInstall = false; - var app = (HttpApplication)sender; - try - { - // 875 : completely ignore install/upgrade requests immediately - ignoreForInstall = IgnoreRequestForInstall(app.Request); - - if (ignoreForInstall == false) - { - this.settings = new FriendlyUrlSettings(-1); - - this.SecurityCheck(app); - } - } - catch (Exception ex) - { - // exception handling for advanced Url Rewriting requests - failedInitialization = true; - DotNetNuke.Services.Exceptions.Exceptions.LogException(ex); - if (app.Context != null) - { - ShowDebugData(app.Context, app.Request.Url.AbsoluteUri, null, ex); - var action = new UrlAction(app.Request) { Action = ActionType.Output404 }; - Handle404OrException(this.settings, app.Context, ex, action, false, debug); - } - else - { - throw; - } - } - - if (!failedInitialization && !ignoreForInstall) - { - // if made it through there and not installing, go to next call. Not in exception catch because it implements it's own top-level exception handling - var request = app.Context.Request; - - // 829 : change constructor to stop using physical path - var result = new UrlAction(request) - { - IsSecureConnection = request.IsSecureConnection, - IsSSLOffloaded = UrlUtils.IsSslOffloadEnabled(request), - RawUrl = request.RawUrl, - }; - this.ProcessRequest( - app.Context, - app.Context.Request.Url, - Host.UseFriendlyUrls, - result, - this.settings, - true, - parentTraceId); - } - } - - protected bool IsPortalAliasIncorrect( - HttpContext context, - HttpRequest request, - Uri requestUri, - UrlAction result, - NameValueCollection queryStringCol, - FriendlyUrlSettings settings, - Guid parentTraceId, - out string httpAlias) - { - // now check to make sure it's the primary portal alias for this portal/language/browser - bool incorrectAlias = false; - httpAlias = null; - - // if (result.RedirectAllowed && result.PortalId > -1) - if (result.PortalId > -1) - { - // portal has been identified - var portalAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - - // if we're not on the primary alias, and portalaliasmapping is set to redirect, we might need to be redirected - var redirectToPrimary = !result.PortalAlias.IsPrimary && result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect; - - // forceAlias used in querystring? - var forceAliasInQueryString = queryStringCol != null && queryStringCol["forceAlias"] != null && queryStringCol["forceAlias"] != "true"; - if (redirectToPrimary || forceAliasInQueryString) - { - if (portalAliases.Count > 0) - { - string checkAlias = result.HttpAlias; - bool continueLoop = true; - bool triedWWW = false; - while (httpAlias == null && continueLoop) - { - if (portalAliases.ContainsAlias(result.PortalId, checkAlias)) - { - if (portalAliases.Count > 0) - { - // var cpa = portalAliases.GetAliasByPortalIdAndSettings(result); - string url = requestUri.ToString(); - RewriteController.CheckLanguageMatch(ref url, result); - var cpa = portalAliases - .Where(a => a.IsPrimary || result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) - .GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, result.BrowserType); - - if (cpa != null) - { - httpAlias = cpa.HTTPAlias; - continueLoop = false; - } - - if (string.IsNullOrEmpty(result.CultureCode) && cpa == null) - { - // if there is a specific culture for this portal alias, then check that - string culture = portalAliases.GetCultureByPortalIdAndAlias(result.PortalId, result.HttpAlias); - - // if this matches the alias of the request, then we know we have the correct alias because it is a specific culture - if (!string.IsNullOrEmpty(culture)) - { - continueLoop = false; - } - } - } - } - - // check whether to still go on or not - if (continueLoop) - { - // this alias doesn't exist in the list - // check if it has a www on it - if not, try adding, if it does, try removing - if (!triedWWW) - { - triedWWW = true; // now tried adding/removing www - if (checkAlias.StartsWith("www.", StringComparison.InvariantCultureIgnoreCase)) - { - checkAlias = checkAlias.Substring(4); - } - else - { - checkAlias = "www." + checkAlias; - } - } - else - { - // last thing to try, get the default language and see if there is a portal alias for that - // thus, any aliases not identified as belonging to a language are redirected back to the - // alias named for the default language - continueLoop = false; - - // 735 : switch to custom method for getting portal - PortalInfo pi = CacheController.GetPortal(result.PortalId, false); - if (pi != null) - { - string cultureCode = pi.DefaultLanguage; - if (!string.IsNullOrEmpty(cultureCode)) - { - var primaryPortalAlias = portalAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, cultureCode, settings); - if (primaryPortalAlias != null) - { - httpAlias = primaryPortalAlias.HTTPAlias; - } - } - } - } - } - } - } - - // check to see if it is a custom tab alais - in that case, it is allowed to be requested for the tab - if (CheckIfAliasIsCustomTabAlias(ref result, httpAlias, settings)) - { - // change the primary alias to the custom tab alias that has been requested. - result.PrimaryAlias = result.PortalAlias; - } - else - if (httpAlias != null && string.Compare(httpAlias, result.HttpAlias, StringComparison.OrdinalIgnoreCase) != 0) - { - incorrectAlias = true; - } - } - } - - return incorrectAlias; - } - - private static void ShowDebugData(HttpContext context, string requestUri, UrlAction result, Exception ex) - { - if (context != null) - { - HttpResponse response = context.Response; - - // handle null responses wherever they might be found - this routine must be tolerant to all kinds of invalid inputs - if (requestUri == null) - { - requestUri = "null Uri"; - } - - string finalUrl = "null final Url"; - string rewritePath = "null rewrite path"; - string action = "null action"; - if (result != null) - { - finalUrl = result.FinalUrl; - action = result.Action.ToString(); - rewritePath = result.RewritePath; - } - - // format up the error message to show - const string debugMsg = "{0}, {1}, {2}, {3}, {4}, {5}, {6}"; - string productVer = DotNetNukeContext.Current.Application.Version.ToString(); - string portalSettings = string.Empty; - string browser = "Unknown"; - - // 949 : don't rely on 'result' being non-null - if (result != null) - { - browser = result.BrowserType.ToString(); - } - - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - portalSettings = ps.PortalId.ToString(); - if (ps.PortalAlias != null) - { - portalSettings += ":" + ps.PortalAlias.HTTPAlias; - } - } - } - - response.AppendHeader( - "X-" + ProductName + "-Debug", - string.Format( - debugMsg, - requestUri, - finalUrl, - rewritePath, - action, - productVer, - portalSettings, - browser)); - int msgNum = 1; - if (result != null) - { - foreach (string msg in result.DebugMessages) - { - response.AppendHeader("X-" + ProductName + "-Debug-" + msgNum.ToString("00"), msg); - msgNum++; - } - } - - if (ex != null) - { - response.AppendHeader("X-" + ProductName + "-Ex", ex.Message); - } - } - } - - private static void Handle404OrException(FriendlyUrlSettings settings, HttpContext context, Exception ex, UrlAction result, bool transfer, bool showDebug) - { - // handle Auto-Add Alias - if (result.Action == ActionType.Output404 && CanAutoAddPortalAlias()) - { - // Need to determine if this is a real 404 or a possible new alias. - var portalId = Host.HostPortalID; - if (portalId > Null.NullInteger) - { - if (string.IsNullOrEmpty(result.DomainName)) - { - result.DomainName = Globals.GetDomainName(context.Request); // parse the domain name out of the request - } - - // Get all the existing aliases - var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(portalId).ToList(); - - bool autoaddAlias; - bool isPrimary = false; - if (!aliases.Any()) - { - autoaddAlias = true; - isPrimary = true; - } - else - { - autoaddAlias = true; - foreach (var alias in aliases) - { - if (result.DomainName.ToLowerInvariant().IndexOf(alias.HTTPAlias, StringComparison.Ordinal) == 0 - && result.DomainName.Length >= alias.HTTPAlias.Length) - { - autoaddAlias = false; - break; - } - } - } - - if (autoaddAlias) - { - var portalAliasInfo = new PortalAliasInfo - { - PortalID = portalId, - HTTPAlias = result.DomainName, - IsPrimary = isPrimary, - }; - PortalAliasController.Instance.AddPortalAlias(portalAliasInfo); - - context.Response.Redirect(context.Request.Url.ToString(), true); - } - } - } - - if (context != null) - { - HttpRequest request = context.Request; - HttpResponse response = context.Response; - HttpServerUtility server = context.Server; - - const string errorPageHtmlHeader = @"{0}"; - const string errorPageHtmlFooter = @""; - var errorPageHtml = new StringWriter(); - CustomErrorsSection ceSection = null; - - // 876 : security catch for custom error reading - try - { - ceSection = (CustomErrorsSection)WebConfigurationManager.GetSection("system.web/customErrors"); - } - - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception) - { - // on some medium trust environments, this will throw an exception for trying to read the custom Errors - // do nothing - } - - /* 454 new 404/500 error handling routine */ - bool useDNNTab = false; - int errTabId = -1; - string errUrl = null; - string status = string.Empty; - bool isPostback = false; - if (settings != null) - { - if (request.RequestType == "POST") - { - isPostback = true; - } - - if (result != null && ex != null) - { - result.DebugMessages.Add("Exception: " + ex.Message); - result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); - if (ex.InnerException != null) - { - result.DebugMessages.Add("Inner Ex : " + ex.InnerException.Message); - result.DebugMessages.Add("Stack Trace: " + ex.InnerException.StackTrace); - } - else - { - result.DebugMessages.Add("Inner Ex : null"); - } - } - - string errRH; - string errRV; - int statusCode; - if (result != null && result.Action != ActionType.Output404) - { - // output everything but 404 (usually 500) - if (settings.TabId500 > -1) - { - // tabid specified for 500 error page, use that - useDNNTab = true; - errTabId = settings.TabId500; - } - - errUrl = settings.Url500; - errRH = "X-UrlRewriter-500"; - errRV = "500 Rewritten to {0} : {1}"; - statusCode = 500; - status = "500 Internal Server Error"; - } - else - { - // output 404 error - // if the tabid is specified for a 404 page, then use that - if (settings.TabId404 > -1) - { - useDNNTab = true; - errTabId = settings.TabId404; - } - - // with 404 errors, there's an option to catch certain urls and use an external url for extra processing. - if (!string.IsNullOrEmpty(settings.Regex404)) - { - try - { - // 944 : check the original Url in case the requested Url has been rewritten before discovering it's a 404 error - string requestedUrl = request.Url.ToString(); - if (result != null && !string.IsNullOrEmpty(result.OriginalPath)) - { - requestedUrl = result.OriginalPath; - } - - if (Regex.IsMatch(requestedUrl, settings.Regex404, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) - { - useDNNTab = false; - - // if we have a match in the 404 regex value, then don't use the tabid - } - } - catch (Exception regexEx) - { - // .some type of exception : output in response header, and go back to using the tabid - response.AppendHeader("X-UrlRewriter-404Exception", regexEx.Message); - } - } - - errUrl = settings.Url404; - errRH = "X-UrlRewriter-404"; - errRV = "404 Rewritten to {0} : {1} : Reason {2}"; - status = "404 Not Found"; - statusCode = 404; - } - - // check for 404 logging - if (result == null || result.Action == ActionType.Output404) - { - // Log 404 errors to Event Log - UrlRewriterUtils.Log404(request, settings, result); - } - - // 912 : use unhandled 404 switch - string reason404 = null; - bool unhandled404 = true; - if (useDNNTab && errTabId > -1) - { - unhandled404 = false; // we're handling it here - TabInfo errTab = TabController.Instance.GetTab(errTabId, result.PortalId, true); - if (errTab != null) - { - bool redirect = false; - - // ok, valid tabid. what we're going to do is to load up this tab via a rewrite of the url, and then change the output status - string reason = "Not Found"; - if (result != null) - { - reason = result.Reason.ToString(); - } - - response.AppendHeader( - errRH, - string.Format( - errRV, - "DNN Tab", - errTab.TabName + "(Tabid:" + errTabId.ToString() + ")", - reason)); - - // show debug messages even if in debug mode - if (context != null && response != null && result != null && showDebug) - { - ShowDebugData(context, result.OriginalPath, result, null); - } - - if (!isPostback) - { - response.ClearContent(); - response.StatusCode = statusCode; - response.Status = status; - } - else - { - redirect = true; - - // redirect postbacks as you can't postback successfully to a server.transfer - } - - errUrl = Globals.glbDefaultPage + TabIndexController.CreateRewritePath(errTab.TabID, string.Empty); - - // have to update the portal settings with the new tabid - PortalSettings ps = null; - if (context != null && context.Items != null) - { - if (context.Items.Contains("PortalSettings")) - { - ps = (PortalSettings)context.Items["PortalSettings"]; - context.Items.Remove("PortalSettings"); // nix it from the context - } - } - - if (ps != null && ps.PortalAlias != null) - { - ps = new PortalSettings(errTabId, ps.PortalAlias); - } - else - { - if (result.HttpAlias != null && result.PortalId > -1) - { - PortalAliasInfo pa = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias, result.PortalId); - ps = new PortalSettings(errTabId, pa); - } - else - { - // 912 : handle 404 when no valid portal can be identified - // results when iis is configured to handle portal alias, but - // DNN isn't. This always returns 404 because a multi-portal site - // can't just show the 404 page of the host site. - ArrayList portals = PortalController.Instance.GetPortals(); - if (portals != null && portals.Count == 1) - { - // single portal install, load up portal settings for this portal - var singlePortal = (PortalInfo)portals[0]; - - // list of aliases from database - var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(singlePortal.PortalID).ToList(); - - // list of aliases from Advanced Url settings - List chosen = aliases.GetAliasesForPortalId(singlePortal.PortalID); - PortalAliasInfo useFor404 = null; - - // go through all aliases and either get the first valid one, or the first - // as chosen in the advanced url management settings - foreach (var pa in aliases) - { - if (useFor404 == null) - { - useFor404 = pa; // first one by default - } - - // matching? - if (chosen != null && chosen.Count > 0) - { - if (chosen.Contains(pa.HTTPAlias)) - { - useFor404 = pa; - } - } - else - { - break; // no further checking - } - } - - // now configure that as the portal settings - if (useFor404 != null) - { - // create portal settings context for identified portal alias in single portal install - ps = new PortalSettings(errTabId, useFor404); - } - } - else - { - reason404 = "Requested domain name is not configured as valid website"; - unhandled404 = true; - } - } - } - - if (ps != null) - { - // re-add the context items portal settings back in - context.Items.Add("PortalSettings", ps); - } - - if (redirect) - { - errUrl = TestableGlobals.Instance.NavigateURL(); - response.Redirect(errUrl, true); // redirect and end response. - - // It will mean the user will have to postback again, but it will work the second time - } - else - { - if (transfer) - { - // execute a server transfer to the default.aspx?tabid=xx url - // 767 : object not set error on extensionless 404 errors - if (context.User == null) - { - context.User = GetCurrentPrincipal(context); - } - - response.TrySkipIisCustomErrors = true; - - // 881 : spoof the basePage object so that the client dependency framework - // is satisfied it's working with a page-based handler - IHttpHandler spoofPage = new CDefault(); - context.Handler = spoofPage; - server.Transfer("~/" + errUrl, true); - } - else - { - context.RewritePath("~/Default.aspx", false); - response.TrySkipIisCustomErrors = true; - response.Status = "404 Not Found"; - response.StatusCode = 404; - } - } - } - } - - // 912 : change to new if statement to handle cases where the TabId404 couldn't be handled correctly - if (unhandled404) - { - // proces the error on the external Url by rewriting to the external url - if (!string.IsNullOrEmpty(errUrl)) - { - response.ClearContent(); - response.TrySkipIisCustomErrors = true; - string reason = "Not Found"; - if (result != null) - { - reason = result.Reason.ToString(); - } - - response.AppendHeader(errRH, string.Format(errRV, "Url", errUrl, reason)); - if (reason404 != null) - { - response.AppendHeader("X-Url-Master-404-Data", reason404); - } - - response.StatusCode = statusCode; - response.Status = status; - server.Transfer("~/" + errUrl, true); - } - else - { - errorPageHtml.Write(status + "
    The requested Url does not return any valid content."); - if (reason404 != null) - { - errorPageHtml.Write(status + "
    " + reason404); - } - - errorPageHtml.Write("
    Administrators
    "); - errorPageHtml.Write("
    Change this message by configuring a specific 404 Error Page or Url for this website.
    "); - - // output a reason for the 404 - string reason = string.Empty; - if (result != null) - { - reason = result.Reason.ToString(); - } - - if (!string.IsNullOrEmpty(errRH) && !string.IsNullOrEmpty(reason)) - { - response.AppendHeader(errRH, reason); - } - - response.StatusCode = statusCode; - response.Status = status; - } - } - } - else - { - // fallback output if not valid settings - if (result != null && result.Action == ActionType.Output404) - { - // don't restate the requested Url to prevent cross site scripting - errorPageHtml.Write("404 Not Found
    The requested Url does not return any valid content."); - response.StatusCode = 404; - response.Status = "404 Not Found"; - } - else - { - // error, especially if invalid result object - errorPageHtml.Write("500 Server Error
    An error occured during processing : if possible, check the event log of the server
    "); - response.StatusCode = 500; - response.Status = "500 Internal Server Error"; - if (result != null) - { - result.Action = ActionType.Output500; - } - } - } - - if (ex != null) - { - if (context != null) - { - if (context.Items.Contains("UrlRewrite:Exception") == false) - { - context.Items.Add("UrlRewrite:Exception", ex.Message); - context.Items.Add("UrlRewrite:StackTrace", ex.StackTrace); - } - } - - if (ceSection != null && ceSection.Mode == CustomErrorsMode.Off) - { - errorPageHtml.Write(errorPageHtmlHeader); - errorPageHtml.Write("
    Exception:
    " + ex.Message + "
    "); - errorPageHtml.Write("
    Stack Trace:
    " + ex.StackTrace + "
    "); - errorPageHtml.Write("
    Administrators
    "); - errorPageHtml.Write("
    You can see this exception because the customErrors attribute in the web.config is set to 'off'. Change this value to 'on' or 'RemoteOnly' to show Error Handling
    "); - try - { - if (errUrl != null && errUrl.StartsWith("~")) - { - errUrl = VirtualPathUtility.ToAbsolute(errUrl); - } - } - finally - { - if (errUrl != null) - { - errorPageHtml.Write("
    The error handling would have shown this page : " + errUrl + "
    "); - } - else - { - errorPageHtml.Write("
    The error handling could not determine the correct page to show.
    "); - } - } - } - } - - string errorPageHtmlBody = errorPageHtml.ToString(); - if (errorPageHtmlBody.Length > 0) - { - response.Write(errorPageHtmlHeader); - response.Write(errorPageHtmlBody); - response.Write(errorPageHtmlFooter); - } - - if (ex != null) - { - UrlRewriterUtils.LogExceptionInRequest(ex, status, result); - } - } - } - - private static IPrincipal GetCurrentPrincipal(HttpContext context) - { - // Extract the forms authentication cookie - var authCookie = context.Request.Cookies[FormsAuthentication.FormsCookieName]; - var currentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), new string[0]); - - try - { - if (authCookie != null) - { - var authTicket = FormsAuthentication.Decrypt(authCookie.Value); - if (authTicket != null && !authTicket.Expired) - { - var roles = authTicket.UserData.Split('|'); - var id = new FormsIdentity(authTicket); - currentPrincipal = new GenericPrincipal(id, roles); - } - } - } - catch (Exception) - { - // do nothing here. - } - - return currentPrincipal; - } - - private static bool CheckForDebug(HttpRequest request, NameValueCollection queryStringCol, bool debugEnabled) - { - string debugValue = string.Empty; - bool retVal = false; - - if (debugEnabled) - { - const string debugToken = "_aumdebug"; - if (queryStringCol != null && queryStringCol[debugToken] != null) - { - debugValue = queryStringCol[debugToken]; - } - else - { - if (request != null) - { - debugValue = request.Params.Get("HTTP_" + debugToken.ToUpper()); - } - - if (debugValue == null) - { - debugValue = "false"; - } - } - } - - switch (debugValue.ToLowerInvariant()) - { - case "true": - retVal = true; - break; - } - - return retVal; - } - - private static bool CheckForTabExternalForwardOrRedirect( - HttpContext context, - ref UrlAction result, - HttpResponse response, - FriendlyUrlSettings settings, - Guid parentTraceId) - { - bool finished = false; - HttpRequest request = null; - if (context != null) - { - request = context.Request; - } - - try - { - // check for external forwarding or a permanent redirect request - // 592 : check for permanent redirect (823 : moved location from 'checkForRedirects') - if (result.TabId > -1 && result.PortalId > -1 && - (settings.ForwardExternalUrlsType != DNNPageForwardType.NoForward || - result.Reason == RedirectReason.Tab_Permanent_Redirect)) - { - bool allowRedirect = !(result.RewritePath != null && result.RewritePath.ToLowerInvariant().Contains("&ctl=tab")); - - // 594 : do not redirect settings pages for external urls - if (allowRedirect) - { - TabInfo tab; - allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, false, out tab, settings); - if (allowRedirect) - { - // 772 : not redirecting file type Urls when requested. - bool permanentRedirect = false; - string redirectUrl = null; - string cleanPath = null; - bool doRedirect = false; - switch (tab.TabType) - { - case TabType.File: - // have to fudge in a portal settings object for this to work - shortcoming of LinkClick URl generation - var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); - if (context != null) - { - context.Items.Add("PortalSettings", portalSettings); - result.Reason = RedirectReason.File_Url; - string fileUrl = Globals.LinkClick(tab.Url, tab.TabID, -1); - context.Items.Remove("PortalSettings"); - - // take back out again, because it will be done further downstream - // do a check to make sure we're not repeating the Url again, because the tabid is set but we don't want to touch - // a linkclick url - if (!result.OriginalPathNoAlias.EndsWith(HttpUtility.UrlDecode(fileUrl), true, CultureInfo.InvariantCulture)) - { - redirectUrl = fileUrl; - } - } - - if (redirectUrl != null) - { - doRedirect = true; - } - - break; - case TabType.Url: - result.Reason = RedirectReason.Tab_External_Url; - redirectUrl = tab.Url; - if (redirectUrl != null) - { - doRedirect = true; - if (tab.PermanentRedirect) - { - result.Action = ActionType.Redirect301; - } - else - { - result.Action = ActionType.Redirect302; - } - } - - break; - case TabType.Tab: - // get the redirect path of the specific tab, as long as we have a valid request to work from - if (request != null) - { - // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider - cleanPath = RewriteController.GetRewriteOrRequestedPath(result, request.Url); - - // 727 prevent redirectLoop with do301 in querystring - if (result.Action == ActionType.Redirect301 || - result.Action == ActionType.Redirect302) - { - cleanPath = RedirectTokens.RemoveAnyRedirectTokens( - cleanPath, - request.QueryString); - } - - // get the redirect Url from the friendly url provider using the tab, path and settings - redirectUrl = RedirectController.GetTabRedirectUrl( - tab, - settings, - cleanPath, - result, - out permanentRedirect, - parentTraceId); - } - - // check to make sure there isn't a blank redirect Url - if (redirectUrl == null) - { - // problem : no redirect Url to redirect to - // solution : cancel the redirect - string message = "Permanent Redirect chosen for Tab " + - tab.TabPath.Replace("//", "/") + - " but forwarding Url was not valid"; - RedirectController.CancelRedirect(ref result, context, settings, message); - } - else - { - // if there was a redirect Url, set the redirect action and set the type of redirect going to use - doRedirect = true; - if (permanentRedirect) - { - result.Action = ActionType.Redirect301; - result.Reason = RedirectReason.Tab_Permanent_Redirect; - } - else - { - // not a permanent redirect, check if the page forwarding is set - result.Action = ActionType.Redirect302; - result.Reason = RedirectReason.Tab_Temporary_Redirect; - } - - // should be already set, anyway - result.RewritePath = cleanPath; - } - - break; - default: - // only concern here is if permanent redirect is requested, but there is no external url specified - if (result.Reason == RedirectReason.Tab_Permanent_Redirect) - { - bool permRedirect = tab.PermanentRedirect; - if (permRedirect) - { - // problem : permanent redirect marked, but no forwarding url supplied - // solution : cancel redirect - string message = "Permanent Redirect chosen for Tab " + - tab.TabPath.Replace("//", "/") + - " but no forwarding Url Supplied"; - RedirectController.CancelRedirect(ref result, context, settings, message); - } - } - - break; - } - - // do the redirect we have specified - if (doRedirect && - (result.Action == ActionType.Redirect301 || result.Action == ActionType.Redirect302)) - { - result.FinalUrl = redirectUrl; - if (result.Action == ActionType.Redirect301) - { - if (response != null) - { - // perform a 301 redirect to the external url of the tab - response.AppendHeader( - "X-Redirect-Reason", - result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl); - } - } - else - { - if (result.Action == ActionType.Redirect302) - { - if (response != null) - { - // perform a 301 redirect to the external url of the tab - response.AppendHeader( - "X-Redirect-Reason", - result.Reason.ToString().Replace("_", " ") + " Requested"); - response.Redirect(result.FinalUrl); - } - } - } - - finished = true; - } - } - } - } - } - catch (ThreadAbortException) - { - // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest - // level try/catch block, so we handle it here. - } - - return finished; - } - - /// Redirects an alias if that is allowed by the settings. - /// - /// - /// - /// if the is a redirect, otherwise . - private static bool RedirectPortalAlias(string httpAlias, ref UrlAction result, FriendlyUrlSettings settings) - { - bool redirected = false; - - // redirect to primary alias - if (result.PortalAliasMapping == PortalSettings.PortalAliasMapping.Redirect && result.RedirectAllowed) - { - if (result.Reason == RedirectReason.Wrong_Portal_Alias_For_Browser_Type || result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture || - result.Reason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) - { - redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, result.Reason, settings.InternalAliasList, settings); - } - else - { - redirected = ConfigurePortalAliasRedirect(ref result, result.HttpAlias, httpAlias, false, settings.InternalAliasList, settings); - } - } - - return redirected; - } - - private static bool ConfigurePortalAliasRedirect( - ref UrlAction result, - string wrongAlias, - string rightAlias, - bool ignoreCustomAliasTabs, - List internalAliases, - FriendlyUrlSettings settings) - { - return ConfigurePortalAliasRedirect( - ref result, - wrongAlias, - rightAlias, - ignoreCustomAliasTabs, - RedirectReason.Wrong_Portal_Alias, - internalAliases, - settings); - } - - /// Checks to see whether the specified alias is a customTabAlias. - /// - /// - /// - /// if the alias is a custom tab alias, otherwise . - private static bool CheckIfAliasIsCustomTabAlias(ref UrlAction result, string httpAlias, FriendlyUrlSettings settings) - { - List customAliasesForTabs = TabIndexController.GetCustomPortalAliases(settings); - bool isACustomTabAlias = false; - if (customAliasesForTabs != null && customAliasesForTabs.Count > 0) - { - // remove any customAliases that are also primary aliases. - foreach (var cpa in PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId)) - { - if (cpa.IsPrimary == true && customAliasesForTabs.Contains(cpa.HTTPAlias)) - { - customAliasesForTabs.Remove(cpa.HTTPAlias); - } - } - - isACustomTabAlias = customAliasesForTabs.Contains(httpAlias.ToLowerInvariant()); - } - - return isACustomTabAlias; - } - - /// Checks to see whether the specified alias is a customTabAlias for the TabId in result. - /// - /// - /// if the the current alias is a custom tab alias, otherwise . - private static bool CheckIfAliasIsCurrentTabCustomTabAlias(ref UrlAction result, FriendlyUrlSettings settings) - { - var customAliasesForTab = TabController.Instance.GetCustomAliases(result.TabId, result.PortalId); - bool isCurrentTabCustomTabAlias = false; - if (customAliasesForTab != null && customAliasesForTab.Count > 0) - { - // see if we have a customAlias for the current CultureCode - if (customAliasesForTab.ContainsKey(result.CultureCode)) - { - // if it is for the current culture, we need to know if it's a primary alias - var tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(customAliasesForTab[result.CultureCode]); - if (tabPortalAlias != null && !tabPortalAlias.IsPrimary) - { - // it's not a primary alias, so must be a custom tab alias - isCurrentTabCustomTabAlias = true; - } - } - } - - // if it's not a custom alias for the current tab, we'll need to change the result - if (!isCurrentTabCustomTabAlias) - { - result.Action = ActionType.Redirect301; - result.Reason = RedirectReason.Wrong_Portal_Alias; - } - - return isCurrentTabCustomTabAlias; - } - - /// Configures the result object to set the correct Alias redirect parameters and destination URL. - /// - /// - /// - /// - /// - /// - /// - /// if the is a redirect, otherwise . - private static bool ConfigurePortalAliasRedirect( - ref UrlAction result, - string wrongAlias, - string rightAlias, - bool ignoreCustomAliasTabs, - RedirectReason redirectReason, - List internalAliases, - FriendlyUrlSettings settings) - { - // wrong alias for the portal - // check to see if the wrong portal alias could be a custom alias for a tab - bool doRedirect; - if (ignoreCustomAliasTabs == false) - { - // check out custom alias tabs collection - // if an alias is a custom tab alias for a specific tab, then don't redirect - // if we have the TabId, we'll need to check if the alias is valid for the current tab - if (result.TabId > 0 && CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) - { - doRedirect = false; - } - else if (result.TabId < 0 && CheckIfAliasIsCustomTabAlias(ref result, wrongAlias, settings)) - { - doRedirect = false; - } - else - { - doRedirect = true; - } - } - else - { - doRedirect = true; // do redirect, ignore custom alias entries for tabs - } - - // check to see if it is an internal alias. These are used to block redirects - // to allow for reverse proxy requests, which must change the rewritten alias - // while leaving the requested alias - bool internalAliasFound = false; - if (doRedirect && internalAliases != null && internalAliases.Count > 0) - { - if (internalAliases.Any(ia => string.Compare(ia.HttpAlias, wrongAlias, StringComparison.OrdinalIgnoreCase) == 0)) - { - internalAliasFound = true; - doRedirect = false; - } - } - - // if still need to do redirect, then set the settings that will cause the redirect (redirect not done here) - if (doRedirect) - { - result.Action = ActionType.Redirect301; - result.Reason = redirectReason; - var destUrl = result.OriginalPath; - if (result.OriginalPath.Contains(wrongAlias)) - { - destUrl = result.OriginalPath.Replace(wrongAlias, rightAlias); - } - else if (result.OriginalPath.ToLowerInvariant().Contains(wrongAlias)) - { - destUrl = result.OriginalPath.ToLowerInvariant().Replace(wrongAlias, rightAlias); - } - - if (redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture || - redirectReason == RedirectReason.Wrong_Portal_Alias_For_Culture_And_Browser) - { - destUrl = destUrl.Replace("/language/" + result.CultureCode, string.Empty); - } - - destUrl = CheckForSiteRootRedirect(rightAlias, destUrl); - result.FinalUrl = destUrl; - } - else - { - // 838 : don't overwrite the reason if already have checkfor301 - // and don't do a check on the basis that an internal alias was found - if (result.Action != ActionType.CheckFor301 && internalAliasFound == false) - { - // set status to 'check for redirect' - result.Action = ActionType.CheckFor301; - result.Reason = RedirectReason.Custom_Tab_Alias; - } - } - - return doRedirect; - } - - private static string MakeUrlWithAlias(Uri requestUri, string httpAlias) - { - return requestUri.AbsoluteUri.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase) - ? "https://" + httpAlias.Replace("*.", string.Empty) + "/" - : "http://" + httpAlias.Replace("*.", string.Empty) + "/"; - } - - private static string MakeUrlWithAlias(Uri requestUri, PortalAliasInfo alias) - { - return MakeUrlWithAlias(requestUri, alias.HTTPAlias); - } - - /// Determines if this is a request from an install / upgrade url. - /// - /// - /// - /// - /// if the request is for an install URL, otherwise . - /// - /// //875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1. - /// - private static bool IgnoreRequestForInstall(string physicalPath, string refererPath, string requestedDomain, string refererDomain) - { - if (physicalPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) - || physicalPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) - || physicalPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture) - || Globals.Status == Globals.UpgradeStatus.Install - || Globals.Status == Globals.UpgradeStatus.Upgrade) - { - return true; - } - - // 954 : DNN 7.0 compatibility - // check for /default.aspx which is default Url launched from the Upgrade/Install wizard page - // 961 : check domain as well as path for the referer - if (physicalPath.EndsWith(Globals.glbDefaultPage, true, CultureInfo.InvariantCulture) == false - && refererPath != null - && string.Compare(requestedDomain, refererDomain, StringComparison.OrdinalIgnoreCase) == 0 - && (refererPath.EndsWith("install.aspx", true, CultureInfo.InvariantCulture) - || refererPath.EndsWith("installwizard.aspx", true, CultureInfo.InvariantCulture) - || refererPath.EndsWith("upgradewizard.aspx", true, CultureInfo.InvariantCulture))) - { - return true; - } - - return false; - } - - private static bool IgnoreRequestForWebServer(string requestedPath) - { - // Should standardize comparison methods - if (requestedPath.IndexOf("synchronizecache.aspx", StringComparison.OrdinalIgnoreCase) > 1 - || requestedPath.EndsWith("keepalive.aspx", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - // Get the root - var rootPath = requestedPath.Substring(0, requestedPath.LastIndexOf("/", StringComparison.Ordinal)); - rootPath = rootPath.Substring(rootPath.IndexOf("://", StringComparison.Ordinal) + 3); - - // Check if this is a WebServer and not a portalalias. - // if can auto add portal alias enabled, then return false, alias will add later. - var alias = PortalAliasController.Instance.GetPortalAlias(rootPath); - if (alias != null || CanAutoAddPortalAlias()) - { - return false; - } - - // Check if this is a WebServer - var server = ServerController.GetEnabledServers().SingleOrDefault(s => s.Url == rootPath); - if (server != null) - { - return true; - } - - return false; - } - - private static bool IgnoreRequestForInstall(HttpRequest request) - { - try - { - string physicalPath = request.PhysicalPath; - string requestedDomain = request.Url.Host; - string refererPath = null, refererDomain = null; - if (request.UrlReferrer != null) - { - refererDomain = request.UrlReferrer.Host; - refererPath = request.UrlReferrer.LocalPath; - } - - return IgnoreRequestForInstall(physicalPath, refererPath, requestedDomain, refererDomain); - } - catch (PathTooLongException) - { - // catch and handle this exception, caused by an excessively long file path based on the - // mapped virtual url - return false; - } - catch (ArgumentException) - { - // catch and handle this exception, caused by an invalid character in the file path based on the - // mapped virtual url - return false; - } - catch (UriFormatException) - { - // catch and handle this exception, caused by an invalid hostname in the referrer - return false; - } - } - - private static bool IgnoreRequest(UrlAction result, string requestedPath, string ignoreRegex, HttpRequest request) - { - bool retVal = false; - - // check if we are upgrading/installing - // 829 : use result physical path instead of requset physical path - // 875 : cater for the upgradewizard.aspx Url that is new to DNN 6.1 - if (request != null && (IgnoreRequestForInstall(request) || IgnoreRequestForWebServer(requestedPath))) - { - // ignore all install requests - retVal = true; - } - else if (request != null && request.Path.EndsWith("imagechallenge.captcha.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - retVal = true; - } - else - { - try - { - if (ignoreRegex.Length > 0) - { - if (Regex.IsMatch(requestedPath, ignoreRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) - { - retVal = true; - } - } - } - catch (Exception ex) - { - UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); - result.Ex = ex; - } - } - - return retVal; - } - - private static void CheckForRewrite( - string fullUrl, - string querystring, - UrlAction result, - bool useFriendlyUrls, - NameValueCollection queryStringCol, - FriendlyUrlSettings settings, - out bool isPhysicalResource, - Guid parentTraceId) - { - bool checkForRewrites; - - // just check to make sure it isn't a physical resource on the server - RewriteController.IdentifyByPhysicalResource( - result.PhysicalPath, - fullUrl, - queryStringCol, - ref result, - useFriendlyUrls, - settings, - out isPhysicalResource, - out checkForRewrites, - parentTraceId); - - if (checkForRewrites && RewriteController.CanRewriteRequest(result, fullUrl, settings)) - { - bool doSiteUrlProcessing = false; - - // 728 new regex expression to pass values straight onto the siteurls.config file - if (!string.IsNullOrEmpty(settings.UseSiteUrlsRegex)) - { - doSiteUrlProcessing = Regex.IsMatch(fullUrl, settings.UseSiteUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - - // if a virtual request, and not starting with the siteUrls.config file, go on to find the rewritten path - if (!doSiteUrlProcessing) - { - // looks up the page index to find the correct Url - bool doRewrite = RewriteController.IdentifyByTabPathEx(fullUrl, querystring, result, queryStringCol, settings, parentTraceId); - if (!doRewrite) - { - doSiteUrlProcessing = true; - } - } - - if (doSiteUrlProcessing) - { - // 728 : compare requests against the siteurls.config file, either if no other match was found, or if we want to skip the rest of the processing - // the standard DNN way of rewriting, using expressions found in the siteurls.config file - RewriteController.IdentifyByRegEx(fullUrl, querystring, result.ApplicationPath, ref result, settings, parentTraceId); - } - } - } - - private static bool CheckForRedirects( - Uri requestUri, - string fullUrl, - NameValueCollection queryStringCol, - UrlAction result, - string requestType, - FriendlyUrlSettings settings, - int portalHomeTabId) - { - bool redirected = false; - if (queryStringCol["error"] == null && queryStringCol["message"] == null && requestType != "POST") - { - // if the / is missing from an extension-less request, then check for a 301 redirect - if (settings.PageExtensionUsageType == PageExtensionUsageType.Never) - { - // 575 check on absolutePath instead of absoluteUri : this ignores query strings and fragments like # - // 610 don't always end with '/' - reverses previous setting - // 687 don't double-check 301 redirects. 'CheckFor301' is less concise than 'Redirect301' - // DNN-21906: if the redirect is for splash page, then we should continue the 302 redirect. - if (requestUri.AbsolutePath.EndsWith("/") && result.Action != ActionType.Redirect301 && result.Reason != RedirectReason.Requested_SplashPage) - { - result.Action = ActionType.CheckFor301; - } - } - - if (settings.RedirectWrongCase && result.Action == ActionType.Continue) - { - result.Action = ActionType.CheckFor301; - } - - string scheme = requestUri.Scheme + Uri.SchemeDelimiter; - bool queryStringHas301Parm = queryStringCol["do301"] != null; - - // 727 : keep a bool value if there is a do301 request in the querystring - // check for a 301 request in the query string, or an explicit 301 or 302 request - // 2.0 - check for explicit do301=true instead of just do301 key - string do301Val = queryStringCol["do301"]; - if (result.TabId > -1 - && (result.Action == ActionType.Redirect301 - || (do301Val != null && do301Val == "true") - || result.Action == ActionType.Redirect302)) - { - // valid tab, specific 301 redirect, rewrite hint for specific 301 redirect, or specific 302 redirect - // we have ordered a 301 redirect earlier in the code - // get the url for redirection by re-submitting the path into the Friendly Url Provider - string pathOnly = RewriteController.GetRewriteOrRequestedPath(result, requestUri); - - // 727 prevent redirectLoop with do301 in querystring - if (result.Action == ActionType.Redirect301 || queryStringHas301Parm || result.Action == ActionType.Redirect302) - { - pathOnly = RedirectTokens.RemoveAnyRedirectTokens(pathOnly, queryStringCol); - } - - // check for exclusion by regex for this url - if (result.RedirectAllowed) - { - // get the tab so we know where to go - TabInfo tab; - bool checkRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); - - if (checkRedirect) - { - if ((result.Reason == RedirectReason.Deleted_Page || result.Reason == RedirectReason.Disabled_Page) - && portalHomeTabId > 0 - && settings.DeletedTabHandlingType == DeletedTabHandlingType.Do301RedirectToPortalHome) - { - // redirecting to home page - TabInfo homeTab = TabController.Instance.GetTab(portalHomeTabId, result.PortalId, false); - if (homeTab != null) - { - string homePageUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( - homeTab, - pathOnly, - Globals.glbDefaultPage, - result.HttpAlias, - false, - settings, - Guid.Empty); - result.Action = ActionType.Redirect301; - result.FinalUrl = homePageUrl; - result.RewritePath = pathOnly; - redirected = true; - } - } - else - { - // get the rewrite or requested path in a clean format, suitable for input to the friendly url provider - string cleanPath = RewriteController.GetRewriteOrRequestedPath(result, requestUri); - - // 727 prevent redirectLoop with do301 in querystring - // also check for existing in path of do301 token - if (result.Action == ActionType.Redirect301 || do301Val != null || result.Action == ActionType.Redirect302) - { - cleanPath = RedirectTokens.RemoveAnyRedirectTokens(cleanPath, queryStringCol); - } - - // get best friendly url from friendly url provider - string bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( - tab, - cleanPath, - Globals.glbDefaultPage, - result.HttpAlias, - false, - settings, - Guid.Empty); - - // get what the friendly Url for this tab should be and stick it in as the redirect - // 727 : using boolean because we wanted to get rid of the do301 before calculating the correct url - if (queryStringHas301Parm) - { - result.Action = ActionType.Redirect301; - if (result.Reason == RedirectReason.Not_Redirected) - { - result.Reason = RedirectReason.Unfriendly_Url_1; - } - } - - result.FinalUrl = bestFriendlyUrl; - result.RewritePath = pathOnly; - redirected = true; // mark as redirected - } - } - else - { - // redirect disallowed - // 618: dont' clear if 302 redirect selected - if (result.Action != ActionType.Redirect302Now || result.Action != ActionType.Redirect302) - { - RedirectController.CancelRedirect(ref result, null, settings, "Redirect requested but cancelled because disallowed"); - } - } - } - } - else if (result.TabId > -1 && result.RedirectAllowed && result.Action == ActionType.CheckFor301) - { - // 301 check was requested in earlier processing - // get the tab controller and retrieve the tab the request is for - // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab - if (settings.RedirectUnfriendly) - { - TabInfo tab; - bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); - if (allowRedirect && tab != null) - { - // remove the http alias from the url. Do this by putting the url back together from the request and removing the alias - string rewritePathOnly; - if (result.DoRewrite) - { - rewritePathOnly = result.RewritePath; - var pos = rewritePathOnly.IndexOf("default.aspx", StringComparison.OrdinalIgnoreCase); - if (pos > Null.NullInteger) - { - rewritePathOnly = rewritePathOnly.Substring(pos); - } - } - else - { - rewritePathOnly = requestUri.Host + requestUri.PathAndQuery; - } - - // remove the http alias from the path - var pathAliasEnd = rewritePathOnly.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase); - var queryStringIndex = rewritePathOnly.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); - if (pathAliasEnd > Null.NullInteger && (queryStringIndex == Null.NullInteger || pathAliasEnd < queryStringIndex)) - { - rewritePathOnly = rewritePathOnly.Substring(pathAliasEnd + result.PortalAlias.HTTPAlias.Length); - } - - // now check to see if need to remove /default.aspx from the end of the requested Url - string requestedUrl = fullUrl; - int requestedUrlAliasEnd = requestedUrl.IndexOf(result.PortalAlias.HTTPAlias, StringComparison.InvariantCultureIgnoreCase) - + (result.PortalAlias.HTTPAlias + "/").Length; - if (requestedUrlAliasEnd > Null.NullInteger) - { - // 818 : when a site root is used for a custom page Url, then check for max length within bounds - if ((requestedUrl.Length - requestedUrlAliasEnd) >= 12 && requestedUrl.Substring(requestedUrlAliasEnd).Equals("default.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - requestedUrl = requestedUrl.Substring(0, requestedUrl.Length - 12); - - // 12 = default.aspx length - } - } - - // what happens here is that the request is reverse-engineered to see if it matches what the friendly Url shoudl have been - // get what the friendly Url for this tab should be - string bestFriendlyUrl; - - // 819 : leaving /do301/check in Url because not using cleanPath to remove from - string cleanPath = RedirectTokens.RemoveAnyRedirectTokensAndReasons(rewritePathOnly); - - // string cleanPath = rewritePathOnly.Replace("&do301=check","");//remove check parameter if it exists - // cleanPath = cleanPath.Replace("&do301=true", "");//don't pass through internal redirect check parameter - cleanPath = cleanPath.Replace("&_aumdebug=true", string.Empty); // remove debug parameter if it exists - - Match match = RewritePathRx.Match(rewritePathOnly ?? string.Empty); - if (match.Success) - { - // when the pathOnly value ends with '=' it means there is a query string pair with a key and no value - // make the assumption that this was passed in as a page name OTHER than default page - string pageName = match.Groups["parm"].Value; // get the last parameter in the list - - cleanPath = cleanPath.Replace(match.Value, string.Empty); - - // remove the last parameter from the path - - // generate teh friendly URl name with the last parameter as the page name, not a query string parameter - bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( - tab, - cleanPath, - pageName + settings.PageExtension, - result.HttpAlias, - false, - settings, - Guid.Empty); - } - else - { - bestFriendlyUrl = AdvancedFriendlyUrlProvider.ImprovedFriendlyUrl( - tab, - cleanPath, - Globals.glbDefaultPage, - result.HttpAlias, - false, - settings, - Guid.Empty); - } - - // if the incoming request doesn't match the 'most friendly' url, a 301 Moved Permanently status is returned, along with the friendly url - // check the bestFriendlyUrl against either the url, or rawUrl (with and without host) - // in each case, the aumdebug parameter will be searched for and replaced - var urlDecode = HttpUtility.UrlDecode(requestedUrl); - if (urlDecode != null) - { - string rawUrlWithHost = StripDebugParameter(urlDecode.ToLowerInvariant()); - - // string rawUrlWithHost = StripDebugParameter(System.Web.HttpUtility.UrlDecode(scheme + requestUri.Host + requestUri.PathAndQuery).ToLowerInvariant()); - string rawUrlWithHostNoScheme = StripDebugParameter(rawUrlWithHost.Replace(scheme, string.Empty)); - string bestFriendlyNoScheme = StripDebugParameter(bestFriendlyUrl.ToLowerInvariant().Replace(scheme, string.Empty)); - string requestedPathNoScheme = StripDebugParameter(requestUri.AbsoluteUri.Replace(scheme, string.Empty).ToLowerInvariant()); - string rawUrlLowerCase = StripDebugParameter(requestUri.AbsoluteUri.ToLowerInvariant()); - - // check to see if just an alias redirect of an internal alias - var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - - if (settings.InternalAliasList != null && settings.InternalAliasList.Count > 0 && primaryAliases.Count > 0) - { - var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result); - if (cpa != null) - { - string chosenAlias = cpa.HTTPAlias.ToLowerInvariant(); - foreach (InternalAlias ia in settings.InternalAliasList) - { - string internalAlias = ia.HttpAlias.ToLowerInvariant(); - if (requestedPathNoScheme.Contains(internalAlias)) - { - // an internal alias has been used. - // replace this in the comparison charts to do a 'fair' comparison - requestedPathNoScheme = requestedPathNoScheme.Replace(internalAlias, chosenAlias); - rawUrlWithHost = rawUrlWithHost.Replace(scheme + internalAlias, scheme + chosenAlias); - rawUrlWithHostNoScheme = rawUrlWithHostNoScheme.Replace(internalAlias, chosenAlias); - rawUrlLowerCase = rawUrlLowerCase.Replace(internalAlias, chosenAlias); - break; - } - } - } - } - - // DNN-9158: prevent SSL Offloading infinite redirects - if (!result.IsSecureConnection && result.IsSSLOffloaded && bestFriendlyNoScheme.StartsWith("https")) - { - bestFriendlyNoScheme = $"http://{bestFriendlyNoScheme.Substring(8)}"; - } - - if (!(bestFriendlyNoScheme == requestedPathNoScheme - || bestFriendlyNoScheme == rawUrlWithHost - || HttpUtility.UrlDecode(bestFriendlyNoScheme) == rawUrlWithHost - || bestFriendlyNoScheme == rawUrlWithHostNoScheme - || bestFriendlyNoScheme == HttpUtility.UrlDecode(requestedPathNoScheme) - || HttpUtility.UrlDecode(bestFriendlyNoScheme) == HttpUtility.UrlDecode(requestedPathNoScheme) - || bestFriendlyNoScheme == rawUrlLowerCase)) - { - redirected = true; - result.Action = ActionType.Redirect301; - result.FinalUrl = bestFriendlyUrl; - if (result.Reason != RedirectReason.Custom_Tab_Alias && - result.Reason != RedirectReason.Deleted_Page && - result.Reason != RedirectReason.Disabled_Page) - { - result.Reason = RedirectReason.Unfriendly_Url_2; - } - - result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + requestedPathNoScheme + " [requested with no scheme]"); - result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHost + " [requested with host and scheme]"); - result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlWithHostNoScheme + " [requested with host, no scheme]"); - result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + HttpUtility.UrlDecode(requestedPathNoScheme) + " [requested and decoded]"); - result.DebugMessages.Add("Compared :" + bestFriendlyNoScheme + " [generated] -> " + rawUrlLowerCase + " [requested raw Url]"); - } - } - } - } - } - - if (result.RedirectAllowed && settings.RedirectWrongCase) - { - // check for redirects where a redirectToSubDomain is specified, - // redirect for Wrong case is specified, and there is a valid tab and it's not already redirected somewhere else - bool doRedirect = false; - string redirectPath = redirected ? result.FinalUrl : requestUri.AbsoluteUri; - string redirectPathOnly = redirectPath; - if (redirectPathOnly.Contains("?")) - { - redirectPathOnly = redirectPathOnly.Substring(0, redirectPathOnly.IndexOf("?", StringComparison.Ordinal)); - } - - // Thanks Etienne for the fix for Diacritic Characters Terminal Loop! - // if the url contains url encoded characters, they appear here uppercase -> %C3%83%C2 - // decode the url to get back the original character and do proper casing comparison - string urlDecodedRedirectPath = HttpUtility.UrlDecode(redirectPathOnly); - - // check for wrong case redirection - if (urlDecodedRedirectPath != null && (settings.RedirectWrongCase && string.CompareOrdinal(urlDecodedRedirectPath, urlDecodedRedirectPath.ToLowerInvariant()) != 0)) - { - TabInfo tab; - bool allowRedirect = CheckFor301RedirectExclusion(result.TabId, result.PortalId, true, out tab, settings); - - if (allowRedirect && !string.IsNullOrEmpty(settings.ForceLowerCaseRegex)) - { - // don't allow redirect if excluded from redirecting in the force lower case regex pattern (606) - allowRedirect = !Regex.IsMatch(redirectPath, settings.ForceLowerCaseRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - - if (allowRedirect) - { - // special case : when IIS automatically places /default.aspx on the end of the string, - // then don't try and redirect to the lower case /default.aspx, just let it through. - // we don't know whether IIS appended /Default.aspx on the end, however, we can guess - // if the redirectDefault.aspx is turned on (511) - if (settings.RedirectDefaultPage == false && redirectPathOnly.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) - { - // ignore this, because it's just a redirect of the /Default.aspx to /default.aspx - } - else - { - redirectPath = redirectPath.Replace(redirectPathOnly, redirectPathOnly.ToLowerInvariant()); - doRedirect = true; - result.Reason = RedirectReason.Not_Lower_Case; - } - } - } - - if (doRedirect) - { - result.Action = ActionType.Redirect301; - result.FinalUrl = CheckForSiteRootRedirect(result.PortalAlias.HTTPAlias, redirectPath); - redirected = true; - } - } - } - - return redirected; - } - - private static string StripDebugParameter(string url) - { - return AumDebugRegex.Replace(url, string.Empty); - } - - private static bool CheckFor301RedirectExclusion(int tabId, int portalId, bool checkBaseUrls, out TabInfo tab, FriendlyUrlSettings settings) - { - bool doRedirect = false; - tab = TabController.Instance.GetTab(tabId, portalId, false); - - // don't redirect unless allowed, the tab is valid, and it's not an admin or super tab - if (tab != null && tab.IsSuperTab == false && !tab.DoNotRedirect) - { - if (checkBaseUrls) - { - // no redirect for friendly url purposes if the tab is in the 'base friendly urls' section - doRedirect = !RewriteController.IsExcludedFromFriendlyUrls(tab, settings, true); - } - else - { - doRedirect = true; - } - } - - return doRedirect; - } - - private PortalAliasInfo GetPortalAlias(FriendlyUrlSettings settings, string requestUrl, out bool redirectAlias, out bool isPrimaryAlias, out string wrongAlias) - { - PortalAliasInfo aliasInfo = null; - redirectAlias = false; - wrongAlias = null; - isPrimaryAlias = false; - OrderedDictionary portalAliases = TabIndexController.GetPortalAliases(settings); - foreach (string alias in portalAliases.Keys) - { - var urlToMatch = requestUrl; - - // in fact, requested url should contain alias - // for better performance, need to check whether we want to proceed with a whole url matching or not - // if alias is not a part of url -> let's proceed to the next iteration - var aliasIndex = urlToMatch.IndexOf(alias, StringComparison.InvariantCultureIgnoreCase); - if (aliasIndex < 0) - { - continue; - } - else - { - // we do not accept URL if the first occurence of alias is presented somewhere in the query string - var queryIndex = urlToMatch.IndexOf("?", StringComparison.InvariantCultureIgnoreCase); - if (queryIndex >= 0 && queryIndex < aliasIndex) - { - // alias is in the query string, go to the next alias - continue; - } - - // we are fine here, lets prepare URL to be validated in regex - urlToMatch = urlToMatch.ReplaceIgnoreCase(alias, "_ALIAS_"); - } - - // check whether requested URL has the right URL format containing existing alias - // i.e. url is http://dnndev.me/site1/query?string=test, alias is dnndev.me/site1 - // in the below expression we will validate following value http://_ALIAS_/query?string=test - var aliasMatch = AliasUrlRegex.Match(urlToMatch); - if (aliasMatch.Success) - { - // check for mobile browser and matching - var aliasEx = (PortalAliasInfo)portalAliases[alias]; - redirectAlias = aliasEx.Redirect; - if (redirectAlias) - { - wrongAlias = alias; - } - - isPrimaryAlias = aliasEx.IsPrimary; - aliasInfo = aliasEx; - break; - } - } - - return aliasInfo; - } - - private void ProcessRequest( - HttpContext context, - Uri requestUri, - bool useFriendlyUrls, - UrlAction result, - FriendlyUrlSettings settings, - bool allowSettingsChange, - Guid parentTraceId) - { - bool finished = false; - bool showDebug = false; - bool postRequest = false; - - HttpRequest request = context.Request; - HttpResponse response = context.Response; - string requestType = request.RequestType; - NameValueCollection queryStringCol = request.QueryString; - - try - { - string fullUrl, querystring; - - // 699: get the full url based on the request and the quersytring, rather than the requestUri.ToString() - // there is a difference in encoding, which can corrupt results when an encoded value is in the querystring - RewriteController.GetUrlWithQuerystring(request, requestUri, out fullUrl, out querystring); - - showDebug = CheckForDebug(request, queryStringCol, settings.AllowDebugCode); - string ignoreRegex = settings.IgnoreRegex; - bool ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); - bool redirectAlias = false; - if (!ignoreRequest) - { - // set original path - context.Items["UrlRewrite:OriginalUrl"] = requestUri.AbsoluteUri; - - // set the path of the result object, and determine if a redirect is allowed on this request - result.SetOriginalPath(requestUri.ToString(), settings); - - // 737 : set the mobile browser - result.SetBrowserType(request, response, settings); - - // add to context - context.Items["UrlRewrite:BrowserType"] = result.BrowserType.ToString(); - - // 839 : split out this check - result.SetRedirectAllowed(result.OriginalPath, settings); - - // find the portal alias first - string wrongAlias; - bool isPrimaryAlias; - var requestedAlias = this.GetPortalAlias(settings, fullUrl, out redirectAlias, out isPrimaryAlias, out wrongAlias); - - if (requestedAlias != null) - { - // 827 : now get the correct settings for this portal (if not a test request) - // 839 : separate out redirect check as well and move above first redirect test (ConfigurePortalAliasRedirect) - if (allowSettingsChange) - { - settings = new FriendlyUrlSettings(requestedAlias.PortalID); - result.SetRedirectAllowed(result.OriginalPath, settings); - } - - result.PortalAlias = requestedAlias; - result.PrimaryAlias = requestedAlias; // this is the primary alias - result.PortalId = requestedAlias.PortalID; - result.CultureCode = requestedAlias.CultureCode; - - // get the portal alias mapping for this portal - result.PortalAliasMapping = PortalSettingsController.Instance().GetPortalAliasMappingMode(requestedAlias.PortalID); - - // if requested alias wasn't the primary, we have a replacement, redirects are allowed and the portal alias mapping mode is redirect - // then do a redirect based on the wrong portal - if ((redirectAlias && wrongAlias != null) && result.RedirectAllowed && result.PortalAliasMapping != PortalSettings.PortalAliasMapping.Redirect) - { - // this is the alias, we are going to enforce it as the primary alias - result.PortalAlias = requestedAlias; - result.PrimaryAlias = requestedAlias; - - // going to redirect this alias because it is incorrect - // or do we just want to mark as 'check for 301??' - redirectAlias = ConfigurePortalAliasRedirect( - ref result, - wrongAlias, - requestedAlias.HTTPAlias, - false, - settings.InternalAliasList, - settings); - } - else - { - // do not redirect the wrong alias, but set the primary alias value - if (wrongAlias != null) - { - // get the portal alias info for the requested alias (which is the wrong one) - // and set that as the alias, but also set the found alias as the primary - PortalAliasInfo wrongAliasInfo = PortalAliasController.Instance.GetPortalAlias(wrongAlias); - if (wrongAliasInfo != null) - { - result.PortalAlias = wrongAliasInfo; - result.PrimaryAlias = requestedAlias; - } - } - } - } - } - - ignoreRegex = settings.IgnoreRegex; - ignoreRequest = IgnoreRequest(result, fullUrl, ignoreRegex, request); - if (!ignoreRequest) - { - // check to see if a post request - if (request.RequestType == "POST") - { - postRequest = true; - } - - // check the portal alias again. This time, in more depth now that the portal Id is known - // this check handles browser types/language specific aliases & mobile aliases - string primaryHttpAlias; - if (!redirectAlias && this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryHttpAlias)) - { - // it was an incorrect alias - PortalAliasInfo primaryAlias = PortalAliasController.Instance.GetPortalAlias(primaryHttpAlias); - if (primaryAlias != null) - { - result.PrimaryAlias = primaryAlias; - } - - // try and redirect the alias if the settings allow it - redirectAlias = RedirectPortalAlias(primaryHttpAlias, ref result, settings); - } - - if (redirectAlias) - { - // not correct alias for portal : will be redirected - // perform a 301 redirect if one has already been found - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl, false); - finished = true; - } - - if (!finished) - { - // Check to see if this to be rewritten into default.aspx?tabId=nn format - // this call is the main rewriting matching call. It makes the decision on whether it is a - // physical file, whether it is toe be rewritten or redirected by way of a stored rule - - // Check if we have a standard url - var uri = new Uri(fullUrl); - if (uri.PathAndQuery.StartsWith("/" + Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) - { - result.DoRewrite = true; - result.Action = ActionType.CheckFor301; - result.RewritePath = Globals.glbDefaultPage + uri.Query; - } - else - { - bool isPhysicalResource; - CheckForRewrite(fullUrl, querystring, result, useFriendlyUrls, queryStringCol, settings, out isPhysicalResource, parentTraceId); - } - - // return 404 if there is no portal alias for a rewritten request - if (result.DoRewrite && result.PortalAlias == null) - { - // 882 : move this logic in from where it was before to here - // so that non-rewritten requests don't trip over it - // no portal alias found for the request : that's a 404 error - result.Action = ActionType.Output404; - result.Reason = RedirectReason.No_Portal_Alias; - - Handle404OrException(settings, context, null, result, false, showDebug); - finished = true; // cannot fulfil request unless correct portal alias specified - } - } - - // now we may know the TabId. If the current alias is not the same as the primary alias, - // we should check if the current alias is indeed a valid custom alias for the current tab. - if (result.TabId > 0 && result.HttpAlias != result.PrimaryAlias.HTTPAlias && !CheckIfAliasIsCurrentTabCustomTabAlias(ref result, settings)) - { - // it was an incorrect alias - // try and redirect the alias if the settings allow it - if (RedirectPortalAlias(result.PrimaryAlias.HTTPAlias, ref result, settings)) - { - // not correct alias for tab : will be redirected - // perform a 301 redirect if one has already been found - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl, false); - finished = true; - } - } - - if (!finished && result.DoRewrite) - { - // check the identified portal alias details for any extra rewrite information required - // this includes the culture and the skin, which can be placed into the rewrite path - // This logic is here because it will catch any Urls which are excluded from rewriting - var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - - if (result.PortalId > -1 && result.HttpAlias != null) - { - string culture; - string skin; - BrowserTypes browserType; - primaryAliases.GetSettingsByPortalIdAndAlias( - result.PortalId, - result.HttpAlias, - out culture, - out browserType, - out skin); - - // add language code to path if it exists (not null) and if it's not already there - string rewritePath = result.RewritePath; - if (RewriteController.AddLanguageCodeToRewritePath(ref rewritePath, culture)) - { - result.CultureCode = culture; - } - - // 852: add skinSrc to path if it exists and if it's not already there - string debugMessage; - RewriteController.AddSkinToRewritePath(result.TabId, result.PortalId, ref rewritePath, skin, out debugMessage); - result.RewritePath = rewritePath; // reset back from ref temp var - if (debugMessage != null) - { - result.DebugMessages.Add(debugMessage); - } - } - } - - if (!finished && result.DoRewrite) - { - // if so, do the rewrite - if (result.RewritePath.StartsWith(result.Scheme) || result.RewritePath.StartsWith(Globals.glbDefaultPage) == false) - { - if (result.RewritePath.Contains(Globals.glbDefaultPage) == false) - { - RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); - } - else - { - // if there is no TabId and we have the domain - if (!result.RewritePath.ToLowerInvariant().Contains("tabId=")) - { - RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); - } - else - { - RewriterUtils.RewriteUrl(context, result.RewritePath); - } - } - } - else - { - if (IsMvc(result, queryStringCol, context, result.TabId, result.PortalId)) - { - RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath.Replace(Globals.glbDefaultPage, "DesktopModules/Default/Page/" + result.TabId + "/" + result.CultureCode)); - } - else - { - RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath); - } - } - } - - // confirm which portal the request is for - if (!finished) - { - this.IdentifyPortalAlias(context, request, requestUri, result, queryStringCol, settings, parentTraceId); - if (result.Action == ActionType.Redirect302Now) - { - // performs a 302 redirect if requested - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.Redirect(result.FinalUrl, false); - finished = true; - } - else - { - if (result.Action == ActionType.Redirect301 && !string.IsNullOrEmpty(result.FinalUrl)) - { - finished = true; - - // perform a 301 redirect if one has already been found - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl, false); - } - } - } - - if (!finished) - { - // check to see if this tab has an external url that should be forwared or not - finished = CheckForTabExternalForwardOrRedirect(context, ref result, response, settings, parentTraceId); - } - - // check for a parameter redirect (we had to do all the previous processing to know we are on the right portal and identify the tabid) - // if the CustomParmRewrite flag is set, it means we already rewrote these parameters, so they have to be correct, and aren't subject to - // redirection. The only reason to do a custom parm rewrite is to interpret already-friendly parameters - if (!finished - && !postRequest /* either request is null, or it's not a post - 551 */ - && result.HttpAlias != null /* must have a http alias */ - && !result.CustomParmRewrite && /* not custom rewritten parms */ - ((settings.EnableCustomProviders && - RedirectController.CheckForModuleProviderRedirect(requestUri, ref result, queryStringCol, settings, parentTraceId)) - - // 894 : allow disable of all custom providers - || - RedirectController.CheckForParameterRedirect(requestUri, ref result, queryStringCol, settings))) - { - // 301 redirect to new location based on parameter match - if (response != null) - { - switch (result.Action) - { - case ActionType.Redirect301: - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl); - break; - case ActionType.Redirect302: - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.Redirect(result.FinalUrl); - break; - case ActionType.Output404: - response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); - Handle404OrException(settings, context, null, result, true, showDebug); - break; - } - } - - finished = true; - } - - // shifted until after the 301 redirect code to allow redirects to be checked for pages which have no rewrite value - // look for a 404 result from the rewrite, because of a deleted page or rule - if (!finished && result.Action == ActionType.Output404) - { - if (result.OriginalPath.Equals(result.HttpAlias, StringComparison.InvariantCultureIgnoreCase) - && result.PortalAlias != null - && result.Reason != RedirectReason.Deleted_Page - && result.Reason != RedirectReason.Disabled_Page) - { - // Request for domain with no page identified (and no home page set in Site Settings) - result.Action = ActionType.Continue; - } - else - { - finished = true; - response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); - - if (showDebug) - { - ShowDebugData(context, requestUri.AbsoluteUri, result, null); - } - - // show the 404 page if configured - result.Reason = RedirectReason.Requested_404; - Handle404OrException(settings, context, null, result, true, showDebug); - } - } - - if (!finished) - { - // add the portal settings to the app context if the portal alias has been found and is correct - if (result.PortalId != -1 && result.PortalAlias != null) - { - // for invalid tab id other than -1, show the 404 page - TabInfo tabInfo = TabController.Instance.GetTab(result.TabId, result.PortalId, false); - if (tabInfo == null && result.TabId > -1) - { - finished = true; - - if (showDebug) - { - ShowDebugData(context, requestUri.AbsoluteUri, result, null); - } - - // show the 404 page if configured - result.Action = ActionType.Output404; - result.Reason = RedirectReason.Requested_404; - response.AppendHeader("X-Result-Reason", result.Reason.ToString().Replace("_", " ")); - Handle404OrException(settings, context, null, result, true, showDebug); - } - else - { - Globals.SetApplicationName(result.PortalId); - - // load the PortalSettings into current context - var portalSettings = new PortalSettings(result.TabId, result.PortalAlias); - - // set the primary alias if one was specified - if (result.PrimaryAlias != null) - { - portalSettings.PrimaryAlias = result.PrimaryAlias; - } - - if (result.CultureCode != null && fullUrl.Contains(result.CultureCode) && - portalSettings.DefaultLanguage == result.CultureCode) - { - // when the request culture code is the same as the portal default, check for a 301 redirect, because we try and remove the language from the url where possible - result.Action = ActionType.CheckFor301; - } - - int portalHomeTabId = portalSettings.HomeTabId; - if (context != null && portalSettings != null && !context.Items.Contains("PortalSettings")) - { - context.Items.Add("PortalSettings", portalSettings); - - // load PortalSettings and HostSettings dictionaries into current context - // specifically for use in DotNetNuke.Web.Client, which can't reference DotNetNuke.dll to get settings the normal way - context.Items.Add("PortalSettingsDictionary", PortalController.Instance.GetPortalSettings(portalSettings.PortalId)); - context.Items.Add("HostSettingsDictionary", HostController.Instance.GetSettingsDictionary()); - } - - // check if a secure redirection is needed - // this would be done earlier in the piece, but need to know the portal settings, tabid etc before processing it - bool redirectSecure = this.CheckForSecureRedirect(portalSettings, requestUri, result, queryStringCol, settings); - if (redirectSecure) - { - if (response != null) - { - // 702 : don't check final url until checked for null reference first - if (result.FinalUrl != null) - { - if (result.FinalUrl.StartsWith("https://")) - { - if (showDebug) - { - /* - string debugMsg = "{0}, {1}, {2}, {3}, {4}"; - string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); - response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); - */ - ShowDebugData(context, fullUrl, result, null); - } - - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl); - finished = true; - } - else - { - if (settings.SSLClientRedirect) - { - // redirect back to http version, use client redirect - response.Clear(); - - // add a refresh header to the response - response.AddHeader("Refresh", "0;URL=" + result.FinalUrl); - - // add the clientside javascript redirection script - var finalUrl = HttpUtility.HtmlEncode(result.FinalUrl); - response.Write(""); - response.Write(@""); - response.Write(""); - if (showDebug) - { - /* - string debugMsg = "{0}, {1}, {2}, {3}, {4}"; - string productVer = System.Reflection.Assembly.GetExecutingAssembly().GetName(false).Version.ToString(); - response.AppendHeader("X-" + prodName + "-Debug", string.Format(debugMsg, requestUri.AbsoluteUri, result.FinalUrl, result.RewritePath, result.Action, productVer)); - */ - ShowDebugData(context, fullUrl, result, null); - } - - // send the response - // 891 : reinstate the response.end to stop the entire page loading - response.End(); - finished = true; - } - else - { - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl); - finished = true; - } - } - } - } - } - else - { - // check for, and do a 301 redirect if required - if (CheckForRedirects(requestUri, fullUrl, queryStringCol, result, requestType, settings, portalHomeTabId)) - { - if (response != null) - { - if (result.Action == ActionType.Redirect301) - { - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.RedirectPermanent(result.FinalUrl, false); - finished = true; - } - else if (result.Action == ActionType.Redirect302) - { - response.AppendHeader("X-Redirect-Reason", result.Reason.ToString().Replace("_", " ") + " Requested"); - response.Redirect(result.FinalUrl, false); - finished = true; - } - } - } - else - { - // 612 : Don't clear out a 302 redirect if set - if (result.Action != ActionType.Redirect302 && - result.Action != ActionType.Redirect302Now) - { - result.Reason = RedirectReason.Not_Redirected; - result.FinalUrl = null; - } - } - } - } - } - else - { - // alias does not exist in database - // and all attempts to find another have failed - // this should only happen if the HostPortal does not have any aliases - result.Action = ActionType.Output404; - if (response != null) - { - if (showDebug) - { - ShowDebugData(context, fullUrl, result, null); - } - - result.Reason = RedirectReason.Requested_404; - - // 912 : change 404 type to transfer to allow transfer to main portal in single-portal installs - Handle404OrException(settings, context, null, result, true, showDebug); - finished = true; - } - } - } - - // 404 page ?? - if (settings.TabId404 > 0 && settings.TabId404 == result.TabId) - { - string status = queryStringCol["status"]; - if (status == "404") - { - // respond with a 404 error - result.Action = ActionType.Output404; - result.Reason = RedirectReason.Requested_404_In_Url; - Handle404OrException(settings, context, null, result, true, showDebug); - } - } - else - { - if (result.DoRewrite == false && result.CanRewrite != StateBoolean.False && !finished && - result.Action == ActionType.Continue) - { - // 739 : catch no-extension 404 errors - string pathWithNoQs = result.OriginalPath; - if (pathWithNoQs.Contains("?")) - { - pathWithNoQs = pathWithNoQs.Substring(0, pathWithNoQs.IndexOf("?", StringComparison.Ordinal)); - } - - if (!pathWithNoQs.Substring(pathWithNoQs.Length - 5, 5).Contains(".")) - { - // no page extension, output a 404 if the Url is not found - // 766 : check for physical path before passing off as a 404 error - // 829 : change to use action physical path - // 893 : filter by regex pattern to exclude urls which are valid, but show up as extensionless - if ((request != null && Directory.Exists(result.PhysicalPath)) - || - Regex.IsMatch(pathWithNoQs, settings.ValidExtensionlessUrlsRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) - { - // do nothing : it's a request for a valid physical path, maybe including a default document - result.VirtualPath = StateBoolean.False; - } - else - { - if (!Globals.ServicesFrameworkRegex.IsMatch(context.Request.RawUrl)) - { - // no physical path, intercept the request and hand out a 404 error - result.Action = ActionType.Output404; - result.Reason = RedirectReason.Page_404; - result.VirtualPath = StateBoolean.True; - - // add in a message to explain this 404, becaue it can be cryptic - result.DebugMessages.Add("404 Reason : Not found and no extension"); - Handle404OrException(settings, context, null, result, true, showDebug); - } - } - } - } - } - - // show debug messages after extensionless-url special 404 handling - if (showDebug) - { - ShowDebugData(context, fullUrl, result, null); - } - } - } - catch (ThreadAbortException) - { - // do nothing, a threadAbortException will have occured from using a server.transfer or response.redirect within the code block. This is the highest - // level try/catch block, so we handle it here. - Thread.ResetAbort(); - } - catch (Exception ex) - { - if (showDebug) - { - Services.Exceptions.Exceptions.LogException(ex); - } - - if (response != null) - { - if (showDebug) - { - ShowDebugData(context, requestUri.AbsoluteUri, result, ex); - } - - if (result != null) - { - result.Ex = ex; - result.Reason = RedirectReason.Exception; - } - - Handle404OrException(settings, context, ex, result, false, showDebug); - } - else - { - if (result != null && result.DebugMessages != null) - { - result.DebugMessages.Add("Exception: " + ex.Message); - result.DebugMessages.Add("Stack Trace: " + ex.StackTrace); - } - - throw; - } - } - finally - { - // 809 : add in new code copied from urlRewrite class in standard Url Rewrite module - if (context != null && context.Items["FirstRequest"] != null) - { - context.Items.Remove("FirstRequest"); - - // process any messages in the eventQueue for the Application_Start_FIrstRequest event - EventQueueController.ProcessMessages("Application_Start_FirstRequest"); - } - } - } - - private bool CheckForSecureRedirect( - PortalSettings portalSettings, - Uri requestUri, - UrlAction result, - NameValueCollection queryStringCol, - FriendlyUrlSettings settings) - { - bool redirectSecure = false; - string url = requestUri.ToString(); - - // 889 : don't run secure redirect code for physical resources or requests that aren't a rewritten Url - if (result.IsPhysicalResource == false && result.TabId >= 0) - { - // no secure redirection for physical resources, only tab-specific requests can be redirected for ssl connections - if (portalSettings.ActiveTab != null) - { - result.DebugMessages.Add("ActiveTab: " + portalSettings.ActiveTab.TabID.ToString() + "/" + - portalSettings.ActiveTab.TabName + " IsSecure: " + - portalSettings.ActiveTab.IsSecure.ToString()); - - switch (portalSettings.SSLSetup) - { - case Abstractions.Security.SiteSslSetup.On: - if (!result.IsSecureConnection) - { - redirectSecure = true; - url = url.Replace("http://", "https://"); - } - - break; - case Abstractions.Security.SiteSslSetup.Advanced: - // 717 : check page is secure, connection is not secure - // 952 : support SSl Offloading in DNN 6.2+ - if (portalSettings.ActiveTab.IsSecure && !result.IsSecureConnection && !result.IsSSLOffloaded) - { - redirectSecure = true; - string stdUrl = portalSettings.STDURL; - string sslUrl = portalSettings.SSLURL; - if (string.IsNullOrEmpty(result.HttpAlias) == false) - { - stdUrl = result.HttpAlias; - } - - url = url.Replace("http://", "https://"); - url = this.ReplaceDomainName(url, stdUrl, sslUrl); - } - - if (portalSettings.SSLEnforced) - { - // Prevent browser's mixed-content error in case we open a secure PopUp or a secure iframe - // from an unsecure page - if (!portalSettings.ActiveTab.IsSecure && - result.IsSecureConnection && - !UrlUtils.IsPopUp(url)) - { - // has connection already been forced to secure? - if (queryStringCol["ssl"] == null) - { - // no? well this page shouldn't be secure - string stdUrl = portalSettings.STDURL; - string sslUrl = portalSettings.SSLURL; - url = url.Replace("https://", "http://"); - url = this.ReplaceDomainName(url, sslUrl, stdUrl); - redirectSecure = true; - } - } - } - - break; - } - } - - if (redirectSecure) - { - // now check to see if excluded. Why now? because less requests are made to redirect secure, - // so we don't have to check the exclusion as often. - bool exclude = false; - string doNotRedirectSecureRegex = settings.DoNotRedirectSecureRegex; - if (!string.IsNullOrEmpty(doNotRedirectSecureRegex)) - { - // match the raw url - exclude = Regex.IsMatch(result.RawUrl, doNotRedirectSecureRegex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - - if (!exclude) - { - result.Action = ActionType.Redirect302Now; - result.Reason = RedirectReason.Secure_Page_Requested; - - // 760 : get the culture specific home page tabid for a redirect comparison - int homePageTabId = portalSettings.HomeTabId; - homePageTabId = TabPathHelper.GetHomePageTabIdForCulture( - portalSettings.DefaultLanguage, - portalSettings.PortalId, - result.CultureCode, - homePageTabId); - if (result.TabId == homePageTabId) - { - // replace the /default.aspx in the Url if it was found - url = DefaultPageRegex.Replace(url, "/"); - } - - result.FinalUrl = url; - } - else - { - // 702 : change return value if exclusion has occured - redirectSecure = false; - } - } - } - - return redirectSecure; - } - - private string ReplaceDomainName(string url, string replaceDomain, string withDomain) - { - if (replaceDomain != string.Empty && withDomain != string.Empty) - { - // 951 : change find/replace routine to regex for more accurate replacement - // (previous method gives false positives if the SSL Url is contained within the STD url) - string find = @"(?<=https?://)" + Regex.Escape(withDomain); - if (Regex.IsMatch(url, find, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) == false) - { - string replaceFind = @"(?<=https?://)" + Regex.Escape(replaceDomain); - url = Regex.Replace(url, replaceFind, withDomain, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - } - - return url; - } - - private void IdentifyPortalAlias( - HttpContext context, - HttpRequest request, - Uri requestUri, - UrlAction result, - NameValueCollection queryStringCol, - FriendlyUrlSettings settings, - Guid parentTraceId) - { - // get the domain name of the request, if it isn't already supplied - if (request != null && string.IsNullOrEmpty(result.DomainName)) - { - result.DomainName = Globals.GetDomainName(request); // parse the domain name out of the request - } - - // get tabId from querystring ( this is mandatory for maintaining portal context for child portals ) - if (queryStringCol["tabid"] != null) - { - string raw = queryStringCol["tabid"]; - int tabId; - if (int.TryParse(raw, out tabId)) - { - result.TabId = tabId; - } - else - { - // couldn't parse tab id - // split in two? - string[] tabids = raw.Split(','); - if (tabids.GetUpperBound(0) > 0) - { - // hmm more than one tabid - if (int.TryParse(tabids[0], out tabId)) - { - result.TabId = tabId; - - // but we want to warn against this! - var ex = - new Exception( - "Illegal request exception : Two TabId parameters provided in a single request: " + - requestUri); - UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); - - result.Ex = ex; - } - else - { - // yeah, nothing, divert to 404 - result.Action = ActionType.Output404; - var ex = - new Exception( - "Illegal request exception : TabId parameters in query string, but invalid TabId requested : " + - requestUri); - UrlRewriterUtils.LogExceptionInRequest(ex, "Not Set", result); - result.Ex = ex; - } - } - } - } - - // get PortalId from querystring ( this is used for host menu options as well as child portal navigation ) - if (queryStringCol["portalid"] != null) - { - string raw = queryStringCol["portalid"]; - int portalId; - if (int.TryParse(raw, out portalId)) - { - // 848 : if portal already found is different to portal id in querystring, then load up different alias - // this is so the portal settings will be loaded correctly. - if (result.PortalId != portalId) - { - // portal id different to what we expected - result.PortalId = portalId; - - // check the loaded portal alias, because it might be wrong - if (result.PortalAlias != null && result.PortalAlias.PortalID != portalId) - { - // yes, the identified portal alias is wrong. Find the correct alias for this portal - PortalAliasInfo pa = TabIndexController.GetPortalAliasByPortal(portalId, result.DomainName); - if (pa != null) - { - // note: sets portal id and portal alias - result.PortalAlias = pa; - } - } - } - } - } - else - { - // check for a portal alias if there's no portal Id in the query string - // check for absence of captcha value, because the captcha string re-uses the alias querystring value - if (queryStringCol["alias"] != null && queryStringCol["captcha"] == null) - { - string alias = queryStringCol["alias"]; - PortalAliasInfo portalAlias = PortalAliasController.Instance.GetPortalAlias(alias); - if (portalAlias != null) - { - // ok the portal alias was found by the alias name - // check if the alias contains the domain name - if (alias.Contains(result.DomainName) == false) - { - // replaced to the domain defined in the alias - if (request != null) - { - string redirectDomain = Globals.GetPortalDomainName(alias, request, true); - - // retVal.Url = redirectDomain; - result.FinalUrl = redirectDomain; - result.Action = ActionType.Redirect302Now; - result.Reason = RedirectReason.Alias_In_Url; - } - } - else - { - // the alias is the same as the current domain - result.HttpAlias = portalAlias.HTTPAlias; - result.PortalAlias = portalAlias; - result.PortalId = portalAlias.PortalID; - - // don't use this crap though - we don't want ?alias=portalAlias in our Url - if (result.RedirectAllowed) - { - string redirect = requestUri.Scheme + Uri.SchemeDelimiter + result.PortalAlias.HTTPAlias + - "/"; - result.Action = ActionType.Redirect301; - result.FinalUrl = redirect; - result.Reason = RedirectReason.Unfriendly_Url_Child_Portal; - } - } - } - } - } - - // first try and identify the portal using the tabId, but only if we identified this tab by looking up the tabid - // from the original url - // 668 : error in child portal redirects to child portal home page because of mismatch in tab/domain name - if (result.TabId != -1 && result.FriendlyRewrite == false) - { - // get the alias from the tabid, but only if it is for a tab in that domain - // 2.0 : change to compare retrieved alias to the already-set httpAlias - string httpAliasFromTab = PortalAliasController.GetPortalAliasByTab(result.TabId, result.DomainName); - if (httpAliasFromTab != null) - { - // 882 : account for situation when portalAlias is null. - if ((result.PortalAlias != null && string.Compare(result.PortalAlias.HTTPAlias, httpAliasFromTab, StringComparison.OrdinalIgnoreCase) != 0) - || result.PortalAlias == null) - { - // 691 : change logic to force change in portal alias context rather than force back. - // This is because the tabid in the query string should take precedence over the portal alias - // to handle parent.com/default.aspx?tabid=xx where xx lives in parent.com/child/ - var tab = TabController.Instance.GetTab(result.TabId, Null.NullInteger, false); - - // when result alias is null or result alias is different from tab-identified portalAlias - if (tab != null && (result.PortalAlias == null || tab.PortalID != result.PortalAlias.PortalID)) - { - // the tabid is different to the identified portalid from the original alias identified - // so get a new alias - PortalAliasInfo tabPortalAlias = PortalAliasController.Instance.GetPortalAlias(httpAliasFromTab, tab.PortalID); - if (tabPortalAlias != null) - { - result.PortalId = tabPortalAlias.PortalID; - result.PortalAlias = tabPortalAlias; - result.Action = ActionType.CheckFor301; - result.Reason = RedirectReason.Wrong_Portal; - } - } - } - } - } - - // if no alias, try and set by using the identified http alias or domain name - if (result.PortalAlias == null) - { - if (!string.IsNullOrEmpty(result.HttpAlias)) - { - result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.HttpAlias); - } - else - { - result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); - if (result.PortalAlias == null && result.DomainName.EndsWith("/")) - { - result.DomainName = result.DomainName.TrimEnd('/'); - result.PortalAlias = PortalAliasController.Instance.GetPortalAlias(result.DomainName); - } - } - } - - if (result.PortalId == -1) - { - if (!requestUri.LocalPath.EndsWith(Globals.glbDefaultPage, StringComparison.InvariantCultureIgnoreCase)) - { - // allows requests for aspx pages in custom folder locations to be processed - return; - } - - // the domain name was not found so try using the host portal's first alias - if (Host.HostPortalID != -1) - { - result.PortalId = Host.HostPortalID; - - // use the host portal, but replaced to the host portal home page - var aliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - if (aliases.Count > 0) - { - string alias = null; - - // get the alias as the chosen portal alias for the host portal based on the result culture code - var cpa = aliases.GetAliasByPortalIdAndSettings(result.PortalId, result, result.CultureCode, settings); - if (cpa != null) - { - alias = cpa.HTTPAlias; - } - - if (alias != null) - { - result.Action = ActionType.Redirect301; - result.Reason = RedirectReason.Host_Portal_Used; - string destUrl = MakeUrlWithAlias(requestUri, alias); - destUrl = CheckForSiteRootRedirect(alias, destUrl); - result.FinalUrl = destUrl; - } - else - { - // Get the first Alias for the host portal - result.PortalAlias = aliases[result.PortalId]; - string url = MakeUrlWithAlias(requestUri, result.PortalAlias); - if (result.TabId != -1) - { - url += requestUri.Query; - } - - result.FinalUrl = url; - result.Reason = RedirectReason.Host_Portal_Used; - result.Action = ActionType.Redirect302Now; - } - } - } - } - - // double check to make sure we still have the correct alias now that all other information is known (ie tab, portal, culture) - // 770 : redirect alias based on tab id when custom alias used - if (result.TabId == -1 && result.Action == ActionType.CheckFor301 && - result.Reason == RedirectReason.Custom_Tab_Alias) - { - // here because the portal alias matched, but no tab was found, and because there are custom tab aliases used for this portal - // need to redirect back to the chosen portal alias and keep the current path. - string wrongAlias = result.HttpAlias; // it's a valid alias, but only for certain tabs - var primaryAliases = PortalAliasController.Instance.GetPortalAliasesByPortalId(result.PortalId).ToList(); - if (primaryAliases != null && result.PortalId > -1) - { - // going to look for the correct alias based on the culture of the request - string requestCultureCode = result.CultureCode; - - // if that didn't work use the default language of the portal - if (requestCultureCode == null) - { - // this might end up in a double redirect if the path of the Url is for a specific language as opposed - // to a path belonging to the default language domain - PortalInfo portal = PortalController.Instance.GetPortal(result.PortalId); - if (portal != null) - { - requestCultureCode = portal.DefaultLanguage; - } - } - - // now that the culture code is known, look up the correct portal alias for this portalid/culture code - var cpa = primaryAliases.GetAliasByPortalIdAndSettings(result.PortalId, result, requestCultureCode, settings); - if (cpa != null) - { - // if an alias was found that matches the request and the culture code, then run with that - string rightAlias = cpa.HTTPAlias; - - // will cause a redirect to the primary portal alias - we know now that there was no custom alias tab - // found, so it's just a plain wrong alias - ConfigurePortalAliasRedirect(ref result, wrongAlias, rightAlias, true, settings.InternalAliasList, settings); - } - } - } - else - { - // then check to make sure it's the chosen portal alias for this portal - // 627 : don't do it if we're redirecting to the host portal - if (result.RedirectAllowed && result.Reason != RedirectReason.Host_Portal_Used) - { - string primaryAlias; - - // checking again in case the rewriting operation changed the values for the valid portal alias - bool incorrectAlias = this.IsPortalAliasIncorrect(context, request, requestUri, result, queryStringCol, settings, parentTraceId, out primaryAlias); - if (incorrectAlias) - { - RedirectPortalAlias(primaryAlias, ref result, settings); - } - } - } - - // check to see if we have to avoid the core 302 redirect for the portal alias that is in the /defualt.aspx - // for child portals - // exception to that is when a custom alias is used but no rewrite has taken place - if (result.DoRewrite == false && (result.Action == ActionType.Continue - || - (result.Action == ActionType.CheckFor301 && - result.Reason == RedirectReason.Custom_Tab_Alias))) - { - string aliasQuerystring; - bool isChildAliasRootUrl = CheckForChildPortalRootUrl(requestUri.AbsoluteUri, result, out aliasQuerystring); - if (isChildAliasRootUrl) - { - RewriteAsChildAliasRoot(context, result, aliasQuerystring, settings); - } - } - } - - private void SecurityCheck(HttpApplication app) - { - HttpRequest request = app.Request; - HttpServerUtility server = app.Server; - - // 675 : unnecessarily strict url validation - // URL validation - // check for ".." escape characters commonly used by hackers to traverse the folder tree on the server - // the application should always use the exact relative location of the resource it is requesting - var strURL = request.Url.AbsolutePath; - var strDoubleDecodeURL = server.UrlDecode(server.UrlDecode(request.Url.AbsolutePath)) ?? string.Empty; - if (UrlSlashesRegex.Match(strURL).Success || UrlSlashesRegex.Match(strDoubleDecodeURL).Success) - { - throw new HttpException(404, "Not Found"); - } - } - } -} diff --git a/DNN Platform/Library/Mvc/MvcClientAPI.cs b/DNN Platform/Library/Mvc/MvcClientAPI.cs deleted file mode 100644 index 28086b9893b..00000000000 --- a/DNN Platform/Library/Mvc/MvcClientAPI.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.Mvc -{ - using System; - using System.Collections.Generic; - using System.Web; - - public class MvcClientAPI - { - public static Dictionary GetClientVariableList() - { - var dic = HttpContext.Current.Items["CAPIVariableList"] as Dictionary; - if (dic == null) - { - dic = new Dictionary(); - HttpContext.Current.Items["CAPIVariableList"] = dic; - } - - return dic; - } - - public static Dictionary GetClientStartupScriptList() - { - var dic = HttpContext.Current.Items["CAPIStartupScriptList"] as Dictionary; - if (dic == null) - { - dic = new Dictionary(); - HttpContext.Current.Items["CAPIStartupScriptList"] = dic; - } - - return dic; - } - - public static void RegisterClientVariable(string key, string value, bool overwrite) - { - GetClientVariableList().Add(key, value); - } - - public static void RegisterEmbeddedResource(string fileName, Type assemblyType) - { - // RegisterClientVariable(FileName + ".resx", ThePage.ClientScript.GetWebResourceUrl(AssemblyType, FileName), true); - } - - public static void RegisterStartupScript(string key, string value) - { - if (!GetClientStartupScriptList().ContainsKey(key)) - { - GetClientStartupScriptList().Add(key, value); - } - } - - public static void RegisterScript(string key, string value) - { - if (!GetClientStartupScriptList().ContainsKey(key)) - { - GetClientStartupScriptList().Add(key, value); - } - } - } -} From 195b4076c5ffa785257e9bcee7bcb12d5cbd9ba2 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 14:47:22 +0100 Subject: [PATCH 006/146] Add build file --- .../DotNetNuke.Web.MvcPipeline/Library.build | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build b/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build new file mode 100644 index 00000000000..a1f95545d12 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build @@ -0,0 +1,15 @@ + + + $(MSBuildProjectDirectory)\..\.. + + + + + + + + + + + + \ No newline at end of file From 192a1fe373ffddc9f2645c0d18fede452111a53f Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 16:18:42 +0100 Subject: [PATCH 007/146] Move MVC skin detection to separate class --- .../Entities/Urls/MvcAdvancedUrlRewriter.cs | 99 +---------------- .../Entities/Urls/MvcUrlRewriterController.cs | 100 ++++++++++++++++++ 2 files changed, 105 insertions(+), 94 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs index 24cfbc4cdce..38e806df432 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcAdvancedUrlRewriter.cs @@ -55,100 +55,11 @@ public void ProcessTestRequestWithContext( parentTraceId); } - internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) - { - bool mvcCtl = false; - var skinSrc = string.Empty; - - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; - if (string.IsNullOrEmpty(skinSrc)) - { - skinSrc = PortalSettings.Current.DefaultPortalSkin; - } - } - } - - if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) - { - var tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - skinSrc = tab.SkinSrc; - } - } - - if (!string.IsNullOrEmpty(skinSrc)) - { - mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); - } - - /* - var mvcCtls = new[] { "Module", "Terms", "Privacy" }; - bool mvcSkin = false; - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - mvcSkin = !string.IsNullOrEmpty(PortalSettings.Current.ActiveTab.SkinSrc) && - PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); - } - } - - if (result.RewritePath.Contains("&ctl=")) - { - foreach (var item in mvcCtls) - { - mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); - } - - if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) - { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) - { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - mvcCtl = tab.GetTags().Contains("mvc"); - } - } - - // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); - } - } - else - { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) - { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - mvcCtl = tab.GetTags().Contains("mvc"); - } - } - - // mvcCtl = result.RawUrl.EndsWith("mvc"); - } - - mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; - mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; - */ - - return mvcCtl; - } - internal static void RewriteAsChildAliasRoot( - HttpContext context, - UrlAction result, - string aliasQueryString, - FriendlyUrlSettings settings) + HttpContext context, + UrlAction result, + string aliasQueryString, + FriendlyUrlSettings settings) { string culture = null; @@ -2359,7 +2270,7 @@ private void ProcessRequest( } else { - if (IsMvc(result, queryStringCol, context, result.TabId, result.PortalId)) + if (MvcUrlRewriterController.IsMvc(result, queryStringCol, context, result.TabId, result.PortalId)) { RewriterUtils.RewriteUrl(context, "~/" + result.RewritePath.Replace(Globals.glbDefaultPage, "DesktopModules/Default/Page/" + result.TabId + "/" + result.CultureCode)); } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs new file mode 100644 index 00000000000..4fa12c926ed --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs @@ -0,0 +1,100 @@ +using System.Collections.Specialized; +using DotNetNuke.Entities.Portals; +using DotNetNuke.Entities.Tabs; +using DotNetNuke.Entities.Urls; +using System.Web; + +namespace DotNetNuke.Web.MvcPipeline.Entities.Urls +{ + internal class MvcUrlRewriterController + { + internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) + { + bool mvcCtl = false; + var skinSrc = string.Empty; + + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; + if (string.IsNullOrEmpty(skinSrc)) + { + skinSrc = PortalSettings.Current.DefaultPortalSkin; + } + } + } + + if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) + { + var tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + skinSrc = tab.SkinSrc; + } + } + + if (!string.IsNullOrEmpty(skinSrc)) + { + mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); + } + + /* + var mvcCtls = new[] { "Module", "Terms", "Privacy" }; + bool mvcSkin = false; + if (context.Items.Contains("PortalSettings")) + { + var ps = (PortalSettings)context.Items["PortalSettings"]; + if (ps != null) + { + mvcSkin = !string.IsNullOrEmpty(PortalSettings.Current.ActiveTab.SkinSrc) && + PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); + } + } + + if (result.RewritePath.Contains("&ctl=")) + { + foreach (var item in mvcCtls) + { + mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); + } + + if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); + } + } + else + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = result.RawUrl.EndsWith("mvc"); + } + + mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; + mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; + */ + + return mvcCtl; + } + } +} From ed9dfad94b7ca22a3d73e339629aa0fe692833bd Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Thu, 20 Mar 2025 20:24:41 +0100 Subject: [PATCH 008/146] Added the controllers for the core website --- .../Controllers/DnnPageController.cs | 13 - .../DotNetNuke.Web.MvcPipeline.csproj | 1 + .../Website/Controllers/DefaultController.cs | 215 +++++++++++ .../Controllers/ModuleActionsController.cs | 53 +++ .../ModuleActionsViewController.cs | 335 ++++++++++++++++++ .../Website/Controllers/PrivacyController.cs | 20 ++ .../Website/Controllers/TermsController.cs | 20 ++ .../Models/ModuleActionsDeleteModel.cs | 9 + .../Website/Models/ModuleActionsModel.cs | 38 ++ 9 files changed, 691 insertions(+), 13 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs index b76dba27e38..2a15fbc9789 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs @@ -4,25 +4,12 @@ namespace DotNetNuke.Web.MvcPipeline.Controllers { - using System; - using System.Text; using System.Web.Mvc; using System.Web.Routing; - using System.Web.UI; - using DotNetNuke.Entities.Modules; - using DotNetNuke.Entities.Modules.Actions; using DotNetNuke.Entities.Portals; using DotNetNuke.Entities.Tabs; - using DotNetNuke.Entities.Users; using DotNetNuke.Services.Localization; - using DotNetNuke.UI.Modules; - - /* - using DotNetNuke.Web.Mvc.Framework.ActionResults; - using DotNetNuke.Web.Mvc.Framework.Modules; - using DotNetNuke.Web.Mvc.Helpers; - */ public abstract class DnnPageController : Controller, IMvcController { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index cd8239fb087..d22f474b8f2 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -45,6 +45,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs new file mode 100644 index 00000000000..46c53b4faeb --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs @@ -0,0 +1,215 @@ +// 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.Web.MvcPipeline.Website.Controllers +{ + using System.Collections.Generic; + using System.Text.RegularExpressions; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common.Utilities; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Installer.Blocker; + using DotNetNuke.Services.Localization; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Web.MvcPipeline.Exceptions; + using DotNetNuke.Web.MvcPipeline.Framework; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + using DotNetNuke.Web.MvcPipeline.Models; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; + + public class DefaultController : DnnPageController + { + private static readonly Regex HeaderTextRegex = new Regex( + "])+name=('|\")robots('|\")", + RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); + + private readonly INavigationManager navigationManager; + private readonly IContentSecurityPolicy contentSecurityPolicy; + private readonly IPageModelFactory pageModelFactory; + + public DefaultController(IContentSecurityPolicy contentSecurityPolicy, INavigationManager navigationManager, IPageModelFactory pageModelFactory) + { + this.contentSecurityPolicy = contentSecurityPolicy; + this.navigationManager = navigationManager; + this.pageModelFactory = pageModelFactory; + } + + public static void RegisterAjaxScript(ControllerContext context) + { + if (MvcServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) + { + MvcServicesFrameworkInternal.Instance.RegisterAjaxScript(context); + } + } + + public ActionResult Page(int tabid, string language) + { + this.HttpContext.Items.Add("CSP-NONCE", this.contentSecurityPolicy.Nonce); + + this.contentSecurityPolicy.DefaultSource.AddSelf(); + this.contentSecurityPolicy.ImgSource.AddSelf(); + this.contentSecurityPolicy.FontSource.AddSelf(); + this.contentSecurityPolicy.StyleSource.AddSelf(); + this.contentSecurityPolicy.FrameSource.AddSelf(); + this.contentSecurityPolicy.ObjectSource.AddNone(); + this.contentSecurityPolicy.BaseUriSource.AddNone(); + this.contentSecurityPolicy.ScriptSource.AddNonce(this.contentSecurityPolicy.Nonce); + this.contentSecurityPolicy.AddReportUri(this.Request.Url.Scheme + "://" + this.Request.Url.Host + "/mvc/Csp/Report"); + + if (this.Request.IsAuthenticated) + { + this.contentSecurityPolicy.FrameSource.AddHost("https://dnndocs.com").AddHost("https://docs.dnncommunity.org"); + } + + // There could be a pending installation/upgrade process + if (InstallBlocker.Instance.IsInstallInProgress()) + { + Exceptions.ProcessHttpException(new HttpException(503, Localization.GetString("SiteAccessedWhileInstallationWasInProgress.Error", Localization.GlobalResourceFile))); + } + + var user = this.PortalSettings.UserInfo; + + if (PortalSettings.Current.UserId > 0) + { + // TODO: should we do this? It creates a dependency towards the PersonaBar which is probably not a great idea + // MvcContentEditorManager.CreateManager(this); + } + + // Configure the ActiveTab with Skin/Container information + PortalSettingsController.Instance().ConfigureActiveTab(this.PortalSettings); + PageModel model = this.pageModelFactory.CreatePageModel(this); + try + { + this.InitializePage(model); + } + catch (MvcPageException ex) + { + if (string.IsNullOrEmpty(ex.RedirectUrl)) + { + return this.HttpNotFound(ex.Message); + } + else + { + return this.Redirect(ex.RedirectUrl); + } + } + + // DotNetNuke.Framework.JavaScriptLibraries.MvcJavaScript.Register(this.ControllerContext); + model.ClientVariables = MvcClientAPI.GetClientVariableList(); + model.StartupScripts = MvcClientAPI.GetClientStartupScriptList(); + + // Register the scripts and stylesheets + this.RegisterScriptsAndStylesheets(model); + + // this.Response.AddHeader("Content-Security-Policy", $"default-src 'self';base-uri 'self';form-action 'self';object-src 'none'; img-src *; style-src 'self' 'unsafe-inline';font-src *; script-src * 'unsafe-inline';"); + return this.View(model.Skin.RazorFile, "Layout", model); + } + + private void RegisterScriptsAndStylesheets(PageModel page) + { + foreach (var styleSheet in page.Skin.RegisteredStylesheets) + { + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, styleSheet.Stylesheet, styleSheet.FileOrder); + } + + foreach (var pane in page.Skin.Panes) + { + foreach (var container in pane.Value.Containers) + { + foreach (var stylesheet in container.Value.RegisteredStylesheets) + { + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, stylesheet.Stylesheet, stylesheet.FileOrder); + } + } + } + + foreach (var script in page.Skin.RegisteredScripts) + { + MvcClientResourceManager.RegisterScript(this.ControllerContext, script); + } + } + + private void InitializePage(PageModel page) + { + // redirect to a specific tab based on name + if (!string.IsNullOrEmpty(this.Request.QueryString["tabname"])) + { + TabInfo tab = TabController.Instance.GetTabByName(this.Request.QueryString["TabName"], this.PortalSettings.PortalId); + if (tab != null) + { + var parameters = new List(); // maximum number of elements + for (int intParam = 0; intParam <= this.Request.QueryString.Count - 1; intParam++) + { + switch (this.Request.QueryString.Keys[intParam].ToLowerInvariant()) + { + case "tabid": + case "tabname": + break; + default: + parameters.Add( + this.Request.QueryString.Keys[intParam] + "=" + this.Request.QueryString[intParam]); + break; + } + } + + // this.Response.Redirect(this.NavigationManager.NavigateURL(tab.TabID, Null.NullString, parameters.ToArray()), true); + throw new MvcPageException("redirect to a specific tab based on name", this.navigationManager.NavigateURL(tab.TabID, Null.NullString, parameters.ToArray())); + } + else + { + // 404 Error - Redirect to ErrorPage + // Exceptions.ProcessHttpException(this.Request); + throw new NotFoundException("redirect to a specific tab based on name - tab not found"); + } + } + + string cacheability = this.Request.IsAuthenticated ? Host.AuthenticatedCacheability : Host.UnauthenticatedCacheability; + + switch (cacheability) + { + case "0": + this.Response.Cache.SetCacheability(HttpCacheability.NoCache); + break; + case "1": + this.Response.Cache.SetCacheability(HttpCacheability.Private); + break; + case "2": + this.Response.Cache.SetCacheability(HttpCacheability.Public); + break; + case "3": + this.Response.Cache.SetCacheability(HttpCacheability.Server); + break; + case "4": + this.Response.Cache.SetCacheability(HttpCacheability.ServerAndNoCache); + break; + case "5": + this.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate); + break; + } + + // Cookie Consent + if (this.PortalSettings.ShowCookieConsent) + { + MvcJavaScript.RegisterClientReference(this.ControllerContext, ClientAPI.ClientNamespaceReferences.dnn); + MvcClientAPI.RegisterClientVariable("cc_morelink", this.PortalSettings.CookieMoreLink, true); + MvcClientAPI.RegisterClientVariable("cc_message", Localization.GetString("cc_message", Localization.GlobalResourceFile), true); + MvcClientAPI.RegisterClientVariable("cc_dismiss", Localization.GetString("cc_dismiss", Localization.GlobalResourceFile), true); + MvcClientAPI.RegisterClientVariable("cc_link", Localization.GetString("cc_link", Localization.GlobalResourceFile), true); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/Resources/Shared/Components/CookieConsent/cookieconsent.min.js", FileOrder.Js.DnnControls); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Resources/Shared/Components/CookieConsent/cookieconsent.min.css", FileOrder.Css.ResourceCss); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/js/dnn.cookieconsent.js", FileOrder.Js.DefaultPriority); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs new file mode 100644 index 00000000000..f48c395d8b2 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs @@ -0,0 +1,53 @@ +// 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.Web.MvcPipeline.Website.Controllers +{ + using System.Web.Mvc; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Log.EventLog; + using DotNetNuke.Web.MvcPipeline.Website.Models; + + public class ModuleActionsController : Controller + { + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult Delete(ModuleActionsDeleteModel model) + { + var module = ModuleController.Instance.GetModule(model.ModuleId, model.TabId, false); + if (module == null) + { + return this.HttpNotFound(); + } + + var portalSettings = PortalSettings.Current; + var user = UserController.Instance.GetCurrentUserInfo(); + if (!module.IsShared) + { + foreach (ModuleInfo instance in ModuleController.Instance.GetTabModulesByModule(module.ModuleID)) + { + if (instance.IsShared) + { + // HARD Delete Shared Instance + ModuleController.Instance.DeleteTabModule(instance.TabID, instance.ModuleID, false); + EventLogController.Instance.AddLog(instance, portalSettings, user.UserID, string.Empty, EventLogController.EventLogType.MODULE_DELETED); + } + } + } + + ModuleController.Instance.DeleteTabModule(model.TabId, model.ModuleId, true); + EventLogController.Instance.AddLog(module, portalSettings, user.UserID, string.Empty, EventLogController.EventLogType.MODULE_SENT_TO_RECYCLE_BIN); + return new EmptyResult(); + } + + protected string LocalizeString(string key) + { + return Localization.GetString(key, Localization.GlobalResourceFile); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs new file mode 100644 index 00000000000..226fe4304b3 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs @@ -0,0 +1,335 @@ +// 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.Web.MvcPipeline.Website.Controllers +{ + using System; + using System.Collections.Generic; + using System.Web.Mvc; + using System.Web.Script.Serialization; + + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + using DotNetNuke.Web.MvcPipeline.Models; + using DotNetNuke.Web.MvcPipeline.Website.Models; + + public class ModuleActionsViewController : Controller + { + private readonly List validIDs = new List(); + private ModuleAction actionRoot; + + private Dictionary actionScripts = new Dictionary(); + + public ModuleInstanceContext ModuleContext { get; private set; } + + public bool EditMode + { + get + { + return Personalization.GetUserMode() != PortalSettings.Mode.View; + } + } + + protected ModuleAction ActionRoot + { + get + { + if (this.actionRoot == null) + { + this.actionRoot = new ModuleAction(this.ModuleContext.GetNextActionID(), Localization.GetString("Manage.Text", Localization.GlobalResourceFile), string.Empty, string.Empty, "manage-icn.png"); + } + + return this.actionRoot; + } + } + + protected string AdminText + { + get { return Localization.GetString("ModuleGenericActions.Action", Localization.GlobalResourceFile); } + } + + protected string CustomText + { + get { return Localization.GetString("ModuleSpecificActions.Action", Localization.GlobalResourceFile); } + } + + protected string MoveText + { + get { return Localization.GetString(ModuleActionType.MoveRoot, Localization.GlobalResourceFile); } + } + + protected PortalSettings PortalSettings + { + get + { + return this.ModuleContext.PortalSettings; + } + } + + protected string AdminActionsJSON { get; set; } + + protected string CustomActionsJSON { get; set; } + + protected bool DisplayQuickSettings { get; set; } + + protected string Panes { get; set; } + + protected bool SupportsMove { get; set; } + + protected bool SupportsQuickSettings { get; set; } + + protected bool IsShared { get; set; } + + protected string ModuleTitle { get; set; } + + protected ModuleActionCollection Actions + { + get + { + return this.ModuleContext.Actions; + } + } + + [ChildActionOnly] + public ActionResult Invoke(ControlViewModel input) + { + var moduleInfo = ModuleController.Instance.GetModule(input.ModuleId, input.TabId, false); + if (moduleInfo.ModuleControlId != input.ModuleControlId) + { + moduleInfo = moduleInfo.Clone(); + moduleInfo.ContainerPath = input.ContainerPath; + moduleInfo.ContainerSrc = input.ContainerSrc; + moduleInfo.ModuleControlId = input.ModuleControlId; + moduleInfo.PaneName = input.PanaName; + moduleInfo.IconFile = input.IconFile; + } + + this.ModuleContext = new ModuleInstanceContext(/*new FakeModuleControl()*/) { Configuration = moduleInfo }; + this.OnInit(); + this.OnLoad(moduleInfo); + + var viewModel = new ModuleActionsModel + { + ModuleContext = moduleInfo, + SupportsQuickSettings = this.SupportsQuickSettings, + DisplayQuickSettings = this.DisplayQuickSettings, + + // QuickSettingsModel = this.qu, + CustomActionsJSON = this.CustomActionsJSON, + AdminActionsJSON = this.AdminActionsJSON, + Panes = this.Panes, + CustomText = this.CustomText, + AdminText = this.AdminText, + MoveText = this.MoveText, + SupportsMove = this.SupportsMove, + IsShared = this.IsShared, + ModuleTitle = moduleInfo.ModuleTitle, + ActionScripts = this.actionScripts, + }; + + return this.View(viewModel); + } + + protected string LocalizeString(string key) + { + return Localization.GetString(key, Localization.GlobalResourceFile); + } + + protected void OnInit() + { + // base.OnInit(e); + // this.ID = "ModuleActions"; + // this.actionButton.Click += this.ActionButton_Click; + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); + + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/admin/menus/ModuleActions/ModuleActions.css", FileOrder.Css.ModuleCss); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Resources/Shared/stylesheets/dnnicons/css/dnnicon.min.css", FileOrder.Css.ModuleCss); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/admin/menus/ModuleActions/ModuleActions.js"); + + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + } + + protected void OnLoad(ModuleInfo moduleInfo) + { + // base.OnLoad(e); + this.ModuleContext = new ModuleInstanceContext() { Configuration = moduleInfo }; + ModuleActionCollection moduleActions = new ModuleActionCollection(); + var desktopModule = DesktopModuleController.GetDesktopModule(moduleInfo.DesktopModuleID, moduleInfo.PortalID); + if (!string.IsNullOrEmpty(desktopModule.BusinessControllerClass)) + { + var businessController = Reflection.CreateType(desktopModule.BusinessControllerClass); + var controlClass = businessController.Namespace + "." + System.IO.Path.GetFileNameWithoutExtension(moduleInfo.ModuleControl.ControlSrc) + "Control," + businessController.Assembly; + try + { + var controller = Reflection.CreateObject(controlClass, controlClass); + + var control = controller as IModuleControl; + control.ModuleContext.Configuration = moduleInfo; + + var actionable = controller as IActionable; + if (actionable != null) + { + moduleActions = actionable.ModuleActions; + } + } + catch (Exception) + { + } + } + + this.ActionRoot.Actions.AddRange(this.Actions); + + var moduleSpecificActions = new ModuleAction(this.ModuleContext.GetNextActionID(), Localization.GetString("ModuleSpecificActions.Action", Localization.GlobalResourceFile), string.Empty, string.Empty, string.Empty); + + foreach (ModuleAction action in moduleActions) + { + if (ModulePermissionController.HasModuleAccess(action.Secure, "CONTENT", this.ModuleContext.Configuration)) + { + if (string.IsNullOrEmpty(action.Icon)) + { + action.Icon = "edit.gif"; + } + + /* + if (action.ID > maxActionId) + { + maxActionId = action.ID; + } + */ + moduleSpecificActions.Actions.Add(action); + + if (!UIUtilities.IsLegacyUI(this.ModuleContext.ModuleId, action.ControlKey, this.ModuleContext.PortalId) && action.Url.Contains("ctl")) + { + action.ClientScript = UrlUtils.PopUpUrl(action.Url, null, this.PortalSettings, true, false); + } + } + } + + if (moduleSpecificActions.Actions.Count > 0) + { + this.ActionRoot.Actions.Add(moduleSpecificActions); + } + + this.AdminActionsJSON = "[]"; + this.CustomActionsJSON = "[]"; + this.Panes = "[]"; + try + { + this.SupportsQuickSettings = false; + this.DisplayQuickSettings = false; + this.ModuleTitle = this.ModuleContext.Configuration.ModuleTitle; + var moduleDefinitionId = this.ModuleContext.Configuration.ModuleDefID; + var quickSettingsControl = ModuleControlController.GetModuleControlByControlKey("QuickSettings", moduleDefinitionId); + + if (quickSettingsControl != null) + { + this.SupportsQuickSettings = true; + /* + var control = ModuleControlFactory.LoadModuleControl(this.Page, this.ModuleContext.Configuration, "QuickSettings", quickSettingsControl.ControlSrc); + control.ID += this.ModuleContext.ModuleId; + this.quickSettings.Controls.Add(control); + + this.DisplayQuickSettings = this.ModuleContext.Configuration.ModuleSettings.GetValueOrDefault("QS_FirstLoad", true); + ModuleController.Instance.UpdateModuleSetting(this.ModuleContext.ModuleId, "QS_FirstLoad", "False"); + + ClientResourceManager.RegisterScript(this.Page, "~/admin/menus/ModuleActions/dnnQuickSettings.js"); + */ + } + + if (this.ActionRoot.Visible) + { + // Add Menu Items + foreach (ModuleAction rootAction in this.ActionRoot.Actions) + { + // Process Children + var actions = new List(); + foreach (ModuleAction action in rootAction.Actions) + { + if (action.Visible) + { + if ((this.EditMode && Globals.IsAdminControl() == false) || + (action.Secure != SecurityAccessLevel.Anonymous && action.Secure != SecurityAccessLevel.View)) + { + if (!action.Icon.Contains("://") + && !action.Icon.StartsWith("/") + && !action.Icon.StartsWith("~/")) + { + action.Icon = "~/images/" + action.Icon; + } + + if (action.Icon.StartsWith("~/")) + { + action.Icon = Globals.ResolveUrl(action.Icon); + } + + actions.Add(action); + + if (string.IsNullOrEmpty(action.Url)) + { + this.validIDs.Add(action.ID); + } + } + } + + if (string.IsNullOrEmpty(action.ClientScript) && !string.IsNullOrEmpty(action.Url) && action.Url.StartsWith("javascript:")) + { + if (!UIUtilities.IsLegacyUI(this.ModuleContext.ModuleId, action.ControlKey, this.ModuleContext.PortalId)) + { + action.ClientScript = UrlUtils.PopUpUrl(action.Url, null, this.PortalSettings, true, false); + } + } + + if (!string.IsNullOrEmpty(action.ClientScript) && !string.IsNullOrEmpty(action.Url)) + { + this.actionScripts.Add(action.Url, action.ClientScript); + } + } + + var oSerializer = new JavaScriptSerializer(); + if (rootAction.Title == Localization.GetString("ModuleGenericActions.Action", Localization.GlobalResourceFile)) + { + this.AdminActionsJSON = oSerializer.Serialize(actions); + } + else + { + if (rootAction.Title == Localization.GetString("ModuleSpecificActions.Action", Localization.GlobalResourceFile)) + { + this.CustomActionsJSON = oSerializer.Serialize(actions); + } + else + { + this.SupportsMove = actions.Count > 0; + this.Panes = oSerializer.Serialize(this.PortalSettings.ActiveTab.Panes); + } + } + } + + this.IsShared = this.ModuleContext.Configuration.AllTabs + || PortalGroupController.Instance.IsModuleShared(this.ModuleContext.ModuleId, PortalController.Instance.GetPortal(this.PortalSettings.PortalId)) + || TabController.Instance.GetTabsByModuleID(this.ModuleContext.ModuleId).Count > 1; + } + } + catch (Exception exc) + { + // Exceptions.ProcessModuleLoadException(this, exc); + throw exc; + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs new file mode 100644 index 00000000000..836e9a137c7 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs @@ -0,0 +1,20 @@ +// 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.Web.MvcPipeline.Website.Controllers +{ + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + + public class PrivacyController : Controller + { + public ActionResult Index() + { + var privacy = Localization.GetSystemMessage(PortalSettings.Current, "MESSAGE_PORTAL_PRIVACY"); + return this.View("Index", string.Empty, privacy); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs new file mode 100644 index 00000000000..ad6f026e64e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs @@ -0,0 +1,20 @@ +// 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.Web.MvcPipeline.Website.Controllers +{ + using System.Web.Mvc; + + using DotNetNuke.Entities.Portals; + using DotNetNuke.Services.Localization; + + public class TermsController : Controller + { + public ActionResult Index() + { + var terms = Localization.GetSystemMessage(PortalSettings.Current, "MESSAGE_PORTAL_TERMS"); + return this.View("Index", string.Empty, terms); + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs new file mode 100644 index 00000000000..e0e1defe7fe --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs @@ -0,0 +1,9 @@ +namespace DotNetNuke.Web.MvcPipeline.Website.Models +{ + public class ModuleActionsDeleteModel + { + public int ModuleId { get; set; } + + public int TabId { get; set; } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs new file mode 100644 index 00000000000..9780f85abdf --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs @@ -0,0 +1,38 @@ +namespace DotNetNuke.Web.MvcPipeline.Website.Models +{ + using System.Collections.Generic; + + using DotNetNuke.Entities.Modules; + + public class ModuleActionsModel + { + // public ModuleInstanceContext ModuleContext { get; internal set; } + public ModuleInfo ModuleContext { get; set; } + + public bool SupportsQuickSettings { get; set; } + + public bool DisplayQuickSettings { get; set; } + + public object QuickSettingsModel { get; set; } + + public string CustomActionsJSON { get; set; } + + public string AdminActionsJSON { get; set; } + + public string Panes { get; set; } + + public string CustomText { get; set; } + + public string AdminText { get; set; } + + public string MoveText { get; set; } + + public bool SupportsMove { get; set; } + + public bool IsShared { get; set; } + + public string ModuleTitle { get; set; } + + public Dictionary ActionScripts { get; set; } + } +} From 0b39bb27d0906a87d0745eef3e0a558c4340f029 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Fri, 21 Mar 2025 14:23:27 +0100 Subject: [PATCH 009/146] NewDDRMenu project which forks the original DDR menu project with customizations for the MVC pipeline. --- .cursorrules | 72 ++ .../Library/Properties/AssemblyInfoMvc.cs | 1 + DNN Platform/Modules/NewDDRMenu/02.00.03.txt | 1 + DNN Platform/Modules/NewDDRMenu/Actions.ascx | 1 + DNN Platform/Modules/NewDDRMenu/Actions.cs | 221 ++++ .../App_LocalResources/MenuSettings.ascx.resx | 129 ++ .../App_LocalResources/MenuView.ascx.resx | 126 ++ .../Modules/NewDDRMenu/App_Start/Startup.cs | 21 + .../BuildScripts/ModulePackage.targets | 218 ++++ .../NewDDRMenu/BuildScripts/version.txt | 1 + .../Modules/NewDDRMenu/Common/DNNContext.cs | 138 +++ .../Modules/NewDDRMenu/Common/PathResolver.cs | 148 +++ .../Modules/NewDDRMenu/Common/Utilities.cs | 127 ++ DNN Platform/Modules/NewDDRMenu/Controller.cs | 219 ++++ .../Modules/NewDDRMenu/DNNAbstract.cs | 110 ++ .../NewDDRMenu/DNNMenu/DNNMenu-menudef.xml | 11 + .../Modules/NewDDRMenu/DNNMenu/DNNMenu.js | 239 ++++ .../Modules/NewDDRMenu/DNNMenu/DNNMenu.min.js | 1 + .../Modules/NewDDRMenu/DefaultTemplate.xslt | 62 + .../DotNetNuke.Modules.NewDDRMenu.csproj | 295 +++++ .../NewDDRMenu/DumpXML/DumpXML-menudef.xml | 4 + .../Modules/NewDDRMenu/DumpXML/DumpXML.xslt | 17 + .../Modules/NewDDRMenu/INodeManipulator.cs | 19 + DNN Platform/Modules/NewDDRMenu/License.txt | 20 + .../NewDDRMenu/Localisation/Generic.cs | 101 ++ .../NewDDRMenu/Localisation/ILocalisation.cs | 28 + .../NewDDRMenu/Localisation/ILocaliser.cs | 23 + .../NewDDRMenu/Localisation/Localiser.cs | 119 ++ DNN Platform/Modules/NewDDRMenu/Menu.ascx | 1 + DNN Platform/Modules/NewDDRMenu/MenuBase.cs | 439 +++++++ DNN Platform/Modules/NewDDRMenu/MenuNode.cs | 506 ++++++++ .../Modules/NewDDRMenu/MenuSettings.ascx | 86 ++ .../Modules/NewDDRMenu/MenuSettings.ascx.cs | 55 + .../NewDDRMenu/MenuSettings.ascx.designer.cs | 96 ++ DNN Platform/Modules/NewDDRMenu/MenuView.ascx | 1 + .../Modules/NewDDRMenu/MenuView.ascx.cs | 120 ++ .../NewDDRMenu/MenuView.ascx.designer.cs | 19 + DNN Platform/Modules/NewDDRMenu/MenuXml.cs | 27 + DNN Platform/Modules/NewDDRMenu/Module.build | 42 + DNN Platform/Modules/NewDDRMenu/ModuleBase.cs | 65 + .../Modules/NewDDRMenu/MvcMenuBase.cs | 418 +++++++ .../Modules/NewDDRMenu/NewDDRMenu.dnn | 109 ++ .../Modules/NewDDRMenu/NewDDRMenuControl.cs | 88 ++ .../NewDDRMenuNavigationProvider.cs | 402 +++++++ .../NewDDRMenu/Properties/AssemblyInfo.cs | 16 + .../Modules/NewDDRMenu/ReleaseNotes.txt | 181 +++ DNN Platform/Modules/NewDDRMenu/Settings.cs | 130 ++ .../Modules/NewDDRMenu/SkinHelpers.cs | 97 ++ DNN Platform/Modules/NewDDRMenu/SkinObject.cs | 129 ++ .../TemplateEngine/ClientBoolean.cs | 19 + .../NewDDRMenu/TemplateEngine/ClientNumber.cs | 19 + .../NewDDRMenu/TemplateEngine/ClientOption.cs | 28 + .../NewDDRMenu/TemplateEngine/ClientString.cs | 19 + .../TemplateEngine/ITemplateProcessor.cs | 15 + .../TemplateEngine/RazorTemplateProcessor.cs | 109 ++ .../TemplateEngine/TemplateArgument.cs | 27 + .../TemplateEngine/TemplateDefinition.cs | 404 +++++++ .../TemplateEngine/TokenTemplateProcessor.cs | 224 ++++ .../TemplateEngine/XsltFunctions.cs | 121 ++ .../TemplateEngine/XsltTemplateProcessor.cs | 108 ++ .../Modules/NewDDRMenu/js/NewDDRMenu.js | 863 +++++++++++++ .../Modules/NewDDRMenu/js/jquery.effects.js | 1069 +++++++++++++++++ .../Modules/NewDDRMenu/packages.config | 14 + .../Modules/NewDDRMenu/web.Debug.config | 30 + .../Modules/NewDDRMenu/web.Release.config | 31 + DNN Platform/Modules/NewDDRMenu/web.config | 63 + DNN_Platform.sln | 27 + 67 files changed, 8689 insertions(+) create mode 100644 .cursorrules create mode 100644 DNN Platform/Modules/NewDDRMenu/02.00.03.txt create mode 100644 DNN Platform/Modules/NewDDRMenu/Actions.ascx create mode 100644 DNN Platform/Modules/NewDDRMenu/Actions.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuSettings.ascx.resx create mode 100644 DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuView.ascx.resx create mode 100644 DNN Platform/Modules/NewDDRMenu/App_Start/Startup.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/BuildScripts/ModulePackage.targets create mode 100644 DNN Platform/Modules/NewDDRMenu/BuildScripts/version.txt create mode 100644 DNN Platform/Modules/NewDDRMenu/Common/DNNContext.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Common/PathResolver.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Common/Utilities.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Controller.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/DNNAbstract.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu-menudef.xml create mode 100644 DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.js create mode 100644 DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.min.js create mode 100644 DNN Platform/Modules/NewDDRMenu/DefaultTemplate.xslt create mode 100644 DNN Platform/Modules/NewDDRMenu/DotNetNuke.Modules.NewDDRMenu.csproj create mode 100644 DNN Platform/Modules/NewDDRMenu/DumpXML/DumpXML-menudef.xml create mode 100644 DNN Platform/Modules/NewDDRMenu/DumpXML/DumpXML.xslt create mode 100644 DNN Platform/Modules/NewDDRMenu/INodeManipulator.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/License.txt create mode 100644 DNN Platform/Modules/NewDDRMenu/Localisation/Generic.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Localisation/ILocalisation.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Localisation/ILocaliser.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Localisation/Localiser.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Menu.ascx create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuBase.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuNode.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuSettings.ascx create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuSettings.ascx.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuSettings.ascx.designer.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuView.ascx create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuView.ascx.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuView.ascx.designer.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MenuXml.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Module.build create mode 100644 DNN Platform/Modules/NewDDRMenu/ModuleBase.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn create mode 100644 DNN Platform/Modules/NewDDRMenu/NewDDRMenuControl.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/NewDDRMenuNavigationProvider.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/Properties/AssemblyInfo.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/ReleaseNotes.txt create mode 100644 DNN Platform/Modules/NewDDRMenu/Settings.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/SkinHelpers.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/SkinObject.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/ClientBoolean.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/ClientNumber.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/ClientOption.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/ClientString.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/ITemplateProcessor.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/RazorTemplateProcessor.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/TemplateArgument.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/TemplateDefinition.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/TokenTemplateProcessor.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/XsltFunctions.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/TemplateEngine/XsltTemplateProcessor.cs create mode 100644 DNN Platform/Modules/NewDDRMenu/js/NewDDRMenu.js create mode 100644 DNN Platform/Modules/NewDDRMenu/js/jquery.effects.js create mode 100644 DNN Platform/Modules/NewDDRMenu/packages.config create mode 100644 DNN Platform/Modules/NewDDRMenu/web.Debug.config create mode 100644 DNN Platform/Modules/NewDDRMenu/web.Release.config create mode 100644 DNN Platform/Modules/NewDDRMenu/web.config diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000000..216c4271161 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,72 @@ + +# .NET Development Rules + +You are a senior .NET full stack developer and an expert in C#, ASP.NET Framework, ASP.NET Core, and ReactJS + +## This project + +This is a CMS that runs on classic ASP.NET Framework (version 4.8). The default entry point is /default.aspx which can be found under /DNN Platform/Website. +We are building a new pipeline based on ASP.NET MVC with the goal of moving away from webforms and towards .NET Core. Right now we will create a hybrid solution +with two rendering mechanisms: the old webforms pipeline through default.aspx and a new pipeline through /DesktopModules/Default/Page/{tabId}/{locale}. This +should be picked up and handled in the DotNetNuke.Web.MvcPipeline library. The skin that is being loaded should decide which pipeline is being used to render a +page to the client. + +## Code Organization + +We want to minimize any code changes to the existing project and concentrate as much as we can any code changes in the DotNetNuke.Web.MvcPipeline project. +If need be we can create more projects to house new code. + +## Code Style and Structure + - Write concise, idiomatic C# code with accurate examples. + - Follow .NET and ASP.NET Core conventions and best practices. + - Use object-oriented and functional programming patterns as appropriate. + - Prefer LINQ and lambda expressions for collection operations. + - Use descriptive variable and method names (e.g., 'IsUserSignedIn', 'CalculateTotal'). + - Structure files according to .NET conventions (Controllers, Models, Services, etc.). + +## Naming Conventions + - Follow guidelines from the stylecop.json file + +## C# and .NET Usage + - + +## Syntax and Formatting + - Use 'var' for implicit typing when the type is obvious. + +## Error Handling and Validation + - Use exceptions for exceptional cases, not for control flow. + - Implement proper error logging using built-in .NET logging or a third-party logger. + - Use Data Annotations or Fluent Validation for model validation. + - Implement global exception handling middleware. + - Return appropriate HTTP status codes and consistent error responses. + +## API Design + - Follow RESTful API design principles. + - Use attribute routing in controllers. + - Implement versioning for your API. + - Use action filters for cross-cutting concerns. + +## Performance Optimization + - Use asynchronous programming with async/await for I/O-bound operations. + - Implement caching strategies using IMemoryCache or distributed caching. + - Use efficient LINQ queries and avoid N+1 query problems. + - Implement pagination for large data sets. + +## Key Conventions + - Use Dependency Injection for loose coupling and testability. + - Implement repository pattern. + - Use AutoMapper for object-to-object mapping if needed. + - Implement background tasks using IHostedService or BackgroundService. + +## Testing + - Write unit tests using xUnit, NUnit, or MSTest. + - Use Moq or NSubstitute for mocking dependencies. + - Implement integration tests for API endpoints. + +## Security + - Implement proper CORS policies. + +## API Documentation + - Provide XML comments for controllers and models. + +Follow the official Microsoft documentation and ASP.NET MVC guides for best practices in routing, controllers, models, and other API components. diff --git a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs index f19423773d7..f394d157580 100644 --- a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs +++ b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs @@ -7,3 +7,4 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: InternalsVisibleTo("DotNetNuke.Web.MvcPipeline")] +[assembly: InternalsVisibleTo("DotNetNuke.Web.NewDDRMenu")] // Once Globals is refactored to Dependency Injection we should be able to remove this diff --git a/DNN Platform/Modules/NewDDRMenu/02.00.03.txt b/DNN Platform/Modules/NewDDRMenu/02.00.03.txt new file mode 100644 index 00000000000..1b1915c47c8 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/02.00.03.txt @@ -0,0 +1 @@ +bin\Providers\DotNetNuke.Web.NewDDRMenu.NET40.dll diff --git a/DNN Platform/Modules/NewDDRMenu/Actions.ascx b/DNN Platform/Modules/NewDDRMenu/Actions.ascx new file mode 100644 index 00000000000..d88cbaf34ca --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Actions.ascx @@ -0,0 +1 @@ +<%@ Control Language="C#" AutoEventWireup="false" EnableViewState="false" Inherits="DotNetNuke.Web.NewDDRMenu.Actions" %> diff --git a/DNN Platform/Modules/NewDDRMenu/Actions.cs b/DNN Platform/Modules/NewDDRMenu/Actions.cs new file mode 100644 index 00000000000..bed2d493c0d --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Actions.cs @@ -0,0 +1,221 @@ +// 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.Web.NewDDRMenu +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Web.UI; + + using DotNetNuke.Common; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Modules.NavigationProvider; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.UI; + using DotNetNuke.UI.Containers; + using DotNetNuke.UI.WebControls; + using DotNetNuke.Web.NewDDRMenu.DNNCommon; + using DotNetNuke.Web.NewDDRMenu.TemplateEngine; + + /// Represents DDR Menu Actions. + public class Actions : ActionBase + { + private readonly IServiceProvider serviceProvider; + private NewDDRMenuNavigationProvider navProvider; + private Dictionary actions; + + /// Initializes a new instance of the class. + [Obsolete("Deprecated in DotNetNuke 10.0.0. Please use overload with IServiceProvider. Scheduled removal in v12.0.0.")] + public Actions() + : this(Globals.DependencyProvider) + { + } + + /// Initializes a new instance of the class. + /// The DI container. + public Actions(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + /// Gets or sets the path system script. + public string PathSystemScript { get; set; } + + /// Gets or sets the menu style. + public string MenuStyle { get; set; } + + /// Gets or sets the client options. + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + [PersistenceMode(PersistenceMode.InnerProperty)] + public List ClientOptions { get; set; } + + /// Gets or sets the template arguments. + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + [PersistenceMode(PersistenceMode.InnerProperty)] + public List TemplateArguments { get; set; } + + /// + protected override void OnInit(EventArgs e) + { + using (new DNNContext(this)) + { + base.OnInit(e); + + this.navProvider = (NewDDRMenuNavigationProvider)NavigationProvider.Instance(this.serviceProvider, "NewDDRMenuNavigationProvider"); + this.navProvider.ControlID = "ctl" + this.ID; + this.navProvider.MenuStyle = this.MenuStyle; + this.navProvider.Initialize(); + + this.Controls.Add(this.navProvider.NavigationControl); + } + } + + /// + protected override void OnLoad(EventArgs e) + { + using (new DNNContext(this)) + { + base.OnLoad(e); + + this.SetMenuDefaults(); + } + } + + /// + protected override void OnPreRender(EventArgs e) + { + using (new DNNContext(this)) + { + base.OnPreRender(e); + + try + { + this.navProvider.TemplateArguments = this.TemplateArguments; + this.BindMenu(Navigation.GetActionNodes(this.ActionRoot, this, -1)); + } + catch (Exception exc) + { + Exceptions.ProcessModuleLoadException(this, exc); + } + } + } + + private void BindMenu(DNNNodeCollection objNodes) + { + this.Visible = this.DisplayControl(objNodes); + if (!this.Visible) + { + return; + } + + this.navProvider.ClearNodes(); + foreach (DNNNode node in objNodes) + { + this.ProcessNode(node); + } + + this.navProvider.Bind(objNodes, false); + } + + private void ActionClick(NavigationEventArgs args) + { + using (new DNNContext(this)) + { + try + { + this.ProcessAction(args.ID); + } + catch (Exception exc) + { + Exceptions.ProcessModuleLoadException(this, exc); + } + } + } + + private void AddActionIDs(ModuleAction action) + { + if (!this.actions.ContainsKey(action.ID)) + { + this.actions.Add(action.ID, action); + } + + if (action.HasChildren()) + { + foreach (ModuleAction a in action.Actions) + { + this.AddActionIDs(a); + } + } + } + + private ModuleAction FindAction(int id) + { + if (this.actions == null) + { + this.actions = new Dictionary(); + this.AddActionIDs(this.ActionRoot); + } + + ModuleAction result; + return this.actions.TryGetValue(id, out result) ? result : null; + } + + private void ProcessNode(DNNNode dnnNode) + { + if (!dnnNode.IsBreak) + { + var action = this.FindAction(Convert.ToInt32(dnnNode.Key)); + if (action != null) + { + dnnNode.set_CustomAttribute("CommandName", action.CommandName); + dnnNode.set_CustomAttribute("CommandArgument", action.CommandArgument); + } + } + + if (!string.IsNullOrEmpty(dnnNode.JSFunction)) + { + dnnNode.JSFunction = string.Format( + "if({0}){{{1}}};", + dnnNode.JSFunction, + this.Page.ClientScript.GetPostBackEventReference(this.navProvider.NavigationControl, dnnNode.ID)); + } + + foreach (DNNNode node in dnnNode.DNNNodes) + { + this.ProcessNode(node); + } + } + + private void SetMenuDefaults() + { + try + { + this.navProvider.StyleIconWidth = 15M; + this.navProvider.MouseOutHideDelay = 500M; + this.navProvider.MouseOverAction = NavigationProvider.HoverAction.Expand; + this.navProvider.MouseOverDisplay = NavigationProvider.HoverDisplay.None; + this.navProvider.CSSControl = "ModuleTitle_MenuBar"; + this.navProvider.CSSContainerRoot = "ModuleTitle_MenuContainer"; + this.navProvider.CSSNode = "ModuleTitle_MenuItem"; + this.navProvider.CSSIcon = "ModuleTitle_MenuIcon"; + this.navProvider.CSSContainerSub = "ModuleTitle_SubMenu"; + this.navProvider.CSSBreak = "ModuleTitle_MenuBreak"; + this.navProvider.CSSNodeHover = "ModuleTitle_MenuItemSel"; + this.navProvider.CSSIndicateChildSub = "ModuleTitle_MenuArrow"; + this.navProvider.CSSIndicateChildRoot = "ModuleTitle_RootMenuArrow"; + this.navProvider.PathImage = Globals.ApplicationPath + "/Images/"; + this.navProvider.PathSystemImage = Globals.ApplicationPath + "/Images/"; + this.navProvider.IndicateChildImageSub = "action_right.gif"; + this.navProvider.IndicateChildren = true; + this.navProvider.StyleRoot = "background-color: Transparent; font-size: 1pt;"; + this.navProvider.NodeClick += this.ActionClick; + } + catch (Exception exc) + { + Exceptions.ProcessModuleLoadException(this, exc); + } + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuSettings.ascx.resx b/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuSettings.ascx.resx new file mode 100644 index 00000000000..d54ef0a3187 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuSettings.ascx.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add Content + + + NewDDRMenu documentation available on the <a href="http://www.dnnsoftware.com/wiki/">DNN wiki</a>. + + + Menu settings + + diff --git a/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuView.ascx.resx b/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuView.ascx.resx new file mode 100644 index 00000000000..58f014e74d9 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/App_LocalResources/MenuView.ascx.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add Content + + + NewDDRMenu documentation available on the <a href="http://www.dnnsoftware.com/wiki/">DNN wiki</a>. + + diff --git a/DNN Platform/Modules/NewDDRMenu/App_Start/Startup.cs b/DNN Platform/Modules/NewDDRMenu/App_Start/Startup.cs new file mode 100644 index 00000000000..6131f84f7b6 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/App_Start/Startup.cs @@ -0,0 +1,21 @@ +// 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.Web.NewDDRMenu +{ + using DotNetNuke.DependencyInjection; + using DotNetNuke.Web.NewDDRMenu.Localisation; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + + /// Registers DDR Menu services. + public class Startup : IDnnStartup + { + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/BuildScripts/ModulePackage.targets b/DNN Platform/Modules/NewDDRMenu/BuildScripts/ModulePackage.targets new file mode 100644 index 00000000000..4e08f58b2e4 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/BuildScripts/ModulePackage.targets @@ -0,0 +1,218 @@ + + + + + + + + + + + + + NewDDRMenu.dnn + NewDDRMenu + $(MSBuildProjectDirectory)\Install + $(MSBuildProjectDirectory)\bin + E:\websites\DotNetNuke_Enterprise_07.00.00_Install + http://dnndev.me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DNN Platform/Modules/NewDDRMenu/BuildScripts/version.txt b/DNN Platform/Modules/NewDDRMenu/BuildScripts/version.txt new file mode 100644 index 00000000000..9c1c5ffbd2d --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/BuildScripts/version.txt @@ -0,0 +1 @@ +2.1.0.0 diff --git a/DNN Platform/Modules/NewDDRMenu/Common/DNNContext.cs b/DNN Platform/Modules/NewDDRMenu/Common/DNNContext.cs new file mode 100644 index 00000000000..b598e3b17da --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Common/DNNContext.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.Web.NewDDRMenu.DNNCommon +{ + using System; + using System.IO; + using System.Reflection; + using System.Web; + using System.Web.UI; + + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Tabs; + + /// Provides Dnn context for the DDR Menu. + public class DNNContext : IDisposable + { + private static string moduleName; + private static string moduleFolder; + private static string dataName; + + private readonly DNNContext savedContext; + private bool isDisposed; + private Page page; + private PortalSettings portalSettings; + private TabInfo activeTab; + private string skinPath; + + /// Initializes a new instance of the class. + /// The control that hosts the menu. + public DNNContext(Control hostControl) + { + this.HostControl = hostControl; + + this.savedContext = Current; + Current = this; + } + + /// Gets the module name. + public static string ModuleName + { + get { return moduleName ?? (moduleName = GetModuleNameFromAssembly()); } + } + + /// Gets the module folder. + public static string ModuleFolder + { + get + { + return moduleFolder ?? + (moduleFolder = + string.Format( + "~/DesktopModules/{0}/", DesktopModuleController.GetDesktopModuleByModuleName(ModuleName, PortalSettings.Current.PortalId).FolderName)); + } + } + + /// Gets the current Dnn context. + public static DNNContext Current + { + get { return (DNNContext)HttpContext.Current.Items[DataName]; } + private set { HttpContext.Current.Items[DataName] = value; } + } + + /// Gets a reference to the page. + public Page Page + { + get { return this.page ?? (this.page = this.HostControl.Page); } + } + + /// Gets the current portal settings. + public PortalSettings PortalSettings + { + get { return this.portalSettings ?? (this.portalSettings = (PortalSettings)PortalController.Instance.GetCurrentSettings()); } + } + + /// Gets the currently active tab (page). + public TabInfo ActiveTab + { + get { return this.activeTab ?? (this.activeTab = this.PortalSettings.ActiveTab); } + } + + /// Gets the path to the skin (theme). + public string SkinPath + { + get { return this.skinPath ?? (this.skinPath = this.ActiveTab.SkinPath); } + } + + /// Gets the host control. + public Control HostControl { get; private set; } + + private static string DataName + { + get { return dataName ?? (dataName = "NewDDRMenu.DNNContext." + ModuleName); } + } + + /// Converts a url into one that is usable on the requesting Client. + /// The relative url. + /// The converted url. + public string ResolveUrl(string relativeUrl) + { + return this.HostControl.ResolveUrl(relativeUrl); + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// Disposes this instance resources. + /// A value indicating if the current instance is disposing. + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // free managed resources. + } + + Current = this.savedContext; + this.isDisposed = true; + } + + private static string GetModuleNameFromAssembly() + { + var moduleFullName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase); + + return moduleFullName.Substring(moduleFullName.LastIndexOf('.') + 1); + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/Common/PathResolver.cs b/DNN Platform/Modules/NewDDRMenu/Common/PathResolver.cs new file mode 100644 index 00000000000..fd8e787d4a2 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Common/PathResolver.cs @@ -0,0 +1,148 @@ +// 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.Web.NewDDRMenu.DNNCommon +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Web; + + /// Used to resolved paths. + public class PathResolver + { + private readonly string manifestFolder; + + /// Initializes a new instance of the class. + /// The folder where the manifest is located. + public PathResolver(string manifestFolder) + { + this.manifestFolder = manifestFolder; + } + + /// To which folder is the path relative to. + public enum RelativeTo + { + /// Relative to the container. + Container = 0, + + /// Relative to the Dnn application. + Dnn = 1, + + /// Relative to the manifest. + Manifest = 2, + + /// Relative to the module. + Module = 3, + + /// Relative to the portal (site). + Portal = 4, + + /// Relative to the skin (theme). + Skin = 5, + } + + /// Resolves a relative path. + /// The path to resolve. + /// To which folder this path is relative to. + /// A resolved path. + public string Resolve(string path, params RelativeTo[] roots) + { + var context = DNNContext.Current; + + var mappings = new Dictionary + { + { "[NewDDRMenu]", RelativeTo.Module }, + { "[MODULE]", RelativeTo.Module }, + { "[MANIFEST]", RelativeTo.Manifest }, + { "[PORTAL]", RelativeTo.Portal }, + { "[SKIN]", RelativeTo.Skin }, + { "[CONTAINER]", RelativeTo.Container }, + { "[DNN]", RelativeTo.Dnn }, + }; + foreach (var key in mappings.Keys) + { + if (path.StartsWith(key, StringComparison.InvariantCultureIgnoreCase)) + { + path = path.Substring(key.Length); + roots = new[] { mappings[key] }; + break; + } + } + + if (path.StartsWith("/")) + { + path = path.Substring(1); + } + + if (!path.StartsWith("~") && !path.Contains(":")) + { + foreach (var root in roots) + { + string resolvedPath = null; + switch (root) + { + case RelativeTo.Container: + var container = context.HostControl; + while ((container != null) && !(container is UI.Containers.Container)) + { + container = container.Parent; + } + + var containerRoot = (container == null) + ? context.SkinPath + : Path.GetDirectoryName(((UI.Containers.Container)container).AppRelativeVirtualPath).Replace('\\', '/') + "/"; + resolvedPath = Path.Combine(containerRoot, path); + break; + case RelativeTo.Dnn: + resolvedPath = Path.Combine("~/", path); + break; + case RelativeTo.Manifest: + if (!string.IsNullOrEmpty(this.manifestFolder)) + { + resolvedPath = Path.Combine(this.manifestFolder + "/", path); + } + + break; + case RelativeTo.Module: + resolvedPath = Path.Combine(DNNContext.ModuleFolder, path); + break; + case RelativeTo.Portal: + resolvedPath = Path.Combine(context.PortalSettings.HomeDirectory, path); + break; + case RelativeTo.Skin: + resolvedPath = Path.Combine(context.SkinPath, path); + break; + } + + if (!string.IsNullOrEmpty(resolvedPath)) + { + var sep = resolvedPath.LastIndexOf('/'); + var dirName = resolvedPath.Substring(0, sep + 1); + var fileName = resolvedPath.Substring(sep + 1); + + var mappedDir = HttpContext.Current.Server.MapPath(dirName); + mappedDir = mappedDir.Remove(mappedDir.Length - 1); + if (Directory.Exists(mappedDir)) + { + if (string.IsNullOrEmpty(fileName)) + { + return resolvedPath.Replace('\\', '/'); + } + + var matches = Directory.GetFileSystemEntries(mappedDir, fileName); + if (matches.Length > 0) + { + resolvedPath = (dirName + Path.GetFileName(matches[0])).Replace('\\', '/'); + return resolvedPath; + } + } + } + } + } + + return path; + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/Common/Utilities.cs b/DNN Platform/Modules/NewDDRMenu/Common/Utilities.cs new file mode 100644 index 00000000000..f849c7567ee --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Common/Utilities.cs @@ -0,0 +1,127 @@ +// 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.Web.NewDDRMenu.DNNCommon +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Web; + using System.Web.Caching; + using System.Xml; + using System.Xml.Serialization; + using System.Xml.Xsl; + + /// Provides various utilities for the DDN Menu. + internal static class Utilities + { + private static readonly Dictionary Serialisers = new Dictionary(); + + /// A cached string representing a file contents. + /// The name of the file to cache. + /// A cached version of the file contents in a string. + internal static string CachedFileContent(string filename) + { + var cache = HttpContext.Current.Cache; + var result = cache[filename] as string; + if (result == null) + { + result = File.ReadAllText(filename); + cache.Insert(filename, result, new CacheDependency(filename)); + } + + return result; + } + + /// Gets a the cached content of an sml file. + /// The xml file name. + /// A representing the cached file contents. + internal static XmlDocument CachedXml(string filename) + { + var cache = HttpContext.Current.Cache; + var result = cache[filename] as XmlDocument; + if (result == null) + { + result = new XmlDocument { XmlResolver = null }; + result.Load(filename); + cache.Insert(filename, result, new CacheDependency(filename)); + } + + return result; + } + + /// Gets a cached version of an xslt tranformation script. + /// The name of the xslt file. + /// An object with the content of cached content of the file. + internal static XslCompiledTransform CachedXslt(string filename) + { + var cache = HttpContext.Current.Cache; + var result = cache[filename] as XslCompiledTransform; + if (result == null) + { + result = new XslCompiledTransform(); + using (var reader = XmlReader.Create(filename, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore })) + { + result.Load(reader); + } + + cache.Insert(filename, result, new CacheDependency(filename)); + } + + return result; + } + + /// Gets an xml serializer for a given type. + /// The type of the serializer. + /// An . + internal static XmlSerializer SerialiserFor(Type t) + { + XmlSerializer result; + if (!Serialisers.TryGetValue(t, out result)) + { + lock (Serialisers) + { + result = (t.Name == "MenuXml") + ? new XmlSerializer(t, new XmlRootAttribute { ElementName = "Root" }) + : new XmlSerializer(t); + if (!Serialisers.ContainsKey(t)) + { + Serialisers.Add(t, result); + } + } + } + + return result; + } + + /// Converts an object to a string representation that can be used in javascript. + /// The object to convert. + /// A string representation of the object. + internal static string ConvertToJs(object obj) + { + string result; + + if (obj == null) + { + return "null"; + } + + var objType = obj.GetType(); + if (objType == typeof(bool)) + { + result = (bool)obj ? "true" : "false"; + } + else if (objType == typeof(int) || objType == typeof(decimal) || objType == typeof(double)) + { + result = obj.ToString(); + } + else + { + result = string.Format("\"{0}\"", obj.ToString().Replace("\"", "\\\"")); + } + + return result; + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/Controller.cs b/DNN Platform/Modules/NewDDRMenu/Controller.cs new file mode 100644 index 00000000000..9a8957327b7 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/Controller.cs @@ -0,0 +1,219 @@ +// 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.Web.NewDDRMenu +{ + using System; + using System.IO; + using System.Security; + using System.Text.RegularExpressions; + using System.Web; + using System.Xml; + + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Definitions; + + /// Implements the Dnn interfaces for the module. + public class Controller : IUpgradeable, IPortable + { + private const string NewDDRMenuModuleName = "NewDDRMenu"; + private const string NewDDRMenuMmoduleDefinitionName = "DDR Menu"; + + /// + public string UpgradeModule(string version) + { + UpdateWebConfig(); + + TidyModuleDefinitions(); + + CleanOldAssemblies(); + + CheckSkinReferences(); + + return "UpgradeModule completed OK"; + } + + /// + public string ExportModule(int moduleId) + { + var module = ModuleController.Instance.GetModule(moduleId, Null.NullInteger, true); + var moduleSettings = module.ModuleSettings; + + var settings = new Settings + { + MenuStyle = Convert.ToString(moduleSettings["MenuStyle"]), + NodeXmlPath = Convert.ToString(moduleSettings["NodeXmlPath"]), + NodeSelector = Convert.ToString(moduleSettings["NodeSelector"]), + IncludeNodes = Convert.ToString(moduleSettings["IncludeNodes"]), + ExcludeNodes = Convert.ToString(moduleSettings["ExcludeNodes"]), + NodeManipulator = Convert.ToString(moduleSettings["NodeManipulator"]), + IncludeContext = Convert.ToBoolean(moduleSettings["IncludeContext"]), + IncludeHidden = Convert.ToBoolean(moduleSettings["IncludeHidden"]), + ClientOptions = Settings.ClientOptionsFromSettingString(Convert.ToString(moduleSettings["ClientOptions"])), + TemplateArguments = + Settings.TemplateArgumentsFromSettingString(Convert.ToString(moduleSettings["TemplateArguments"])), + }; + return settings.ToXml(); + } + + /// + public void ImportModule(int moduleId, string content, string version, int userId) + { + var settings = Settings.FromXml(content); + + ModuleController.Instance.UpdateModuleSetting(moduleId, "MenuStyle", settings.MenuStyle ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "NodeXmlPath", settings.NodeXmlPath ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "NodeSelector", settings.NodeSelector ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "IncludeNodes", settings.IncludeNodes ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "ExcludeNodes", settings.ExcludeNodes ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "NodeManipulator", settings.NodeManipulator ?? string.Empty); + ModuleController.Instance.UpdateModuleSetting(moduleId, "IncludeContext", settings.IncludeContext.ToString()); + ModuleController.Instance.UpdateModuleSetting(moduleId, "IncludeHidden", settings.IncludeHidden.ToString()); + ModuleController.Instance.UpdateModuleSetting(moduleId, "TemplateArguments", Settings.ToSettingString(settings.TemplateArguments)); + ModuleController.Instance.UpdateModuleSetting(moduleId, "ClientOptions", Settings.ToSettingString(settings.ClientOptions)); + } + + private static void UpdateWebConfig() + { + const string navName = "NewDDRMenuNavigationProvider"; + const string navType = "DotNetNuke.Web.NewDDRMenu.NewDDRMenuNavigationProvider, DotNetNuke.Web.NewDDRMenu"; + + var server = HttpContext.Current.Server; + var webConfig = server.MapPath("~/web.config"); + + var configXml = new XmlDocument { XmlResolver = null }; + configXml.Load(webConfig); + var navProviders = configXml.SelectSingleNode("/configuration/dotnetnuke/navigationControl/providers") as XmlElement; + + // ReSharper disable PossibleNullReferenceException + var addProvider = navProviders.SelectSingleNode("add[@name='" + navName + "']") as XmlElement; + + // ReSharper restore PossibleNullReferenceException + var needsUpdate = true; + if (addProvider == null) + { + addProvider = configXml.CreateElement("add"); + addProvider.SetAttribute("name", navName); + navProviders.AppendChild(addProvider); + } + else + { + needsUpdate = addProvider.GetAttribute("type") != navType; + } + + if (needsUpdate) + { + addProvider.SetAttribute("type", navType); + configXml.Save(webConfig); + } + } + + private static void TidyModuleDefinitions() + { + RemoveLegacyModuleDefinitions(NewDDRMenuModuleName, NewDDRMenuMmoduleDefinitionName); + RemoveLegacyModuleDefinitions("NewDDRMenuAdmin", "N/A"); + } + + private static void RemoveLegacyModuleDefinitions(string moduleName, string currentModuleDefinitionName) + { + var mdc = new ModuleDefinitionController(); + + var desktopModule = DesktopModuleController.GetDesktopModuleByModuleName(moduleName, Null.NullInteger); + if (desktopModule == null) + { + return; + } + + var desktopModuleId = desktopModule.DesktopModuleID; + var modDefs = ModuleDefinitionController.GetModuleDefinitionsByDesktopModuleID(desktopModuleId); + + var currentModDefId = 0; + foreach (var modDefKeyPair in modDefs) + { + if (modDefKeyPair.Value.FriendlyName.Equals(currentModuleDefinitionName, StringComparison.InvariantCultureIgnoreCase)) + { + currentModDefId = modDefKeyPair.Value.ModuleDefID; + } + } + + foreach (var modDefKeyPair in modDefs) + { + var oldModDefId = modDefKeyPair.Value.ModuleDefID; + if (oldModDefId != currentModDefId) + { + foreach (ModuleInfo mod in ModuleController.Instance.GetAllModules()) + { + if (mod.ModuleDefID == oldModDefId) + { + mod.ModuleDefID = currentModDefId; + ModuleController.Instance.UpdateModule(mod); + } + } + + mdc.DeleteModuleDefinition(oldModDefId); + } + } + + modDefs = ModuleDefinitionController.GetModuleDefinitionsByDesktopModuleID(desktopModuleId); + if (modDefs.Count == 0) + { + new DesktopModuleController().DeleteDesktopModule(desktopModuleId); + } + } + + private static void CleanOldAssemblies() + { + var assembliesToRemove = new[] { "DNNDoneRight.NewDDRMenu.dll", "DNNGarden.NewDDRMenu.dll" }; + + var server = HttpContext.Current.Server; + var assemblyPath = server.MapPath("~/bin/"); + foreach (var assembly in assembliesToRemove) + { + File.Delete(Path.Combine(assemblyPath, assembly)); + } + } + + private static void CheckSkinReferences() + { + var server = HttpContext.Current.Server; + var portalsRoot = server.MapPath("~/Portals/"); + foreach (var portal in Directory.GetDirectories(portalsRoot)) + { + foreach (var skinControl in Directory.GetFiles(portal, "*.ascx", SearchOption.AllDirectories)) + { + try + { + var ascxText = File.ReadAllText(skinControl); + var originalText = ascxText; + + Regex ascxText1Regex = new Regex(Regex.Escape(@"Namespace=""DNNDoneRight.NewDDRMenu"""), RegexOptions.IgnoreCase | RegexOptions.Compiled); + Regex ascxText2Regex = new Regex(Regex.Escape(@"Namespace=""DNNGarden.TemplateEngine"""), RegexOptions.IgnoreCase | RegexOptions.Compiled); + Regex ascxText3Regex = new Regex(Regex.Escape(@"Assembly=""DNNDoneRight.NewDDRMenu"""), RegexOptions.IgnoreCase | RegexOptions.Compiled); + Regex ascxText4Regex = new Regex(Regex.Escape(@"Assembly=""DNNGarden.NewDDRMenu"""), RegexOptions.IgnoreCase | RegexOptions.Compiled); + + ascxText = ascxText1Regex.Replace(ascxText, @"Namespace=""DotNetNuke.Web.NewDDRMenu.TemplateEngine"""); + ascxText = ascxText2Regex.Replace(ascxText, @"Namespace=""DotNetNuke.Web.NewDDRMenu.TemplateEngine"""); + ascxText = ascxText3Regex.Replace(ascxText, @"Assembly=""DotNetNuke.Web.NewDDRMenu"""); + ascxText = ascxText4Regex.Replace(ascxText, @"Assembly=""DotNetNuke.Web.NewDDRMenu"""); + + if (!ascxText.Equals(originalText)) + { + File.WriteAllText(skinControl, ascxText); + } + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + catch (SecurityException) + { + } + } + } + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/DNNAbstract.cs b/DNN Platform/Modules/NewDDRMenu/DNNAbstract.cs new file mode 100644 index 00000000000..97a25edf48c --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/DNNAbstract.cs @@ -0,0 +1,110 @@ +// 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.Web.NewDDRMenu +{ + using System.Collections.Generic; + using System.Web; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.UI; + using DotNetNuke.UI.WebControls; + using DotNetNuke.Web.NewDDRMenu.DNNCommon; + using DotNetNuke.Web.NewDDRMenu.TemplateEngine; + using Microsoft.Extensions.DependencyInjection; + + /// Dnn abstractions. + internal static class DNNAbstract + { + /// Gets the Dnn login url. + /// The url to login. + public static string GetLoginUrl() + { + var request = HttpContext.Current.Request; + if (request.IsAuthenticated) + { + var navigationManager = Globals.GetCurrentServiceProvider().GetRequiredService(); + return navigationManager.NavigateURL(PortalSettings.Current.ActiveTab.TabID, "Logoff"); + } + + var returnUrl = HttpContext.Current.Request.RawUrl; + if (returnUrl.IndexOf("?returnurl=") != -1) + { + returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?returnurl=")); + } + + returnUrl = HttpUtility.UrlEncode(returnUrl); + + return Globals.LoginURL(returnUrl, !string.IsNullOrEmpty(request.QueryString["override"])); + } + + /// Gets a url to the user profile or or the registration page. + /// If the user is logged in, returns the url to the user profile page, if not returns the url to the registration page. + public static string GetUserUrl() + { + var request = HttpContext.Current.Request; + var portalSettings = PortalSettings.Current; + if (!request.IsAuthenticated) + { + if (portalSettings.UserRegistration != (int)Globals.PortalRegistrationType.NoRegistration) + { + var navigationManager = Globals.GetCurrentServiceProvider().GetRequiredService(); + return Globals.RegisterURL(HttpUtility.UrlEncode(navigationManager.NavigateURL()), Null.NullString); + } + } + else + { + var objUserInfo = UserController.Instance.GetCurrentUserInfo(); + if (objUserInfo.UserID != -1) + { + return Globals.UserProfileURL(objUserInfo.UserID); + } + } + + return string.Empty; + } + + /// Gets the current culture. + /// The current culture code. + public static string GetCurrentCulture() + { + return DNNContext.Current.PortalSettings.CultureCode; + } + + /// Gets all the supported templating engines processors. + /// An enumeration of all the available processors. + public static IEnumerable SupportedTemplateProcessors() + { + return new ITemplateProcessor[] { new TokenTemplateProcessor(), new RazorTemplateProcessor(), new XsltTemplateProcessor() }; + } + + /// Gets the navigation nodes options. + /// A value indicating whether to include the hidden nodes. + /// An integer totalling the options values, for the values. + public static int GetNavNodeOptions(bool includeHidden) + { + return (int)Navigation.NavNodeOptions.IncludeSiblings + (int)Navigation.NavNodeOptions.IncludeSelf + + (includeHidden ? (int)Navigation.NavNodeOptions.IncludeHiddenNodes : 0); + } + + /// Gets a value indicating whether it is supported to include hidden nodes. + /// Always true. + public static bool IncludeHiddenSupported() + { + return true; + } + + /// Converts a into a . + /// The DnnNode to convert. + /// The MenuNode to return. + public static void DNNNodeToMenuNode(DNNNode dnnNode, MenuNode menuNode) + { + menuNode.LargeImage = dnnNode.LargeImage; + } + } +} diff --git a/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu-menudef.xml b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu-menudef.xml new file mode 100644 index 00000000000..43c1495b66b --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu-menudef.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.js b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.js new file mode 100644 index 00000000000..cc74a7232ff --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.js @@ -0,0 +1,239 @@ +if (!DDR.Menu.Providers.DNNMenu) { + DDRjQuery(function ($) { + DDR.Menu.Providers.DNNMenu = function (jqContainer, dnnNavParams) { + var me = this; + + me.baseConstructor(jqContainer, dnnNavParams); + } + DDR.Menu.Providers.DNNMenu.prototype = new DDR.Menu.Providers.BaseRenderer(); + + DDR.Menu.Providers.DNNMenu.prototype.createRootMenu = function () { + var me = this; + + var outerContainer = $(""); + var dnnNavContainer = me.createRenderedMenu(me.rootMenu); + dnnNavContainer.addClass(me.dnnNavParams.CSSControl); + outerContainer.append(dnnNavContainer); + + me.subMenus.each(function (m) { + dnnNavContainer.append(me.createRenderedMenu(m)); + }); + + me.jqContainer.replaceWith(outerContainer); + + me.jqContainer.show(1); + me.jqContainer.queue(function () { + me.addCovering(); + me.prepareHideAndShow(); + + $(this).dequeue(); + }); + } + + DDR.Menu.Providers.DNNMenu.prototype.createRenderedMenu = function (menu) { + var me = this; + + var level = menu.level; + var childItems = menu.childItems; + + if (level == 0) { + menu.flyout = false; + menu.layout = me.orientHorizontal ? "horizontal" : "vertical"; + var result = $(""); + childItems.each(function (i) { + result.append(me.createRenderedItem(i)); + }); + } + else { + menu.flyout = true; + menu.layout = "vertical"; + var parentItem = menu.parentItem; + var parentMenu = parentItem.parentMenu; + +// var result = $("").css({ "position": "absolute", "left": "-1000px" }); + var result = $("
    ").css({ "position": "absolute", "left": "-1000px" }); + var table = result.children("table"); + table.addClass(this.dnnNavParams.CSSContainerSub); + table.addClass("m"); + table.addClass("m" + (menu.level - 1)); + table.addClass("mid" + menu.id); + childItems.each(function (i) { + table.append(me.createRenderedItem(i)); + }); + } + + menu.rendered = result; + + return result; + }; + + DDR.Menu.Providers.DNNMenu.prototype.createRenderedItem = function (item) { + var me = this; + + var level = item.level; + var title = item.title; + var image = item.image; + var href = item.href; + var separator = item.separator; + + var result; + + if (level == 0) { + result = me.orientHorizontal ? $("") : $("
    "); + var spanImg = result.children("span:eq(0)"); + var spanText = result.children("span:eq(1)"); + + result.addClass("root"); + + if (href && !item.isSeparator) { + item.coveringHere = function () { return item.rendered; }; + } + + spanImg.addClass("icn"); + if (image) { + spanImg.append($("").attr("src", image)); + } + + spanText.addClass("txt"); + spanText.css("cursor", "pointer").text(title); + + var nodeLeftHTML = me.dnnNavParams.NodeLeftHTMLRoot || ""; + var nodeRightHTML = me.dnnNavParams.NodeRightHTMLRoot || ""; + var separatorLeftHTML = me.dnnNavParams.SeparatorLeftHTML || ""; + var separatorRightHTML = me.dnnNavParams.SeparatorRightHTML || ""; + var cssClass = this.dnnNavParams.CSSNodeRoot; + + if (item.isBreadcrumb) { + if ((me.dnnNavParams.CSSBreadCrumbRoot || "") != "") + cssClass = me.dnnNavParams.CSSBreadCrumbRoot; + nodeLeftHTML = me.dnnNavParams.NodeLeftHTMLBreadCrumbRoot || nodeLeftHTML; + nodeRightHTML = me.dnnNavParams.NodeRightHTMLBreadCrumbRoot || nodeRightHTML; + separatorLeftHTML = me.dnnNavParams.SeparatorLeftHTMLBreadCrumb || separatorLeftHTML; + separatorRightHTML = me.dnnNavParams.SeparatorRightHTMLBreadCrumb || separatorRightHTML; + } + + if (item.isSelected) { + if ((me.dnnNavParams.CSSNodeSelectedRoot || "") != "") + cssClass = me.dnnNavParams.CSSNodeSelectedRoot; + separatorLeftHTML = me.dnnNavParams.SeparatorLeftHTMLActive || separatorLeftHTML; + separatorRightHTML = me.dnnNavParams.SeparatorRightHTMLActive || separatorRightHTML; + } + + result.addClass(cssClass); + + if (!item.first) { + separatorLeftHTML = (me.dnnNavParams.SeparatorHTML || "") + separatorLeftHTML; + } + separatorLeftHTML = separatorLeftHTML + nodeLeftHTML; + separatorRightHTML = nodeRightHTML + separatorRightHTML; + + if (separatorLeftHTML != "") { + result.prepend($("").append(separatorLeftHTML)); + } + if (separatorRightHTML != "") { + result.append($("").append(separatorRightHTML)); + } + + if (item.childMenu && me.dnnNavParams.IndicateChildren) + result.css({ + "background-image": "url(" + me.dnnNavParams.PathSystemImage + me.dnnNavParams.IndicateChildImageRoot + ")", + "background-repeat": "no-repeat", + "background-position": "right" + }); + } + else { + result = $("
    "); + var tdImg = result.find("td:eq(0)"); + var spanImg = result.find("span:eq(0)"); + var spanText = result.find("span:eq(1)"); + var tdArrow = result.find("td:eq(2)"); + + if (href) { + item.coveringHere = function () { return item.rendered.find("td"); }; + } + + tdImg.addClass("icn"); + if (image) { + spanImg.append($("").attr("src", image)); + } + + spanText.addClass("txt"); + if (!item.isSeparator) { + spanText.text(title); + } + spanText.css("white-space", "nowrap"); + + if (item.childMenu && me.dnnNavParams.IndicateChildren) + tdArrow.append($("").attr("src", me.dnnNavParams.PathSystemImage + me.dnnNavParams.IndicateChildImageSub)); + + tdImg.addClass(me.dnnNavParams.CSSIcon); + tdArrow.addClass(me.dnnNavParams.CSSIndicateChildSub); + result.css("cursor", "pointer"); + + var nodeLeftHTML = me.dnnNavParams.NodeLeftHTMLSub || ""; + var nodeRightHTML = me.dnnNavParams.NodeRightHTMLSub || ""; + var cssClass = me.dnnNavParams.CSSNode; + + if (item.isBreadcrumb) { + if ((me.dnnNavParams.CSSBreadCrumbSub || "") != "") + cssClass = me.dnnNavParams.CSSBreadCrumbSub; + nodeLeftHTML = me.dnnNavParams.NodeLeftHTMLBreadCrumbSub || nodeLeftHTML; + nodeRightHTML = me.dnnNavParams.NodeRightHTMLBreadCrumbSub || nodeRightHTML; + } + + if (item.isSelected) { + if ((me.dnnNavParams.CSSNodeSelectedSub || "") != "") + cssClass = me.dnnNavParams.CSSNodeSelectedSub; + } + + if (item.isSeparator) { + cssClass = (me.dnnNavParams.CSSBreak || ""); + } + + result.addClass(cssClass); + + if (nodeLeftHTML) + tdImg.prepend($("").append(nodeLeftHTML)); + if (nodeRightHTML) + tdArrow.append($("").append(nodeRightHTML)); + } + + if (item.isSelected) { + result.addClass("sel"); + } + if (item.isBreadcrumb) { + result.addClass("bc"); + } + if (item.isSeparator) { + result.addClass("break"); + } + result.addClass("mi"); + result.addClass("mi" + item.path); + result.addClass("id" + item.id); + if (item.first) { + result.addClass("first"); + } + if (item.last) { + result.addClass("last"); + } + if (item.first && item.last) { + result.addClass("firstlast"); + } + + item.rendered = result; + + return result; + }; + + DDR.Menu.Providers.DNNMenu.prototype.menuItemHover = function (item) { + var me = this; + + if (item.level == 0) { + item.rendered.setHoverClass("hov " + (me.dnnNavParams.CSSNodeHoverRoot || (me.dnnNavParams.CSSNodeHover || ""))); + } + else { + item.rendered.setHoverClass("hov " + (me.dnnNavParams.CSSNodeHoverSub || (me.dnnNavParams.CSSNodeHover || ""))); + } + }; + }); +} diff --git a/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.min.js b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.min.js new file mode 100644 index 00000000000..5c0f51fb1cd --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/DNNMenu/DNNMenu.min.js @@ -0,0 +1 @@ +(function(n){n.effects||function(n){function t(t,i){var u=t[1]&&t[1].constructor==Object?t[1]:{},r,f;return i&&(u.mode=i),r=t[1]&&t[1].constructor!=Object?t[1]:u.duration?u.duration:t[2],r=n.fx.off?0:typeof r=="number"?r:n.fx.speeds[r]||n.fx.speeds._default,f=u.callback||n.isFunction(t[1])&&t[1]||n.isFunction(t[2])&&t[2]||n.isFunction(t[3])&&t[3],[t[0],u,r,f]}function i(t){var i;return t&&t.constructor==Array&&t.length==3?t:(i=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(t))?[parseInt(i[1],10),parseInt(i[2],10),parseInt(i[3],10)]:(i=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(t))?[parseFloat(i[1])*2.55,parseFloat(i[2])*2.55,parseFloat(i[3])*2.55]:(i=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(t))?[parseInt(i[1],16),parseInt(i[2],16),parseInt(i[3],16)]:(i=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(t))?[parseInt(i[1]+i[1],16),parseInt(i[2]+i[2],16),parseInt(i[3]+i[3],16)]:(i=/rgba\(0, 0, 0, 0\)/.exec(t))?r.transparent:r[n.trim(t).toLowerCase()]}function u(t,r){var u;do{if(u=n.css(t,r),u!=""&&u!="transparent"||n.nodeName(t,"body"))break;r="backgroundColor"}while(t=t.parentNode);return i(u)}n.effects={version:"1.7.2",save:function(n,t){for(var i=0;i<\/div>'),t=n.parent(),n.css("position")=="static"?(t.css({position:"relative"}),n.css({position:"relative"})):(i=n.css("top"),isNaN(parseInt(i,10))&&(i="auto"),r=n.css("left"),isNaN(parseInt(r,10))&&(r="auto"),t.css({position:n.css("position"),top:i,left:r,zIndex:n.css("z-index")}).show(),n.css({position:"relative",top:0,left:0})),t.css(u),t)},removeWrapper:function(n){return n.parent().is(".ui-effects-wrapper")?n.parent().replaceWith(n):n},setTransition:function(t,i,r,u){return u=u||{},n.each(i,function(n,i){unit=t.cssUnit(i),unit[0]>0&&(u[i]=unit[0]*r+unit[1])}),u},animateClass:function(t,i,r,u){var f=typeof r=="function"?r:u?u:null,e=typeof r=="string"?r:null;return this.each(function(){var c={},u=n(this),s=u.attr("style")||"",h,o,r;typeof s=="object"&&(s=s.cssText),t.toggle&&(u.hasClass(t.toggle)?t.remove=t.toggle:t.add=t.toggle),h=n.extend({},document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle),t.add&&u.addClass(t.add),t.remove&&u.removeClass(t.remove),o=n.extend({},document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle),t.add&&u.removeClass(t.add),t.remove&&u.addClass(t.remove);for(r in o)typeof o[r]=="function"||!o[r]||r.indexOf("Moz")!=-1||r.indexOf("length")!=-1||o[r]==h[r]||!r.match(/color/i)&&(r.match(/color/i)||isNaN(parseInt(o[r],10)))||h.position=="static"&&(h.position!="static"||r.match(/left|top|bottom|right/))||(c[r]=o[r]);u.animate(c,i,e,function(){typeof n(this).attr("style")=="object"?(n(this).attr("style").cssText="",n(this).attr("style").cssText=s):n(this).attr("style",s),t.add&&n(this).addClass(t.add),t.remove&&n(this).removeClass(t.remove),f&&f.apply(this,arguments)})})}},n.fn.extend({_show:n.fn.show,_hide:n.fn.hide,__toggle:n.fn.toggle,_addClass:n.fn.addClass,_removeClass:n.fn.removeClass,_toggleClass:n.fn.toggleClass,effect:function(t,i,r,u){return n.effects[t]?n.effects[t].call(this,{method:t,options:i||{},duration:r,callback:u}):null},show:function(){return!arguments[0]||arguments[0].constructor==Number||/(slow|normal|fast)/.test(arguments[0])?this._show.apply(this,arguments):this.effect.apply(this,t(arguments,"show"))},hide:function(){return!arguments[0]||arguments[0].constructor==Number||/(slow|normal|fast)/.test(arguments[0])?this._hide.apply(this,arguments):this.effect.apply(this,t(arguments,"hide"))},toggle:function(){return!arguments[0]||arguments[0].constructor==Number||/(slow|normal|fast)/.test(arguments[0])||n.isFunction(arguments[0])||typeof arguments[0]=="boolean"?this.__toggle.apply(this,arguments):this.effect.apply(this,t(arguments,"toggle"))},addClass:function(t,i,r,u){return i?n.effects.animateClass.apply(this,[{add:t},i,r,u]):this._addClass(t)},removeClass:function(t,i,r,u){return i?n.effects.animateClass.apply(this,[{remove:t},i,r,u]):this._removeClass(t)},toggleClass:function(t,i,r,u){return typeof i!="boolean"&&i?n.effects.animateClass.apply(this,[{toggle:t},i,r,u]):this._toggleClass(t,i)},morph:function(t,i,r,u,f){return n.effects.animateClass.apply(this,[{add:i,remove:t},r,u,f])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(t){var i=this.css(t),r=[];return n.each(["em","px","%","pt"],function(n,t){i.indexOf(t)>0&&(r=[parseFloat(i),t])}),r}}),n.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(t,r){n.fx.step[r]=function(n){n.state==0&&(n.start=u(n.elem,r),n.end=i(n.end)),n.elem.style[r]="rgb("+[Math.max(Math.min(parseInt(n.pos*(n.end[0]-n.start[0])+n.start[0],10),255),0),Math.max(Math.min(parseInt(n.pos*(n.end[1]-n.start[1])+n.start[1],10),255),0),Math.max(Math.min(parseInt(n.pos*(n.end[2]-n.start[2])+n.start[2],10),255),0)].join(",")+")"}});var r={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};n.easing.jswing=n.easing.swing,n.extend(n.easing,{def:"easeOutQuad",swing:function(t,i,r,u,f){return n.easing[n.easing.def](t,i,r,u,f)},easeInQuad:function(n,t,i,r,u){return r*(t/=u)*t+i},easeOutQuad:function(n,t,i,r,u){return-r*(t/=u)*(t-2)+i},easeInOutQuad:function(n,t,i,r,u){return(t/=u/2)<1?r/2*t*t+i:-r/2*(--t*(t-2)-1)+i},easeInCubic:function(n,t,i,r,u){return r*(t/=u)*t*t+i},easeOutCubic:function(n,t,i,r,u){return r*((t=t/u-1)*t*t+1)+i},easeInOutCubic:function(n,t,i,r,u){return(t/=u/2)<1?r/2*t*t*t+i:r/2*((t-=2)*t*t+2)+i},easeInQuart:function(n,t,i,r,u){return r*(t/=u)*t*t*t+i},easeOutQuart:function(n,t,i,r,u){return-r*((t=t/u-1)*t*t*t-1)+i},easeInOutQuart:function(n,t,i,r,u){return(t/=u/2)<1?r/2*t*t*t*t+i:-r/2*((t-=2)*t*t*t-2)+i},easeInQuint:function(n,t,i,r,u){return r*(t/=u)*t*t*t*t+i},easeOutQuint:function(n,t,i,r,u){return r*((t=t/u-1)*t*t*t*t+1)+i},easeInOutQuint:function(n,t,i,r,u){return(t/=u/2)<1?r/2*t*t*t*t*t+i:r/2*((t-=2)*t*t*t*t+2)+i},easeInSine:function(n,t,i,r,u){return-r*Math.cos(t/u*(Math.PI/2))+r+i},easeOutSine:function(n,t,i,r,u){return r*Math.sin(t/u*(Math.PI/2))+i},easeInOutSine:function(n,t,i,r,u){return-r/2*(Math.cos(Math.PI*t/u)-1)+i},easeInExpo:function(n,t,i,r,u){return t==0?i:r*Math.pow(2,10*(t/u-1))+i},easeOutExpo:function(n,t,i,r,u){return t==u?i+r:r*(-Math.pow(2,-10*t/u)+1)+i},easeInOutExpo:function(n,t,i,r,u){return t==0?i:t==u?i+r:(t/=u/2)<1?r/2*Math.pow(2,10*(t-1))+i:r/2*(-Math.pow(2,-10*--t)+2)+i},easeInCirc:function(n,t,i,r,u){return-r*(Math.sqrt(1-(t/=u)*t)-1)+i},easeOutCirc:function(n,t,i,r,u){return r*Math.sqrt(1-(t=t/u-1)*t)+i},easeInOutCirc:function(n,t,i,r,u){return(t/=u/2)<1?-r/2*(Math.sqrt(1-t*t)-1)+i:r/2*(Math.sqrt(1-(t-=2)*t)+1)+i},easeInElastic:function(n,t,i,r,u){var f=1.70158,e=0,o=r;return t==0?i:(t/=u)==1?i+r:(e||(e=u*.3),o<\/div>").css({position:"absolute",visibility:"visible",left:-e*(s/u),top:-f*(h/r)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:s/u,height:h/r,left:o.left+e*(s/u)+(t.options.mode=="show"?(e-Math.floor(u/2))*(s/u):0),top:o.top+f*(h/r)+(t.options.mode=="show"?(f-Math.floor(r/2))*(h/r):0),opacity:t.options.mode=="show"?0:1}).animate({left:o.left+e*(s/u)+(t.options.mode=="show"?0:(e-Math.floor(u/2))*(s/u)),top:o.top+f*(h/r)+(t.options.mode=="show"?0:(f-Math.floor(r/2))*(h/r)),opacity:t.options.mode=="show"?1:0},t.duration||500);setTimeout(function(){t.options.mode=="show"?i.css({visibility:"visible"}):i.css({visibility:"visible"}).hide(),t.callback&&t.callback.apply(i[0]),i.dequeue(),n("div.ui-effects-explode").remove()},t.duration||500)})}}(n),function(n){n.effects.fold=function(t){return this.queue(function(){var i=n(this),h=["position","top","left"],r=n.effects.setMode(i,t.options.mode||"hide"),f=t.options.size||15,c=!!t.options.horizFirst,l=t.duration?t.duration/2:n.fx.speeds._default/2,o,s;n.effects.save(i,h),i.show();var u=n.effects.createWrapper(i).css({overflow:"hidden"}),a=r=="show"!=c,v=a?["width","height"]:["height","width"],e=a?[u.width(),u.height()]:[u.height(),u.width()],y=/([0-9]+)%/.exec(f);y&&(f=parseInt(y[1],10)/100*e[r=="hide"?0:1]),r=="show"&&u.css(c?{height:0,width:f}:{height:f,width:0}),o={},s={},o[v[0]]=r=="show"?e[0]:f,s[v[1]]=r=="show"?e[1]:0,u.animate(o,l,t.options.easing).animate(s,l,t.options.easing,function(){r=="hide"&&i.hide(),n.effects.restore(i,h),n.effects.removeWrapper(i),t.callback&&t.callback.apply(i[0],arguments),i.dequeue()})})}}(n),function(n){n.effects.puff=function(t){return this.queue(function(){var i=n(this),r=n.extend(!0,{},t.options),f=n.effects.setMode(i,t.options.mode||"hide"),o=parseInt(t.options.percent,10)||150,u,e;r.fade=!0,u={height:i.height(),width:i.width()},e=o/100,i.from=f=="hide"?u:{height:u.height*e,width:u.width*e},r.from=i.from,r.percent=f=="hide"?o:100,r.mode=f,i.effect("scale",r,t.duration,t.callback),i.dequeue()})},n.effects.scale=function(t){return this.queue(function(){var i=n(this),r=n.extend(!0,{},t.options),u=n.effects.setMode(i,t.options.mode||"effect"),o=parseInt(t.options.percent,10)||(parseInt(t.options.percent,10)==0?0:u=="hide"?0:100),s=t.options.direction||"both",h=t.options.origin,f,e;u!="effect"&&(r.origin=h||["middle","center"],r.restore=!0),f={height:i.height(),width:i.width()},i.from=t.options.from||(u=="show"?{height:0,width:0}:f),e={y:s!="horizontal"?o/100:1,x:s!="vertical"?o/100:1},i.to={height:f.height*e.y,width:f.width*e.x},t.options.fade&&(u=="show"&&(i.from.opacity=0,i.to.opacity=1),u=="hide"&&(i.from.opacity=1,i.to.opacity=0)),r.from=i.from,r.to=i.to,r.mode=u,i.effect("size",r,t.duration,t.callback),i.dequeue()})},n.effects.size=function(t){return this.queue(function(){var i=n(this),f=["position","top","left","width","height","overflow","opacity"],v=["position","top","left","overflow","opacity"],a=["width","height","overflow"],c=["fontSize"],e=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],o=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=n.effects.setMode(i,t.options.mode||"effect"),l=t.options.restore||!1,s=t.options.scale||"both",y=t.options.origin,u={height:i.height(),width:i.width()},h,r;i.from=t.options.from||u,i.to=t.options.to||u,y&&(h=n.effects.getBaseline(y,u),i.from.top=(u.height-i.from.height)*h.y,i.from.left=(u.width-i.from.width)*h.x,i.to.top=(u.height-i.to.height)*h.y,i.to.left=(u.width-i.to.width)*h.x),r={from:{y:i.from.height/u.height,x:i.from.width/u.width},to:{y:i.to.height/u.height,x:i.to.width/u.width}},(s=="box"||s=="both")&&(r.from.y!=r.to.y&&(f=f.concat(e),i.from=n.effects.setTransition(i,e,r.from.y,i.from),i.to=n.effects.setTransition(i,e,r.to.y,i.to)),r.from.x!=r.to.x&&(f=f.concat(o),i.from=n.effects.setTransition(i,o,r.from.x,i.from),i.to=n.effects.setTransition(i,o,r.to.x,i.to))),(s=="content"||s=="both")&&r.from.y!=r.to.y&&(f=f.concat(c),i.from=n.effects.setTransition(i,c,r.from.y,i.from),i.to=n.effects.setTransition(i,c,r.to.y,i.to)),n.effects.save(i,l?f:v),i.show(),n.effects.createWrapper(i),i.css("overflow","hidden").css(i.from),(s=="content"||s=="both")&&(e=e.concat(["marginTop","marginBottom"]).concat(c),o=o.concat(["marginLeft","marginRight"]),a=f.concat(e).concat(o),i.find("*[width]").each(function(){child=n(this),l&&n.effects.save(child,a);var i={height:child.height(),width:child.width()};child.from={height:i.height*r.from.y,width:i.width*r.from.x},child.to={height:i.height*r.to.y,width:i.width*r.to.x},r.from.y!=r.to.y&&(child.from=n.effects.setTransition(child,e,r.from.y,child.from),child.to=n.effects.setTransition(child,e,r.to.y,child.to)),r.from.x!=r.to.x&&(child.from=n.effects.setTransition(child,o,r.from.x,child.from),child.to=n.effects.setTransition(child,o,r.to.x,child.to)),child.css(child.from),child.animate(child.to,t.duration,t.options.easing,function(){l&&n.effects.restore(child,a)})})),i.animate(i.to,{queue:!1,duration:t.duration,easing:t.options.easing,complete:function(){p=="hide"&&i.hide(),n.effects.restore(i,l?f:v),n.effects.removeWrapper(i),t.callback&&t.callback.apply(this,arguments),i.dequeue()}})})}}(n),function(n){n.effects.slide=function(t){return this.queue(function(){var i=n(this),h=["position","top","left"],u=n.effects.setMode(i,t.options.mode||"show"),r=t.options.direction||"left",s;n.effects.save(i,h),i.show(),n.effects.createWrapper(i).css({overflow:"hidden"});var f=r=="up"||r=="down"?"top":"left",e=r=="up"||r=="left"?"pos":"neg",o=t.options.distance||(f=="top"?i.outerHeight({margin:!0}):i.outerWidth({margin:!0}));u=="show"&&i.css(f,e=="pos"?-o:o),s={},s[f]=(u=="show"?e=="pos"?"+=":"-=":e=="pos"?"-=":"+=")+o,i.animate(s,{queue:!1,duration:t.duration,easing:t.options.easing,complete:function(){u=="hide"&&i.hide(),n.effects.restore(i,h),n.effects.removeWrapper(i),t.callback&&t.callback.apply(this,arguments),i.dequeue()}})})}}(n)})(DDRjQuery),window.DDR||(window.DDR={}),DDR.Menu||(DDR.Menu={}),DDR.Menu.Providers||(DDR.Menu.Providers={}),DDRjQuery(function(n){DDR.Menu.Providers.BaseRenderer=function(){},DDR.Menu.Providers.BaseRenderer.prototype.baseConstructor=function(t,i){var r=this;r.jqContainer=t,r.dnnNavParams=i,r.menus=t.find("ul").toDDRObjectArray(),r.items=t.find("li").toDDRObjectArray(),r.subMenus=r.menus.filter(function(n){return n.level>0}),r.rootItems=r.items.filter(function(n){return n.level==0}),r.jqRootMenu=t.children("ul"),r.rootMenu=r.jqRootMenu.toDDRObject(),r.clientID=t[0].id,r.showEffect=i.effect||"slide",r.showEffectOptions=JSON.parse(i.effectOptions)||{},r.showEffectSpeed=i.effectSpeed||200,r.orientHorizontal=i.ControlOrientation!="Vertical",r.useShim=n.browser.msie&&i.shim},DDR.Menu.Providers.BaseRenderer.prototype.mapToRendered=function(t){return n(n.map(t.get(),function(t){return n(t).ddrData().rendered.get(0)}))},DDR.Menu.Providers.BaseRenderer.prototype.addCovering=function(){var i=this,t=[],r=n("<\/a>").css("background",n.browser.msie?"url("+i.dnnNavParams.PathSystemImage+"spacer.gif)":"transparent"),u=n.browser.msie&&n.browser.version.startsWith("6.")||!n.support.boxModel;i.menus.each(function(i){i.coverings=[],i.childItems.each(function(f){if(f.coveringHere){var e=f.rendered.text();f.coveringHere().each(function(){var o=n(this),s,c,h;o.css("position","relative"),s=r.clone(),s.attr("href",f.href||"javascript:void(0)").children("span").text(e),o.prepend(s),s=o.children("a:first"),c=s.offsetParent(),h=o[0],h===c[0]||h.offsetParent===null?u&&s.css({left:"",right:"",width:o.outerWidth(!1)+"px",height:o.outerHeight(!1)+"px"}):(h.moveCovering=!0,h.covering=s,h.coveringHere=o,s.css({width:"0",height:"0"}),o.bind("mouseenter",function(){i.coverings.each(function(n){var t,i;n.moveCovering&&(t=n.coveringHere.offset(),n.covering.css({top:"0",left:"0"}),i=n.covering.offset(),n.covering.css({top:t.top-i.top+"px",left:t.left-i.left+"px",width:n.coveringHere.outerWidth(!1)+"px",height:n.coveringHere.outerHeight(!1)+"px"}),n.moveCovering=!1)})}),i.coverings.push(h),t.push(h))})}})}),t.length>0&&n(window).resize(function(){t.each(function(n){n.covering.css({width:"0",height:"0"}),n.moveCovering=!0})})},DDR.Menu.Providers.BaseRenderer.prototype.prepareHideAndShow=function(){var n=this;n.hideAllMenus=n.menus.filter(function(n){return n.flyout}),n.setItemsHideAndShow(),n.attachEvents(),n.closeUp()},DDR.Menu.Providers.BaseRenderer.prototype.setItemsHideAndShow=function(){var n=this;n.items.each(function(t){var r=t.allParentMenus,u=t.allChildMenus,i=t.childMenu,f=n.subMenus.filter(function(n){return n.flyout&&!(r.contains(n)||u.contains(n))});t.hideThese=f,t.showThese=[],i&&i.flyout&&(t.showThese[0]=i)})},DDR.Menu.Providers.BaseRenderer.prototype.attachEvents=function(){var t=this;t.menus.each(function(i){if(i.flyout){var r=i.rendered,u=r[0];u.hideMenu=function(){r.stop(!0,!0),this.style.display!="none"&&(r.hide(),i.shim&&i.shim.hide())},u.showMenu=function(u){this.style.display=="none"&&(r.stop(!0,!0),i.childItems.allRendered().stop(!0,!0).unbind("mouseenter mouseleave"),t.positionMenu(i),u||t.showEffectSpeed==0?r.show():t.showEffect=="none"?(r.queue(function(){setTimeout(function(){r.dequeue()},t.showEffectSpeed)}),r.show(1)):t.showEffect=="fade"?r.fadeIn(t.showEffectSpeed):(t.showEffectOptions.direction=t.showEffect=="slide"||t.showEffect=="drop"?i.slideDirection:i.blindDirection,r.show(t.showEffect,t.showEffectOptions,t.showEffectSpeed)),r.queue(function(){r.css("display","none").css("display","block"),i.childItems.each(function(n){t.menuItemHover&&t.menuItemHover(n),n.rendered.hover(function(){n.hideThese.allRendered().each(function(){this.hideMenu()}),n.showThese.allRendered().each(function(){this.showMenu()})},function(){}),n.rendered.focus(function(){n.hideThese.allRendered().each(function(){this.hideMenu()}),n.showThese.allRendered().each(function(){this.showMenu(!0)})})}),n(this).dequeue()}))}}else i.childItems.each(function(n){t.menuItemHover&&t.menuItemHover(n),n.rendered.hover(function(){n.hideThese.allRendered().each(function(){this.hideMenu()}),n.showThese.allRendered().each(function(){this.showMenu()})},function(){}),n.rendered.focus(function(){n.hideThese.allRendered().each(function(){this.hideMenu()}),n.showThese.allRendered().each(function(){this.showMenu(!0)})})});i.rendered.mouseover(function(){t.timeoutID&&(window.clearTimeout(t.timeoutID),t.timeoutID=null)}),i.rendered.mouseout(function(){t.timeoutID||(t.timeoutID=window.setTimeout(function(){t.closeUp()},400))}),i.rendered.mouseover(function(){t.timeoutID&&(window.clearTimeout(t.timeoutID),t.timeoutID=null)}),i.rendered.mouseout(function(){t.timeoutID||(t.timeoutID=window.setTimeout(function(){t.closeUp()},400))})})},DDR.Menu.Providers.BaseRenderer.prototype.positionMenu=function(t){if(t.childItems&&t.childItems.length>0){var l=this,s=t.level,h=t.parentItem.parentMenu,a=t.parentItem,r=t.layout.match(/,menu$/)?h.rendered:a.rendered,i=t.rendered,e=n(window),c=e.scrollLeft(),u=c+e.width(),o=e.scrollTop(),f=o+e.height(),v=i.css("display");i.css({display:"block",width:"auto",height:"auto","overflow-x":"visible","overflow-y":"visible","z-index":1e4+s*3}),i.width(i.width()),i.height(i.height()),h.layout.match(/^horizontal/)?(t.slideDirection="up",t.blindDirection="vertical",i.alignElement(function(){return t.childItems[0].rendered.getLeft(2)},r.getLeft(2),function(){return i.getTop(1)},r.getBottom(1)),i.getRight(3)>u&&i.alignHorizontal(function(){return i.getRight(3)},u),i.getBottom(3)>f&&(i.alignVertical(function(){return i.getBottom(1)},r.getTop(1)),t.slideDirection="down"),i.getTop(3)u&&i.alignHorizontal(function(){return i.getRight(3)},u),t.slideDirection="up"):(i.alignElement(function(){return t.childItems[0].rendered.getLeft(2)},r.getLeft(2),function(){return i.getTop(1)},r.getBottom(1)),i.getRight(3)>u&&i.alignHorizontal(function(){return i.getRight(3)},u),i.getBottom(3)>f&&(i.alignVertical(function(){return i.getBottom(1)},r.getTop(1)),t.slideDirection="down"),i.getTop(3)u&&i.alignHorizontal(function(){return i.getRight(3)},u),t.slideDirection="up"))):(t.slideDirection="left",t.blindDirection="horizontal",i.alignElement(function(){return i.getLeft(1)},r.getRight(1),function(){return t.childItems[0].rendered.getTop(2)},r.getTop(2)),i.getBottom(3)>f&&i.alignVertical(function(){return i.getBottom(3)},f),i.getRight(3)>u&&(i.alignHorizontal(function(){return i.getRight(1)},r.getLeft(1)),t.slideDirection="right"),i.getLeft(3)").css({position:"absolute","z-index":9999+s*3,"background-color":"transparent",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"}).appendTo(n(document.body))),t.shim.css({top:i.css("top"),left:i.css("left"),width:i.outerWidth(!0)+"px",height:i.outerHeight(!0)+"px",display:"block"})),i.css("display",v)}},DDR.Menu.Providers.BaseRenderer.prototype.closeUp=function(){var n=this;n.hideAllMenus.allRendered().each(function(){this.hideMenu()})},DDR.Menu.getCSS=function(t,i){return parseFloat("0"+n.css(t,i,!0))},Array.prototype.each||(Array.prototype.each=function(n){for(var t=0;t=3&&(t+=DDR.Menu.getCSS(i,"borderLeftWidth")),n>=4&&(t+=DDR.Menu.getCSS(i,"paddingLeft")),t},getTop:function(n){var r=this,t=r.offset().top,i=r[0];return n==1&&(t-=DDR.Menu.getCSS(i,"marginTop")),n>=3&&(t+=DDR.Menu.getCSS(i,"borderTopWidth")),n>=4&&(t+=DDR.Menu.getCSS(i,"paddingTop")),t},getRight:function(n){var t=this.getLeft(4)+this.width(),i=this[0];return n<4&&(t+=DDR.Menu.getCSS(i,"paddingRight")),n<3&&(t+=DDR.Menu.getCSS(i,"borderRightWidth")),n<2&&(t+=DDR.Menu.getCSS(i,"marginRight")),t},getBottom:function(n){var t=this.getTop(4)+this.height(),i=this[0];return n<4&&(t+=DDR.Menu.getCSS(i,"paddingBottom")),n<3&&(t+=DDR.Menu.getCSS(i,"borderBottomWidth")),n<2&&(t+=DDR.Menu.getCSS(i,"marginBottom")),t},alignElement:function(t,i,r,u){return this.each(function(){var f=n(this),e=f.css("display");f.css({left:"-999px",top:"-999px",position:"absolute",display:"block"}),i-=999+t(),u-=999+r(),e!="block"&&f.css("display",e),f.css({left:i+"px",top:u+"px"})})},alignHorizontal:function(t,i){return this.each(function(){var r=n(this),u=r.css("display");r.css({left:"-999px",position:"absolute",display:"block"}),i-=999+t(),u!="block"&&r.css("display",u),r.css({left:i+"px"})})},alignVertical:function(t,i){return this.each(function(){var r=n(this),u=r.css("display");r.css({top:"-999px",position:"absolute",display:"block"}),i-=999+t(),u!="block"&&r.css("display",u),r.css({top:i+"px"})})},sizeElement:function(t,i,r,u){return this.each(function(){var f=n(this),e=f.css("display");f.css({width:"9999px",height:"9999px",display:"block"}),i+=9999-t(),u+=9999-r(),e!="block"&&f.css("display",e),f.css({width:i+"px",height:u+"px"})})},setContentWidth:function(t){return this.each(function(){var i=n(this);i.width(n.support.boxModel?t:t+i.fullWidth()-i.width())})},setContentHeight:function(t){return this.each(function(){var i=n(this);i.height(n.support.boxModel?t:t+i.fullHeight()-i.height())})},setFullWidth:function(t){return this.each(function(){var i=n(this),r=i.width()-i.fullWidth(),u,f;for(i.width(t+r),u=0;u<100;u++){if(f=i.fullWidth(),f==t)return;r+=t-f,i.width(t+r)}})},setFullHeight:function(t){return this.each(function(){var i=n(this),r=i.height()-i.fullHeight(),u,f;for(i.height(t+r),u=0;u<100;u++){if(f=i.fullHeight(),f==t)return;r+=t-f,i.height(t+r)}})},fullWidth:function(){var t=0;return this.each(function(){var i=n(this).outerWidth(!0);i>t&&(t=i)}),t},fullHeight:function(){var t=0;return this.each(function(){var i=n(this).outerHeight(!0);i>t&&(t=i)}),t},totalWidth:function(){var t=0;return this.each(function(){t+=n(this).outerWidth(!0)}),t},totalHeight:function(){var t=0;return this.each(function(){t+=n(this).outerHeight(!0)}),t},matchWidths:function(){return this.setFullWidth(this.fullWidth())},matchHeights:function(){return this.setFullHeight(this.fullHeight())},lineUpHorizontal:function(t){var r=n(this[0]),f=left=r.getLeft(1),u=r.getTop(2),i=0;return this.each(function(){var r=n(this);r.alignElement(function(){return r.getLeft(1)},left,function(){return r.getTop(2)},u),left+=r.fullWidth(),i++,i==t&&(i=0,left=f,u+=r.fullHeight())})},lineUpVertical:function(){var t=n(this[0]),r=t.getLeft(2),i=t.getTop(1);return this.each(function(){var t=n(this);t.alignElement(function(){return t.getLeft(2)},r,function(){return t.getTop(1)},i),i+=t.fullHeight()})},fitToContent:function(){return this.each(function(){var i=-999999,r=-999999,t=n(this),u=t.css("display");t.css({display:"block"}),t.children().each(function(){var t=n(this),u=t.getRight(1),f=t.getBottom(1);u>i&&(i=u),f>r&&(r=f)}),t.sizeElement(function(){return t.getRight(4)},i,function(){return t.getBottom(4)},r),u!="block"&&t.css("display",u)})},stretchBlockHyperlinks:function(){this.find("a").each(function(){var t=n(this);t.css("display")=="block"&&(t.setFullWidth(t.parent().width()),t.setFullHeight(t.parent().height()))})},setHoverClass:function(t,i,r){return this.each(function(){if(t){var f=n(this),u=i||f;f.hover(function(){u.each(function(){this.hoverClass||(this.hoverClass=this.className)}),u.addClass(t),r&&u.removeClass(r)},function(){u.each(function(){this.hoverClass&&(this.className=this.hoverClass)})})}})},ddrData:function(){return this.data("ddrData")||this.data("ddrData",{}),this.data("ddrData")},setMenuData:function(t,i){return this.each(function(){var r=n(this),u=r.children("li"),e=r.attr("nid"),f="";i!=null&&(f=i.data("ddrData").path+"-"),r.data("ddrData",{isMenu:!0,id:e,level:t,path:f,childItems:u,parentItem:i,rendered:r,itemIndex:0}),u.setItemData(t,r),u.length>0&&(r.children("li:first").ddrData().first=!0,r.children("li:last").ddrData().last=!0)})},setItemData:function(t,i){this.each(function(){var r=n(this),f=r.children("ul"),o=r.find("ul"),s=r.parents("ul"),u=r.children("a"),e=u.length?u:r,h=e.children("img"),c=e.children("span"),l=r.attr("nid"),a=i.data("ddrData").path+i.data("ddrData").itemIndex++;r.data("ddrData",{isItem:!0,id:l,level:t,path:a,first:!1,last:!1,href:u.attr("href"),image:h.attr("src"),title:c.text(),isBreadcrumb:r.hasClass("breadcrumb"),isSelected:r.hasClass("selected"),isSeparator:r.hasClass("separator"),childMenu:f,parentMenu:i,allChildMenus:o,allParentMenus:s,rendered:r}),f.setMenuData(t+1,r)})},toDDRObject:function(){var t,i,r,n;if(this.data("ddrObject"))return this.data("ddrObject");t=this.ddrData(),i={},this.data("ddrObject",i),r=0;for(n in t)i[n]=n!="rendered"&&t[n]&&t[n].jquery?n.substr(n.length-1)=="s"?t[n].toDDRObjectArray():t[n].length==0?null:t[n].toDDRObject():t[n],r++;return i},toDDRObjectArray:function(){return n.map(this,function(t){return n(t).toDDRObject()})}}),DDR.Menu.createTable=function(){return n("
    ").attr("cellpadding",0).attr("cellspacing",0).attr("border",0)},DDR.Menu.addTableCell=function(t,i){var r=t.find("tr:first"),u;return r.length==0&&t.append(r=n("")),u=n("");u.append(r),e.each(function(){var t=n(this);r.append(t),r.children("td").length==i&&(r=n(""),u.append(r))}),r.children("td").length==0&&r.remove(),f.remove()}}),DDR.Menu.registerMenu=function(n,t){document.write("" + ); + + DDRjQuery(function ($) { + var jqContainer = $("#" + clientID); + var rootMenu = jqContainer.children("ul"); + + jqContainer.hide(); + while ((rootMenu.children("li").length == 0) && (rootMenu.children().length > 0)) { + rootMenu.html(rootMenu.children().html()); + } + if (rootMenu.children().length > 0) { + rootMenu.setMenuData(0, null); + + new DDR.Menu.Providers[dnnNavParams.MenuStyle](jqContainer, dnnNavParams).createRootMenu(); + + jqContainer.css({ "display": "block" }); + } + }); +} diff --git a/DNN Platform/Modules/NewDDRMenu/js/jquery.effects.js b/DNN Platform/Modules/NewDDRMenu/js/jquery.effects.js new file mode 100644 index 00000000000..b0d13187e87 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/js/jquery.effects.js @@ -0,0 +1,1069 @@ +(function(jQuery) { + /* + * jQuery UI Effects 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/ + */ + ; jQuery.effects || (function($) { + + $.effects = { + version: "1.7.2", + + // Saves a set of properties in a data storage + save: function(element, set) { + for (var i = 0; i < set.length; i++) { + if (set[i] !== null) element.data("ec.storage." + set[i], element[0].style[set[i]]); + } + }, + + // Restores a set of previously saved properties from a data storage + restore: function(element, set) { + for (var i = 0; i < set.length; i++) { + if (set[i] !== null) element.css(set[i], element.data("ec.storage." + set[i])); + } + }, + + setMode: function(el, mode) { + if (mode == 'toggle') mode = el.is(':hidden') ? 'show' : 'hide'; // Set for toggle + return mode; + }, + + getBaseline: function(origin, original) { // Translates a [top,left] array into a baseline value + // this should be a little more flexible in the future to handle a string & hash + var y, x; + switch (origin[0]) { + case 'top': y = 0; break; + case 'middle': y = 0.5; break; + case 'bottom': y = 1; break; + default: y = origin[0] / original.height; + }; + switch (origin[1]) { + case 'left': x = 0; break; + case 'center': x = 0.5; break; + case 'right': x = 1; break; + default: x = origin[1] / original.width; + }; + return { x: x, y: y }; + }, + + // Wraps the element around a wrapper that copies position properties + createWrapper: function(element) { + + //if the element is already wrapped, return it + if (element.parent().is('.ui-effects-wrapper')) + return element.parent(); + + //Cache width,height and float properties of the element, and create a wrapper around it + var props = { width: element.outerWidth(true), height: element.outerHeight(true), 'float': element.css('float') }; + element.wrap('
    '); + var wrapper = element.parent(); + + //Transfer the positioning of the element to the wrapper + if (element.css('position') == 'static') { + wrapper.css({ position: 'relative' }); + element.css({ position: 'relative' }); + } else { + var top = element.css('top'); if (isNaN(parseInt(top, 10))) top = 'auto'; + var left = element.css('left'); if (isNaN(parseInt(left, 10))) left = 'auto'; + wrapper.css({ position: element.css('position'), top: top, left: left, zIndex: element.css('z-index') }).show(); + element.css({ position: 'relative', top: 0, left: 0 }); + } + + wrapper.css(props); + return wrapper; + }, + + removeWrapper: function(element) { + if (element.parent().is('.ui-effects-wrapper')) + return element.parent().replaceWith(element); + return element; + }, + + setTransition: function(element, list, factor, value) { + value = value || {}; + $.each(list, function(i, x) { + unit = element.cssUnit(x); + if (unit[0] > 0) value[x] = unit[0] * factor + unit[1]; + }); + return value; + }, + + //Base function to animate from one class to another in a seamless transition + animateClass: function(value, duration, easing, callback) { + + var cb = (typeof easing == "function" ? easing : (callback ? callback : null)); + var ea = (typeof easing == "string" ? easing : null); + + return this.each(function() { + + var offset = {}; var that = $(this); var oldStyleAttr = that.attr("style") || ''; + if (typeof oldStyleAttr == 'object') oldStyleAttr = oldStyleAttr["cssText"]; /* Stupidly in IE, style is a object.. */ + if (value.toggle) { that.hasClass(value.toggle) ? value.remove = value.toggle : value.add = value.toggle; } + + //Let's get a style offset + var oldStyle = $.extend({}, (document.defaultView ? document.defaultView.getComputedStyle(this, null) : this.currentStyle)); + if (value.add) that.addClass(value.add); if (value.remove) that.removeClass(value.remove); + var newStyle = $.extend({}, (document.defaultView ? document.defaultView.getComputedStyle(this, null) : this.currentStyle)); + if (value.add) that.removeClass(value.add); if (value.remove) that.addClass(value.remove); + + // The main function to form the object for animation + for (var n in newStyle) { + if (typeof newStyle[n] != "function" && newStyle[n] /* No functions and null properties */ + && n.indexOf("Moz") == -1 && n.indexOf("length") == -1 /* No mozilla spezific render properties. */ + && newStyle[n] != oldStyle[n] /* Only values that have changed are used for the animation */ + && (n.match(/color/i) || (!n.match(/color/i) && !isNaN(parseInt(newStyle[n], 10)))) /* Only things that can be parsed to integers or colors */ + && (oldStyle.position != "static" || (oldStyle.position == "static" && !n.match(/left|top|bottom|right/))) /* No need for positions when dealing with static positions */ + ) offset[n] = newStyle[n]; + } + + that.animate(offset, duration, ea, function() { // Animate the newly constructed offset object + // Change style attribute back to original. For stupid IE, we need to clear the damn object. + if (typeof $(this).attr("style") == 'object') { $(this).attr("style")["cssText"] = ""; $(this).attr("style")["cssText"] = oldStyleAttr; } else $(this).attr("style", oldStyleAttr); + if (value.add) $(this).addClass(value.add); if (value.remove) $(this).removeClass(value.remove); + if (cb) cb.apply(this, arguments); + }); + + }); + } + }; + + + function _normalizeArguments(a, m) { + + var o = a[1] && a[1].constructor == Object ? a[1] : {}; if (m) o.mode = m; + var speed = a[1] && a[1].constructor != Object ? a[1] : (o.duration ? o.duration : a[2]); //either comes from options.duration or the secon/third argument + speed = $.fx.off ? 0 : typeof speed === "number" ? speed : $.fx.speeds[speed] || $.fx.speeds._default; + var callback = o.callback || ($.isFunction(a[1]) && a[1]) || ($.isFunction(a[2]) && a[2]) || ($.isFunction(a[3]) && a[3]); + + return [a[0], o, speed, callback]; + + } + + //Extend the methods of jQuery + $.fn.extend({ + + //Save old methods + _show: $.fn.show, + _hide: $.fn.hide, + __toggle: $.fn.toggle, + _addClass: $.fn.addClass, + _removeClass: $.fn.removeClass, + _toggleClass: $.fn.toggleClass, + + // New effect methods + effect: function(fx, options, speed, callback) { + return $.effects[fx] ? $.effects[fx].call(this, { method: fx, options: options || {}, duration: speed, callback: callback }) : null; + }, + + show: function() { + if (!arguments[0] || (arguments[0].constructor == Number || (/(slow|normal|fast)/).test(arguments[0]))) + return this._show.apply(this, arguments); + else { + return this.effect.apply(this, _normalizeArguments(arguments, 'show')); + } + }, + + hide: function() { + if (!arguments[0] || (arguments[0].constructor == Number || (/(slow|normal|fast)/).test(arguments[0]))) + return this._hide.apply(this, arguments); + else { + return this.effect.apply(this, _normalizeArguments(arguments, 'hide')); + } + }, + + toggle: function() { + if (!arguments[0] || + (arguments[0].constructor == Number || (/(slow|normal|fast)/).test(arguments[0])) || + ($.isFunction(arguments[0]) || typeof arguments[0] == 'boolean')) { + return this.__toggle.apply(this, arguments); + } else { + return this.effect.apply(this, _normalizeArguments(arguments, 'toggle')); + } + }, + + addClass: function(classNames, speed, easing, callback) { + return speed ? $.effects.animateClass.apply(this, [{ add: classNames }, speed, easing, callback]) : this._addClass(classNames); + }, + removeClass: function(classNames, speed, easing, callback) { + return speed ? $.effects.animateClass.apply(this, [{ remove: classNames }, speed, easing, callback]) : this._removeClass(classNames); + }, + toggleClass: function(classNames, speed, easing, callback) { + return ((typeof speed !== "boolean") && speed) ? $.effects.animateClass.apply(this, [{ toggle: classNames }, speed, easing, callback]) : this._toggleClass(classNames, speed); + }, + morph: function(remove, add, speed, easing, callback) { + return $.effects.animateClass.apply(this, [{ add: add, remove: remove }, speed, easing, callback]); + }, + switchClass: function() { + return this.morph.apply(this, arguments); + }, + + // helper functions + cssUnit: function(key) { + var style = this.css(key), val = []; + $.each(['em', 'px', '%', 'pt'], function(i, unit) { + if (style.indexOf(unit) > 0) + val = [parseFloat(style), unit]; + }); + return val; + } + }); + + /* + * jQuery Color Animations + * Copyright 2007 John Resig + * Released under the MIT and GPL licenses. + */ + + // We override the animation for all of these color styles + $.each(['backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'color', 'outlineColor'], function(i, attr) { + $.fx.step[attr] = function(fx) { + if (fx.state == 0) { + fx.start = getColor(fx.elem, attr); + fx.end = getRGB(fx.end); + } + + fx.elem.style[attr] = "rgb(" + [ + Math.max(Math.min(parseInt((fx.pos * (fx.end[0] - fx.start[0])) + fx.start[0], 10), 255), 0), + Math.max(Math.min(parseInt((fx.pos * (fx.end[1] - fx.start[1])) + fx.start[1], 10), 255), 0), + Math.max(Math.min(parseInt((fx.pos * (fx.end[2] - fx.start[2])) + fx.start[2], 10), 255), 0) + ].join(",") + ")"; + }; + }); + + // Color Conversion functions from highlightFade + // By Blair Mitchelmore + // http://jquery.offput.ca/highlightFade/ + + // Parse strings looking for color tuples [255,255,255] + function getRGB(color) { + var result; + + // Check if we're already dealing with an array of colors + if (color && color.constructor == Array && color.length == 3) + return color; + + // Look for rgb(num,num,num) + if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) + return [parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10)]; + + // Look for rgb(num%,num%,num%) + if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) + return [parseFloat(result[1]) * 2.55, parseFloat(result[2]) * 2.55, parseFloat(result[3]) * 2.55]; + + // Look for #a0b1c2 + if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)) + return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]; + + // Look for #fff + if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) + return [parseInt(result[1] + result[1], 16), parseInt(result[2] + result[2], 16), parseInt(result[3] + result[3], 16)]; + + // Look for rgba(0, 0, 0, 0) == transparent in Safari 3 + if (result = /rgba\(0, 0, 0, 0\)/.exec(color)) + return colors['transparent']; + + // Otherwise, we're most likely dealing with a named color + return colors[$.trim(color).toLowerCase()]; + } + + function getColor(elem, attr) { + var color; + + do { + color = $(elem).css(attr); + + // Keep going until we find an element that has color, or we hit the body + if (color != '' && color != 'transparent' || $.nodeName(elem, "body")) + break; + + attr = "backgroundColor"; + } while (elem = elem.parentNode); + + return getRGB(color); + }; + + // Some named colors to work with + // From Interface by Stefan Petre + // http://interface.eyecon.ro/ + + var colors = { + aqua: [0, 255, 255], + azure: [240, 255, 255], + beige: [245, 245, 220], + black: [0, 0, 0], + blue: [0, 0, 255], + brown: [165, 42, 42], + cyan: [0, 255, 255], + darkblue: [0, 0, 139], + darkcyan: [0, 139, 139], + darkgrey: [169, 169, 169], + darkgreen: [0, 100, 0], + darkkhaki: [189, 183, 107], + darkmagenta: [139, 0, 139], + darkolivegreen: [85, 107, 47], + darkorange: [255, 140, 0], + darkorchid: [153, 50, 204], + darkred: [139, 0, 0], + darksalmon: [233, 150, 122], + darkviolet: [148, 0, 211], + fuchsia: [255, 0, 255], + gold: [255, 215, 0], + green: [0, 128, 0], + indigo: [75, 0, 130], + khaki: [240, 230, 140], + lightblue: [173, 216, 230], + lightcyan: [224, 255, 255], + lightgreen: [144, 238, 144], + lightgrey: [211, 211, 211], + lightpink: [255, 182, 193], + lightyellow: [255, 255, 224], + lime: [0, 255, 0], + magenta: [255, 0, 255], + maroon: [128, 0, 0], + navy: [0, 0, 128], + olive: [128, 128, 0], + orange: [255, 165, 0], + pink: [255, 192, 203], + purple: [128, 0, 128], + violet: [128, 0, 128], + red: [255, 0, 0], + silver: [192, 192, 192], + white: [255, 255, 255], + yellow: [255, 255, 0], + transparent: [255, 255, 255] + }; + + /* + * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/ + * + * Uses the built in easing capabilities added In jQuery 1.1 + * to offer multiple easing options + * + * TERMS OF USE - jQuery Easing + * + * Open source under the BSD License. + * + * Copyright 2008 George McGinley Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + // t: current time, b: begInnIng value, c: change In value, d: duration + $.easing.jswing = $.easing.swing; + + $.extend($.easing, +{ + def: 'easeOutQuad', + swing: function(x, t, b, c, d) { + //alert($.easing.default); + return $.easing[$.easing.def](x, t, b, c, d); + }, + easeInQuad: function(x, t, b, c, d) { + return c * (t /= d) * t + b; + }, + easeOutQuad: function(x, t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + easeInOutQuad: function(x, t, b, c, d) { + if ((t /= d / 2) < 1) return c / 2 * t * t + b; + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + easeInCubic: function(x, t, b, c, d) { + return c * (t /= d) * t * t + b; + }, + easeOutCubic: function(x, t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + }, + easeInOutCubic: function(x, t, b, c, d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t + b; + return c / 2 * ((t -= 2) * t * t + 2) + b; + }, + easeInQuart: function(x, t, b, c, d) { + return c * (t /= d) * t * t * t + b; + }, + easeOutQuart: function(x, t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + }, + easeInOutQuart: function(x, t, b, c, d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b; + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + }, + easeInQuint: function(x, t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + }, + easeOutQuint: function(x, t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + }, + easeInOutQuint: function(x, t, b, c, d) { + if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b; + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + }, + easeInSine: function(x, t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + }, + easeOutSine: function(x, t, b, c, d) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; + }, + easeInOutSine: function(x, t, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + }, + easeInExpo: function(x, t, b, c, d) { + return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + }, + easeOutExpo: function(x, t, b, c, d) { + return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + }, + easeInOutExpo: function(x, t, b, c, d) { + if (t == 0) return b; + if (t == d) return b + c; + if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + easeInCirc: function(x, t, b, c, d) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + }, + easeOutCirc: function(x, t, b, c, d) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + }, + easeInOutCirc: function(x, t, b, c, d) { + if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + }, + easeInElastic: function(x, t, b, c, d) { + var s = 1.70158; var p = 0; var a = c; + if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; + if (a < Math.abs(c)) { a = c; var s = p / 4; } + else var s = p / (2 * Math.PI) * Math.asin(c / a); + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + }, + easeOutElastic: function(x, t, b, c, d) { + var s = 1.70158; var p = 0; var a = c; + if (t == 0) return b; if ((t /= d) == 1) return b + c; if (!p) p = d * .3; + if (a < Math.abs(c)) { a = c; var s = p / 4; } + else var s = p / (2 * Math.PI) * Math.asin(c / a); + return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; + }, + easeInOutElastic: function(x, t, b, c, d) { + var s = 1.70158; var p = 0; var a = c; + if (t == 0) return b; if ((t /= d / 2) == 2) return b + c; if (!p) p = d * (.3 * 1.5); + if (a < Math.abs(c)) { a = c; var s = p / 4; } + else var s = p / (2 * Math.PI) * Math.asin(c / a); + if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b; + }, + easeInBack: function(x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c * (t /= d) * t * ((s + 1) * t - s) + b; + }, + easeOutBack: function(x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + }, + easeInOutBack: function(x, t, b, c, d, s) { + if (s == undefined) s = 1.70158; + if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + }, + easeInBounce: function(x, t, b, c, d) { + return c - $.easing.easeOutBounce(x, d - t, 0, c, d) + b; + }, + easeOutBounce: function(x, t, b, c, d) { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } else if (t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b; + } else if (t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b; + } else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b; + } + }, + easeInOutBounce: function(x, t, b, c, d) { + if (t < d / 2) return $.easing.easeInBounce(x, t * 2, 0, c, d) * .5 + b; + return $.easing.easeOutBounce(x, t * 2 - d, 0, c, d) * .5 + c * .5 + b; + } +}); + + /* + * + * TERMS OF USE - EASING EQUATIONS + * + * Open source under the BSD License. + * + * Copyright 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * Neither the name of the author nor the names of contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + })(jQuery); + /* + * jQuery UI Effects Blind 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Blind + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.blind = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'vertical'; // Default direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({ overflow: 'hidden' }); // Create Wrapper + var ref = (direction == 'vertical') ? 'height' : 'width'; + var distance = (direction == 'vertical') ? wrapper.height() : wrapper.width(); + if (mode == 'show') wrapper.css(ref, 0); // Shift + + // Animation + var animation = {}; + animation[ref] = mode == 'show' ? distance : 0; + + // Animate + wrapper.animate(animation, o.duration, o.options.easing, function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + }); + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Clip 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Clip + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.clip = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left', 'height', 'width']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'vertical'; // Default direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({ overflow: 'hidden' }); // Create Wrapper + var animate = el[0].tagName == 'IMG' ? wrapper : el; + var ref = { + size: (direction == 'vertical') ? 'height' : 'width', + position: (direction == 'vertical') ? 'top' : 'left' + }; + var distance = (direction == 'vertical') ? animate.height() : animate.width(); + if (mode == 'show') { animate.css(ref.size, 0); animate.css(ref.position, distance / 2); } // Shift + + // Animation + var animation = {}; + animation[ref.size] = mode == 'show' ? distance : 0; + animation[ref.position] = mode == 'show' ? 0 : distance / 2; + + // Animate + animate.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + } + }); + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Drop 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Drop + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.drop = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left', 'opacity']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var direction = o.options.direction || 'left'; // Default Direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + var distance = o.options.distance || (ref == 'top' ? el.outerHeight({ margin: true }) / 2 : el.outerWidth({ margin: true }) / 2); + if (mode == 'show') el.css('opacity', 0).css(ref, motion == 'pos' ? -distance : distance); // Shift + + // Animation + var animation = { opacity: mode == 'show' ? 1 : 0 }; + animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance; + + // Animate + el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + } + }); + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Explode 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Explode + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.explode = function(o) { + + return this.queue(function() { + + var rows = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3; + var cells = o.options.pieces ? Math.round(Math.sqrt(o.options.pieces)) : 3; + + o.options.mode = o.options.mode == 'toggle' ? ($(this).is(':visible') ? 'hide' : 'show') : o.options.mode; + var el = $(this).show().css('visibility', 'hidden'); + var offset = el.offset(); + + //Substract the margins - not fixing the problem yet. + offset.top -= parseInt(el.css("marginTop"), 10) || 0; + offset.left -= parseInt(el.css("marginLeft"), 10) || 0; + + var width = el.outerWidth(true); + var height = el.outerHeight(true); + + for (var i = 0; i < rows; i++) { // = + for (var j = 0; j < cells; j++) { // || + el + .clone() + .appendTo('body') + .wrap('
    ') + .css({ + position: 'absolute', + visibility: 'visible', + left: -j * (width / cells), + top: -i * (height / rows) + }) + .parent() + .addClass('ui-effects-explode') + .css({ + position: 'absolute', + overflow: 'hidden', + width: width / cells, + height: height / rows, + left: offset.left + j * (width / cells) + (o.options.mode == 'show' ? (j - Math.floor(cells / 2)) * (width / cells) : 0), + top: offset.top + i * (height / rows) + (o.options.mode == 'show' ? (i - Math.floor(rows / 2)) * (height / rows) : 0), + opacity: o.options.mode == 'show' ? 0 : 1 + }).animate({ + left: offset.left + j * (width / cells) + (o.options.mode == 'show' ? 0 : (j - Math.floor(cells / 2)) * (width / cells)), + top: offset.top + i * (height / rows) + (o.options.mode == 'show' ? 0 : (i - Math.floor(rows / 2)) * (height / rows)), + opacity: o.options.mode == 'show' ? 1 : 0 + }, o.duration || 500); + } + } + + // Set a timeout, to call the callback approx. when the other animations have finished + setTimeout(function() { + + o.options.mode == 'show' ? el.css({ visibility: 'visible' }) : el.css({ visibility: 'visible' }).hide(); + if (o.callback) o.callback.apply(el[0]); // Callback + el.dequeue(); + + $('div.ui-effects-explode').remove(); + + }, o.duration || 500); + + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Fold 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.fold = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var size = o.options.size || 15; // Default fold size + var horizFirst = !(!o.options.horizFirst); // Ensure a boolean value + var duration = o.duration ? o.duration / 2 : $.fx.speeds._default / 2; + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + var wrapper = $.effects.createWrapper(el).css({ overflow: 'hidden' }); // Create Wrapper + var widthFirst = ((mode == 'show') != horizFirst); + var ref = widthFirst ? ['width', 'height'] : ['height', 'width']; + var distance = widthFirst ? [wrapper.width(), wrapper.height()] : [wrapper.height(), wrapper.width()]; + var percent = /([0-9]+)%/.exec(size); + if (percent) size = parseInt(percent[1], 10) / 100 * distance[mode == 'hide' ? 0 : 1]; + if (mode == 'show') wrapper.css(horizFirst ? { height: 0, width: size} : { height: size, width: 0 }); // Shift + + // Animation + var animation1 = {}, animation2 = {}; + animation1[ref[0]] = mode == 'show' ? distance[0] : size; + animation2[ref[1]] = mode == 'show' ? distance[1] : 0; + + // Animate + wrapper.animate(animation1, duration, o.options.easing) + .animate(animation2, duration, o.options.easing, function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(el[0], arguments); // Callback + el.dequeue(); + }); + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Scale 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Scale + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.puff = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this); + + // Set options + var options = $.extend(true, {}, o.options); + var mode = $.effects.setMode(el, o.options.mode || 'hide'); // Set Mode + var percent = parseInt(o.options.percent, 10) || 150; // Set default puff percent + options.fade = true; // It's not a puff if it doesn't fade! :) + var original = { height: el.height(), width: el.width() }; // Save original + + // Adjust + var factor = percent / 100; + el.from = (mode == 'hide') ? original : { height: original.height * factor, width: original.width * factor }; + + // Animation + options.from = el.from; + options.percent = (mode == 'hide') ? percent : 100; + options.mode = mode; + + // Animate + el.effect('scale', options, o.duration, o.callback); + el.dequeue(); + }); + + }; + + $.effects.scale = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this); + + // Set options + var options = $.extend(true, {}, o.options); + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var percent = parseInt(o.options.percent, 10) || (parseInt(o.options.percent, 10) == 0 ? 0 : (mode == 'hide' ? 0 : 100)); // Set default scaling percent + var direction = o.options.direction || 'both'; // Set default axis + var origin = o.options.origin; // The origin of the scaling + if (mode != 'effect') { // Set default origin and restore for show/hide + options.origin = origin || ['middle', 'center']; + options.restore = true; + } + var original = { height: el.height(), width: el.width() }; // Save original + el.from = o.options.from || (mode == 'show' ? { height: 0, width: 0} : original); // Default from state + + // Adjust + var factor = { // Set scaling factor + y: direction != 'horizontal' ? (percent / 100) : 1, + x: direction != 'vertical' ? (percent / 100) : 1 + }; + el.to = { height: original.height * factor.y, width: original.width * factor.x }; // Set to state + + if (o.options.fade) { // Fade option to support puff + if (mode == 'show') { el.from.opacity = 0; el.to.opacity = 1; }; + if (mode == 'hide') { el.from.opacity = 1; el.to.opacity = 0; }; + }; + + // Animation + options.from = el.from; options.to = el.to; options.mode = mode; + + // Animate + el.effect('size', options, o.duration, o.callback); + el.dequeue(); + }); + + }; + + $.effects.size = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left', 'width', 'height', 'overflow', 'opacity']; + var props1 = ['position', 'top', 'left', 'overflow', 'opacity']; // Always restore + var props2 = ['width', 'height', 'overflow']; // Copy for children + var cProps = ['fontSize']; + var vProps = ['borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom']; + var hProps = ['borderLeftWidth', 'borderRightWidth', 'paddingLeft', 'paddingRight']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'effect'); // Set Mode + var restore = o.options.restore || false; // Default restore + var scale = o.options.scale || 'both'; // Default scale mode + var origin = o.options.origin; // The origin of the sizing + var original = { height: el.height(), width: el.width() }; // Save original + el.from = o.options.from || original; // Default from state + el.to = o.options.to || original; // Default to state + // Adjust + if (origin) { // Calculate baseline shifts + var baseline = $.effects.getBaseline(origin, original); + el.from.top = (original.height - el.from.height) * baseline.y; + el.from.left = (original.width - el.from.width) * baseline.x; + el.to.top = (original.height - el.to.height) * baseline.y; + el.to.left = (original.width - el.to.width) * baseline.x; + }; + var factor = { // Set scaling factor + from: { y: el.from.height / original.height, x: el.from.width / original.width }, + to: { y: el.to.height / original.height, x: el.to.width / original.width } + }; + if (scale == 'box' || scale == 'both') { // Scale the css box + if (factor.from.y != factor.to.y) { // Vertical props scaling + props = props.concat(vProps); + el.from = $.effects.setTransition(el, vProps, factor.from.y, el.from); + el.to = $.effects.setTransition(el, vProps, factor.to.y, el.to); + }; + if (factor.from.x != factor.to.x) { // Horizontal props scaling + props = props.concat(hProps); + el.from = $.effects.setTransition(el, hProps, factor.from.x, el.from); + el.to = $.effects.setTransition(el, hProps, factor.to.x, el.to); + }; + }; + if (scale == 'content' || scale == 'both') { // Scale the content + if (factor.from.y != factor.to.y) { // Vertical props scaling + props = props.concat(cProps); + el.from = $.effects.setTransition(el, cProps, factor.from.y, el.from); + el.to = $.effects.setTransition(el, cProps, factor.to.y, el.to); + }; + }; + $.effects.save(el, restore ? props : props1); el.show(); // Save & Show + $.effects.createWrapper(el); // Create Wrapper + el.css('overflow', 'hidden').css(el.from); // Shift + + // Animate + if (scale == 'content' || scale == 'both') { // Scale the children + vProps = vProps.concat(['marginTop', 'marginBottom']).concat(cProps); // Add margins/font-size + hProps = hProps.concat(['marginLeft', 'marginRight']); // Add margins + props2 = props.concat(vProps).concat(hProps); // Concat + el.find("*[width]").each(function() { + child = $(this); + if (restore) $.effects.save(child, props2); + var c_original = { height: child.height(), width: child.width() }; // Save original + child.from = { height: c_original.height * factor.from.y, width: c_original.width * factor.from.x }; + child.to = { height: c_original.height * factor.to.y, width: c_original.width * factor.to.x }; + if (factor.from.y != factor.to.y) { // Vertical props scaling + child.from = $.effects.setTransition(child, vProps, factor.from.y, child.from); + child.to = $.effects.setTransition(child, vProps, factor.to.y, child.to); + }; + if (factor.from.x != factor.to.x) { // Horizontal props scaling + child.from = $.effects.setTransition(child, hProps, factor.from.x, child.from); + child.to = $.effects.setTransition(child, hProps, factor.to.x, child.to); + }; + child.css(child.from); // Shift children + child.animate(child.to, o.duration, o.options.easing, function() { + if (restore) $.effects.restore(child, props2); // Restore children + }); // Animate children + }); + }; + + // Animate + el.animate(el.to, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, restore ? props : props1); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + } + }); + + }); + + }; + + })(jQuery); + /* + * jQuery UI Effects Slide 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Slide + * + * Depends: + * effects.core.js + */ + (function($) { + + $.effects.slide = function(o) { + + return this.queue(function() { + + // Create element + var el = $(this), props = ['position', 'top', 'left']; + + // Set options + var mode = $.effects.setMode(el, o.options.mode || 'show'); // Set Mode + var direction = o.options.direction || 'left'; // Default Direction + + // Adjust + $.effects.save(el, props); el.show(); // Save & Show + $.effects.createWrapper(el).css({ overflow: 'hidden' }); // Create Wrapper + var ref = (direction == 'up' || direction == 'down') ? 'top' : 'left'; + var motion = (direction == 'up' || direction == 'left') ? 'pos' : 'neg'; + var distance = o.options.distance || (ref == 'top' ? el.outerHeight({ margin: true }) : el.outerWidth({ margin: true })); + if (mode == 'show') el.css(ref, motion == 'pos' ? -distance : distance); // Shift + + // Animation + var animation = {}; + animation[ref] = (mode == 'show' ? (motion == 'pos' ? '+=' : '-=') : (motion == 'pos' ? '-=' : '+=')) + distance; + + // Animate + el.animate(animation, { queue: false, duration: o.duration, easing: o.options.easing, complete: function() { + if (mode == 'hide') el.hide(); // Hide + $.effects.restore(el, props); $.effects.removeWrapper(el); // Restore + if (o.callback) o.callback.apply(this, arguments); // Callback + el.dequeue(); + } + }); + + }); + + }; + + })(jQuery); +})(DDRjQuery); diff --git a/DNN Platform/Modules/NewDDRMenu/packages.config b/DNN Platform/Modules/NewDDRMenu/packages.config new file mode 100644 index 00000000000..ea1e2740db7 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/packages.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DNN Platform/Modules/NewDDRMenu/web.Debug.config b/DNN Platform/Modules/NewDDRMenu/web.Debug.config new file mode 100644 index 00000000000..ccf59884f90 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/DNN Platform/Modules/NewDDRMenu/web.Release.config b/DNN Platform/Modules/NewDDRMenu/web.Release.config new file mode 100644 index 00000000000..9b90a9c21c4 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/DNN Platform/Modules/NewDDRMenu/web.config b/DNN Platform/Modules/NewDDRMenu/web.config new file mode 100644 index 00000000000..9a2e6488394 --- /dev/null +++ b/DNN Platform/Modules/NewDDRMenu/web.config @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DNN_Platform.sln b/DNN_Platform.sln index a3313b634db..5e71dc5999c 100644 --- a/DNN_Platform.sln +++ b/DNN_Platform.sln @@ -693,6 +693,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.ContentSecurityP EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Web.MvcPipeline", "DNN Platform\DotNetNuke.Web.MvcPipeline\DotNetNuke.Web.MvcPipeline.csproj", "{AA3EE19B-81A0-3766-E8F4-424C2425A8D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Modules.NewDDRMenu", "DNN Platform\Modules\NewDDRMenu\DotNetNuke.Modules.NewDDRMenu.csproj", "{4366908E-2852-40EB-8675-610E0DA2AB5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Cloud_Debug|Any CPU = Cloud_Debug|Any CPU @@ -2461,6 +2463,30 @@ Global {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|Any CPU.Build.0 = Release|Any CPU {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|x86.ActiveCfg = Release|Any CPU {AA3EE19B-81A0-3766-E8F4-424C2425A8D3}.Release-Net45|x86.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Debug|Any CPU.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Debug|Any CPU.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Debug|x86.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Debug|x86.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Release|Any CPU.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Release|Any CPU.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Release|x86.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Cloud_Release|x86.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug|x86.ActiveCfg = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug|x86.Build.0 = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug-Net45|Any CPU.ActiveCfg = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug-Net45|Any CPU.Build.0 = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug-Net45|x86.ActiveCfg = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Debug-Net45|x86.Build.0 = Debug|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release|Any CPU.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release|x86.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release|x86.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|Any CPU.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|Any CPU.Build.0 = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|x86.ActiveCfg = Release|Any CPU + {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2606,6 +2632,7 @@ Global {549CCB04-6321-4E6B-88C1-06FAC574D061} = {29273BE6-1AA8-4970-98A0-41BFFEEDA67B} {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} + {4366908E-2852-40EB-8675-610E0DA2AB5D} = {FDDC95FE-3341-4AED-A93E-7A5DF85A55C2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46B6A641-57EB-4B19-B199-23E6FC2AB40B} From 23b6b13baa427fcef935c5d52c6684e6a7226e73 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Sun, 23 Mar 2025 16:59:30 +0100 Subject: [PATCH 010/146] Rewire the way we detect MVC skins --- .../Entities/Urls/MvcUrlRewriterController.cs | 2 +- .../Components/Themes/ThemesController.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs index 4fa12c926ed..84d9a6097a7 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs @@ -37,7 +37,7 @@ internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, if (!string.IsNullOrEmpty(skinSrc)) { - mvcCtl = skinSrc.ToLowerInvariant().StartsWith("[m]"); + mvcCtl = skinSrc.ToLowerInvariant().EndsWith(".cshtml"); } /* diff --git a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs index 9b5b31cd2a9..473ca57baad 100644 --- a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs +++ b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs @@ -98,6 +98,10 @@ public IList GetThemeFiles(PortalSettings portalSettings, ThemeIn var canDeleteSkin = SkinController.CanDeleteSkin(themePath, portalSettings.HomeDirectoryMapPath); var arrFiles = Directory.GetFiles(themePath, "*.ascx"); + if (arrFiles.Count() == 0) + { + arrFiles = Directory.GetFiles(themePath, "*.cshtml"); + } foreach (var strFile in arrFiles) { @@ -514,6 +518,10 @@ private static string GetDefaultThemeFileName(string themePath, ThemeType type) var themeFiles = new List(); var folderPath = Path.Combine(Globals.ApplicationMapPath, themePath); themeFiles.AddRange(Directory.GetFiles(folderPath, "*.ascx")); + if (themeFiles.Count == 0) + { + themeFiles.AddRange(Directory.GetFiles(folderPath, "*.cshtml")); + } var defaultFile = themeFiles.FirstOrDefault(i => { From 825ae25da7f3a92271a62a1e57f7becdddb325b2 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Mon, 24 Mar 2025 13:12:11 +0100 Subject: [PATCH 011/146] Ability to replace the urlrewriter --- .../DotNetNuke.Web.MvcPipeline.csproj | 7 +++++++ .../HttpModules/MvcUrlRewriteModule.cs | 18 ++++++++++++++++++ .../DotNetNuke.Web.MvcPipeline/Library.build | 15 --------------- 3 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/HttpModules/MvcUrlRewriteModule.cs delete mode 100644 DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index d22f474b8f2..eb99062f39f 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -47,5 +47,12 @@ + + + + + + + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/HttpModules/MvcUrlRewriteModule.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/HttpModules/MvcUrlRewriteModule.cs new file mode 100644 index 00000000000..dcd1d510603 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/HttpModules/MvcUrlRewriteModule.cs @@ -0,0 +1,18 @@ +using System.Web; +using DotNetNuke.Web.MvcPipeline.Entities.Urls; + +namespace DotNetNuke.Web.MvcPipeline.HttpModules +{ + public class MvcUrlRewriteModule : IHttpModule + { + public void Dispose() + { + } + + public void Init(HttpApplication context) + { + var mvcRewriter = new MvcAdvancedUrlRewriter(); + context.BeginRequest += mvcRewriter.RewriteUrl; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build b/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build deleted file mode 100644 index a1f95545d12..00000000000 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Library.build +++ /dev/null @@ -1,15 +0,0 @@ - - - $(MSBuildProjectDirectory)\..\.. - - - - - - - - - - - - \ No newline at end of file From 26a416500089f3b3358006b022ed31ef82aece02 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Mon, 24 Mar 2025 13:12:44 +0100 Subject: [PATCH 012/146] Changes that hard wired to ascx files for the skin --- DNN Platform/Library/Entities/Tabs/TabInfo.cs | 3 ++- .../Components/Pages/PagesControllerImpl.cs | 2 ++ .../Components/Themes/ThemesController.cs | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DNN Platform/Library/Entities/Tabs/TabInfo.cs b/DNN Platform/Library/Entities/Tabs/TabInfo.cs index 9e5b6d58248..0cb0ea1329b 100644 --- a/DNN Platform/Library/Entities/Tabs/TabInfo.cs +++ b/DNN Platform/Library/Entities/Tabs/TabInfo.cs @@ -1067,7 +1067,8 @@ private XmlDocument LoadDocType() var xmlSkinDocType = new XmlDocument { XmlResolver = null }; // default to the skinname.doctype.xml to allow the individual skin to override the skin package - var skinFileName = HttpContext.Current.Server.MapPath(this.SkinSrc.Replace(".ascx", ".doctype.xml")); + var skinSrc = this.SkinSrc.Substring(0, this.SkinSrc.LastIndexOf('.')); + var skinFileName = HttpContext.Current.Server.MapPath(skinSrc + ".doctype.xml"); if (File.Exists(skinFileName)) { xmlSkinDocType.Load(skinFileName); diff --git a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs index 7fb98d0882d..7c67e79eefe 100644 --- a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs +++ b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs @@ -1463,6 +1463,8 @@ private string GetSkinSrc(PageSettings pageSettings) return null; } + pageSettings.SkinSrc = pageSettings.SkinSrc.Replace(".cshtml.ascx", ".cshtml"); // 4MVCPL + return pageSettings.SkinSrc; } diff --git a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs index 473ca57baad..355e52b23b1 100644 --- a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs +++ b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs @@ -164,6 +164,7 @@ public ThemeFileInfo GetThemeFile(PortalSettings portalSettings, string filePath public void ApplyTheme(int portalId, ThemeFileInfo themeFile, ApplyThemeScope scope) { var skinPath = themeFile.Path + ".ascx"; + skinPath = skinPath.Replace(".cshtml.ascx", ".cshtml"); // 4MVCPL switch (themeFile.Type) { From 261a156448a9f7e517692f36a611574777531b07 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Mon, 31 Mar 2025 19:43:20 +0200 Subject: [PATCH 013/146] Revert changes to core project --- DNN Platform/Library/Entities/Tabs/TabInfo.cs | 3 +-- .../Components/Pages/PagesControllerImpl.cs | 2 -- .../Components/Themes/ThemesController.cs | 9 --------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/DNN Platform/Library/Entities/Tabs/TabInfo.cs b/DNN Platform/Library/Entities/Tabs/TabInfo.cs index 0cb0ea1329b..9e5b6d58248 100644 --- a/DNN Platform/Library/Entities/Tabs/TabInfo.cs +++ b/DNN Platform/Library/Entities/Tabs/TabInfo.cs @@ -1067,8 +1067,7 @@ private XmlDocument LoadDocType() var xmlSkinDocType = new XmlDocument { XmlResolver = null }; // default to the skinname.doctype.xml to allow the individual skin to override the skin package - var skinSrc = this.SkinSrc.Substring(0, this.SkinSrc.LastIndexOf('.')); - var skinFileName = HttpContext.Current.Server.MapPath(skinSrc + ".doctype.xml"); + var skinFileName = HttpContext.Current.Server.MapPath(this.SkinSrc.Replace(".ascx", ".doctype.xml")); if (File.Exists(skinFileName)) { xmlSkinDocType.Load(skinFileName); diff --git a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs index 7c67e79eefe..7fb98d0882d 100644 --- a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs +++ b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Pages/PagesControllerImpl.cs @@ -1463,8 +1463,6 @@ private string GetSkinSrc(PageSettings pageSettings) return null; } - pageSettings.SkinSrc = pageSettings.SkinSrc.Replace(".cshtml.ascx", ".cshtml"); // 4MVCPL - return pageSettings.SkinSrc; } diff --git a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs index 355e52b23b1..9b5b31cd2a9 100644 --- a/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs +++ b/Dnn.AdminExperience/Dnn.PersonaBar.Extensions/Components/Themes/ThemesController.cs @@ -98,10 +98,6 @@ public IList GetThemeFiles(PortalSettings portalSettings, ThemeIn var canDeleteSkin = SkinController.CanDeleteSkin(themePath, portalSettings.HomeDirectoryMapPath); var arrFiles = Directory.GetFiles(themePath, "*.ascx"); - if (arrFiles.Count() == 0) - { - arrFiles = Directory.GetFiles(themePath, "*.cshtml"); - } foreach (var strFile in arrFiles) { @@ -164,7 +160,6 @@ public ThemeFileInfo GetThemeFile(PortalSettings portalSettings, string filePath public void ApplyTheme(int portalId, ThemeFileInfo themeFile, ApplyThemeScope scope) { var skinPath = themeFile.Path + ".ascx"; - skinPath = skinPath.Replace(".cshtml.ascx", ".cshtml"); // 4MVCPL switch (themeFile.Type) { @@ -519,10 +514,6 @@ private static string GetDefaultThemeFileName(string themePath, ThemeType type) var themeFiles = new List(); var folderPath = Path.Combine(Globals.ApplicationMapPath, themePath); themeFiles.AddRange(Directory.GetFiles(folderPath, "*.ascx")); - if (themeFiles.Count == 0) - { - themeFiles.AddRange(Directory.GetFiles(folderPath, "*.cshtml")); - } var defaultFile = themeFiles.FirstOrDefault(i => { From 44c90c7e602f6a55f2696945d67ef0580c368e93 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Mon, 31 Mar 2025 19:56:43 +0200 Subject: [PATCH 014/146] Rolling back mvc skin detection to what it was previously --- .../Entities/Urls/MvcUrlRewriterController.cs | 82 ++++++------------- 1 file changed, 26 insertions(+), 56 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs index 84d9a6097a7..0429afaec0c 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs @@ -10,38 +10,9 @@ internal class MvcUrlRewriterController { internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) { + var mvcCtls = new[] { "Module", "Terms", "Privacy" }; bool mvcCtl = false; - var skinSrc = string.Empty; - - if (context.Items.Contains("PortalSettings")) - { - var ps = (PortalSettings)context.Items["PortalSettings"]; - if (ps != null) - { - skinSrc = PortalSettings.Current.ActiveTab.SkinSrc; - if (string.IsNullOrEmpty(skinSrc)) - { - skinSrc = PortalSettings.Current.DefaultPortalSkin; - } - } - } - - if (string.IsNullOrEmpty(skinSrc) && tabId > 0 && portalId > -1) - { - var tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - skinSrc = tab.SkinSrc; - } - } - - if (!string.IsNullOrEmpty(skinSrc)) - { - mvcCtl = skinSrc.ToLowerInvariant().EndsWith(".cshtml"); - } - /* - var mvcCtls = new[] { "Module", "Terms", "Privacy" }; bool mvcSkin = false; if (context.Items.Contains("PortalSettings")) { @@ -52,48 +23,47 @@ internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, PortalSettings.Current.ActiveTab.SkinSrc.EndsWith("mvc"); } } + */ if (result.RewritePath.Contains("&ctl=")) { - foreach (var item in mvcCtls) - { - mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); - } - - if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) - { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) + foreach (var item in mvcCtls) { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) - { - mvcCtl = tab.GetTags().Contains("mvc"); - } + mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); } - // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); - } + if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) + { + TabInfo tab = null; + if (tabId > 0 && portalId > -1) + { + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } + } + + // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); + } } else { - TabInfo tab = null; - if (tabId > 0 && portalId > -1) - { - tab = TabController.Instance.GetTab(tabId, portalId, false); - if (tab != null) + TabInfo tab = null; + if (tabId > 0 && portalId > -1) { - mvcCtl = tab.GetTags().Contains("mvc"); + tab = TabController.Instance.GetTab(tabId, portalId, false); + if (tab != null) + { + mvcCtl = tab.GetTags().Contains("mvc"); + } } - } - // mvcCtl = result.RawUrl.EndsWith("mvc"); + // mvcCtl = result.RawUrl.EndsWith("mvc"); } mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; mvcCtl = mvcCtl || result.RewritePath.Contains("mvcpage=yes") || queryStringCol["mvcpage"] == "yes"; - */ - return mvcCtl; } } From bcba2c60561645f5f228eb30e2f40899aeacba2b Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Mon, 31 Mar 2025 23:22:52 +0200 Subject: [PATCH 015/146] Fixing routing --- .../Routing/MvcRoutingManager.cs | 28 +++++++++++-------- .../DotNetNuke.Web.MvcPipeline/Startup.cs | 5 ++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs index 069d7972726..12e4c0c69db 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs @@ -4,6 +4,7 @@ namespace DotNetNuke.Web.MvcPipeline.Routing { using System; + using System.Web.Configuration; using System.Web.Http; using System.Web.Mvc; using System.Web.Routing; @@ -47,19 +48,22 @@ private static bool IsTracingEnabled() return !string.IsNullOrEmpty(configValue) && Convert.ToBoolean(configValue); } - private void RegisterSystemRoutes() - { - var route = new Route( - "DesktopModules/{controller}/{action}/{tabid}/{language}", - new RouteValueDictionary(new { action = "Index", tabid = UrlParameter.Optional, language = UrlParameter.Optional }), - new DnnMvcPageRouteHandler()); + private void RegisterSystemRoutes() + { + var dataTokens = new RouteValueDictionary(); + var ns = new string[] { "DotNetNuke.Web.MvcPipeline.Website.Controllers" }; + dataTokens["Namespaces"] = ns; - // route.DataTokens = new RouteValueDictionary(); - // ConstraintValidation.Validate(route); - // route.SetNameSpaces(new string[] { "DotNetNuke.Framework.Controllers" }); - // route.SetName("Default"); - this.routes.Add(route); - } + var route = new Route( + "DesktopModules/{controller}/{action}/{tabid}/{language}", + new RouteValueDictionary(new { action = "Index", tabid = UrlParameter.Optional, language = UrlParameter.Optional }), + null, // No constraints + dataTokens, + new DnnMvcPageRouteHandler() + ); + + this.routes.Add(route); + } private void LocateServicesAndMapRoutes() { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs index a1665141a14..3926e6547d2 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Startup.cs @@ -5,11 +5,14 @@ namespace DotNetNuke.Web.MvcPipeline { using DotNetNuke.Common; + using DotNetNuke.Common.Internal; using DotNetNuke.ContentSecurityPolicy; using DotNetNuke.DependencyInjection; using DotNetNuke.Web.Mvc.Extensions; using DotNetNuke.Web.MvcPipeline.Framework; + using DotNetNuke.Web.MvcPipeline.Routing; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; using System.Web.Mvc; public class Startup : IDnnStartup @@ -17,6 +20,8 @@ public class Startup : IDnnStartup /// public void ConfigureServices(IServiceCollection services) { + services.TryAddEnumerable(new ServiceDescriptor(typeof(IRoutingManager), typeof(MvcRoutingManager), ServiceLifetime.Singleton)); + services.AddMvcControllers(); services.AddTransient(); services.AddTransient(); From 6b1f3179d0eab3f1baade722aca3c3ab0b2aa571 Mon Sep 17 00:00:00 2001 From: Peter Donker Date: Tue, 1 Apr 2025 12:08:53 +0200 Subject: [PATCH 016/146] Fix layout issue --- .../Website/Controllers/DefaultController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs index 46c53b4faeb..2df02c5008c 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs @@ -113,7 +113,7 @@ public ActionResult Page(int tabid, string language) this.RegisterScriptsAndStylesheets(model); // this.Response.AddHeader("Content-Security-Policy", $"default-src 'self';base-uri 'self';form-action 'self';object-src 'none'; img-src *; style-src 'self' 'unsafe-inline';font-src *; script-src * 'unsafe-inline';"); - return this.View(model.Skin.RazorFile, "Layout", model); + return this.View(model.Skin.RazorFile, model); } private void RegisterScriptsAndStylesheets(PageModel page) From af884e507b586fc3f3f8887ff3f80b81f5af8819 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 09:38:44 +0200 Subject: [PATCH 017/146] - avoid dublicate key error with standard DDRMENU - fix case --- DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs | 2 +- DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs b/DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs index 57526f32ff9..8ffefe42272 100644 --- a/DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs +++ b/DNN Platform/Modules/NewDDRMenu/MvcMenuBase.cs @@ -142,7 +142,7 @@ internal virtual void PreRender() var imagePathOption = this.menuSettings.ClientOptions.Find(o => o.Name.Equals("PathImage", StringComparison.InvariantCultureIgnoreCase)); this.RootNode.ApplyContext( - imagePathOption == null ? this.hostPortalSettings.HomeDirectory : imagePathOption.Value); + imagePathOption == null ? this.HostPortalSettings.HomeDirectory : imagePathOption.Value); // this.TemplateDef.PreRender(); } diff --git a/DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn b/DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn index 4604b1d848c..cbe9f4ec062 100644 --- a/DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn +++ b/DNN Platform/Modules/NewDDRMenu/NewDDRMenu.dnn @@ -1,7 +1,7 @@ - DDR Menu + New DDR Menu DotNetNuke Navigation Provider. .NET Foundation and Contributors @@ -27,7 +27,7 @@ - DDR Menu + New DDR Menu 0 From 78bcb42adce0f00e4daf44b1e779da2765738d31 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 15:14:24 +0200 Subject: [PATCH 018/146] client dependecy for mvc (because it not exist in nuget) --- Refs/ClientDependency.Core.Mvc.dll | Bin 0 -> 26112 bytes Refs/ClientDependency.Core.Mvc.pdb | Bin 0 -> 83456 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Refs/ClientDependency.Core.Mvc.dll create mode 100644 Refs/ClientDependency.Core.Mvc.pdb diff --git a/Refs/ClientDependency.Core.Mvc.dll b/Refs/ClientDependency.Core.Mvc.dll new file mode 100644 index 0000000000000000000000000000000000000000..c440f813442f8e6161c8ba81fd8e3e8579e1f364 GIT binary patch literal 26112 zcmeHv3w%`NweQ-G*)y3WLuLXANPyv?9SDT*6a+C40wlZw;US2^B-y~|WOkfAK|&fG zM6Fo0;;U+Fi*0YOw%V(;?P;z44z0AO?XADscfGY9sE?kW-)XPjp3|P*_Tc@mwfF4o zNhTIgdw#!jezyaAecyU~>s#OY*7uy9*|_^I(uhdM^Y+_BUqs5ECV}q^@?b}%JQJbM zmAx?ai^_%p(Wy6Yol9GFC3$X~wgCnRqG_U%j~_-eYx}4V9JQ8LsN~ z7NQM`M&rv~*60i8b@?Yo&p z`TtPrqD;c)8~sF^Ig>}**$KlxCy6RRyX!Df-EiJJ;c?G&kqQA{=aX;9nTK=0v=cmpT~xIZUEx7ENLRJQBr#G=Oh#%=0Z7D|qFGasWZ~OhYZ`K4V|IBK zR`%M{fgSO&Rci(pU_Yz07*?8 zd&5qMq3t>$2Hdimny3fQnhl^Q#z2uP204x3E>~^Afh}Q zZ=;jK7dnN+MC)Q^UKrJ_C6Xz#mU3!ge8Q!uW*IVb?s{cRjLy}d7bXq`b0L;r0tw8>wSzC*!)@4G3UjlR`%9n9F?IsYzhP4t2%3&f2 zmj@HgNGd|Jr%^TqPwCkw*k9IaP^}h##2Ns5Ex-|9wGllKtg$fTpz)RURE92-y5LN1 z8)6J&eM&b^9KJr%Wg0K=!A9Ua?EgqRYtO?@%t-HKCuXODovOs6(*|ERSgqm`HE za(&^G)2%kYAjfS>MR7&NqH)&o_Q* z&o_Q*&pUo<&o_Q*&o_PwL-?KJr}li~r}li~r}kaNPwn}}59&X^_~H78#g9hV0Pw=f zdjW45sIjpN30o1&Q}*S^`R5_*L`KSMf3B=9^T!A~u&(gB>^FkPJGMF{Y)B|X^(Ev> zx3TtJwz2kIw6U~o={DAm(ng+7hx?4jJXIb;|?c^5`?&i0FgZ zoUtCJik|E!da!BWjY-tDT|HR%Sx?N>Q!Qy=5bHndW3K4SjiPTo^r7yk-6#4Y#vrx^ zViC>`tQZS4(7+&8Vjb?n9Ljn*5xs{-(aY^c^HG1Ls-1Gr2YV54@mHJ)E;zoV3J@R7S=WV*E}Rj>b=dy1PG)*K^P}lx`_Pk7`>3lk!CLe|?DyHeS&l9ydb(~LO&9wM z;SjZtxw`5dT^Lhr5M!Q_n@T8QpF@@qN>Te}SH~QuFelQHw~Q=U7=H-5sQo^-TmuTA z0dt+=j7jlZM;7E!TqxSn?W1*{O zk)(ma#Yo{3=8C@CN72XQ4`U%}-y!-i{@`V?XbGo*!KH!*1~1`M9mKd|y_|^NJ4ewA z0m9&j+8=Q3T+ZbKgO_rG_98A=4^u_YU8CsX@i&?EoN)Eve8YO0obrrG`MXP&A3pxJ zvl90X!x?{5Sob}y?v<=tAH?~HZDUN!e%~2?_mx;oofi#~{c zXPp}yT}sIc;dqrV>mf z%(r}3Q>>rmjM=MPx?JRx2SLOMel`U)s}dDfI`&!zU_rGW)n~heGV35Hm1Vf7F-UW- zYbL7$L9}JoG*ugv3}R5FDuw7Gu3xiK+>WaO%7bAek%sNJ0`zv%~;u@GUCsluqkyX?Jy1Ral zk*)U7_4ON#>`V_`UcbV~Ug4p0>gO8S0S}#AA2+ghc<7k=s4+Waua<5h%3j+v|se28nwcOo&J!cC=Vqpq&Nz7`_WusbOttJkLm{e+EQwKN9 z;^Crl1i&~xzpg?#WD&#H_d(*qIkgvEk9}JBoN*nXeVA!^<|H(f60g8zxK1!EY@@;# zL7z2%jH6GLJH&eEQ)kbVKnmBC*N;_$)_WmiB;)rer0fm{YcDSksxtQHs`Y0L#I@T^ zt5j!qjr~^~#^c6Jb@t2Z00xcj60cUB&96Fu?X9j3R55F+$ls_s`&e}VJ3+9j1!C8w z^HnGmqP-K*ZY*^?KVapBX|x)Gqf zJ`uEzAya_~<|d@LpuO2iZb2e_YP}CRTzgu#BAxT%D{vrYzKKkHS(WXvF*X5|t#j@a zSJJ{hKBf?%so09>nmPahw5N`1$DoJoZq?b+PU%($u#jT~-I+%>o&{h}dRwqCu*M zO~|l{xh&-np;xw0>Hu%7)B)aB@#QbfMK5AA!ScLUQU?|ZI;c++< z-G)_fs1GsRhx(AWMnipg32XWb_Te(PV(-0Z{ zTFGQ}0OQl=uV#n18i_j4!Yt2UYaG^EVXYI`$^wqR;OjO=V7;(5Fv~MxBeU3qO+(}c z`!@JA|9*g3yEW@}q%V%a*h$=hT;fh9uj*)9h4G9ib-oWfw6LMEVL{`9d2lv=6Osm8 zh*+GCQFRh&8bfk=M=qPn9Kg(lGU)LqRnWF~(6cZG!sR$_miitL_d* zXpb>oi_y;VgkjJpAQ_`CS0#)X{UX5dOj!bR`1>pQOnjfvpRRaeOqfnou;$+dSi^k% zoEoF&^n?+iS!Lrv?+)FKRvc0p{++Qq5~I(6YtZe`Z_sC@mQR8bqj=>{bb~$xZx~pe z5|Ie45PKGZ9--Y8tgTgNJ#SW2!2a)qxfR>cQiDDX{bAZCt-c;GM*kf7c_>C-D}P~( zK{c@_0Yl|n_La)FLH~Y5!lzZGUmJz8-At2ws6cY4e&Ed?wnlBX(mY1bkb7T!+b8R!o#m#Ab( zgRX%7Dw+@MyZEN7OL)!Y%nOkz<&I0aa=Kc={bksa!s3u74fU-+pfc7Y*wunnkR{mT zf<@?B!G0|mzF-mz2+ED4KEW`r0K0&$6YLX$O`?9m-VkgG4G0z!EmLVwux*0Ppc@3s z3zndxf_+u6dO9YUE>_Q>n+3y)iuxMpeS+;0tdWij_KaW)=>3AdDA>hxhhTpcY&lkM zZad#hrsy&{A=v$bt)zPd`-x!9be~`sNbPIrgM#r~kfJtvP%uldjr1YXrdH8jk=#rV z(~kVP=$(Nr`DX-v18^R#t2hyuO5duw7qF%B!vTf9qMbzk-zq)eA~Rzxb0t#h$*y~enRZuPJWje2_2!xk8G>9mVYhU9!|#D=!9HtrAAaf04t9BYkXoH7!8iSPcu#9n(PI}mY zaSMIJ!)`H-)0-Z4w{ZuJ$8Ouv`LJ;pHF?-$#=VsGurC-7(gPlL+W07)@~{_;kJCT7 zm}30Qc$Dg9IXV^N4dXFd=V8AwK213f(}GXXNe>$ve3D*tu?Vz$mQRmjSp-_1rr93$ z8{_kIxrb@NzoDBvY;y1!deX(-EZ=E7NB`hqoyI9j;P}9;eX~4coTgq6`-1UJI^|)r zgWsX?JbjDgW5$nYpNBnc{FF|5Sd;Ph^sI*s7-#W%Mxop-#%r{}!(KH0flhkZY2#P) zPabx+@fI~=i_P^_RqQnWnGSkbr|~;_)Wb5yALvaF`+`AA6Am{{IlKX-Jm6uE8I{Tz z4|~{%DRG>C9LXl5Mp^D*1IBnI?_swX6P0g!*o(##C64{GQ|`1eLrHts-9|!rQm_+v zDP~!)Ub!5nDWW%&-&N&kuF@;m*OmVfN-2%Xqb_!&g0UA}$;-nHTBEW8yKs~%yExiG z^OQA$9SyZbbHLgKJFUDB*btnrq;bw5a%Rj0N?x$96YE@{oG#+M;9(uX1w-^>@=`(z}_d={d7rSUvRN=-qwRkW;#w_I7`<1H`ZU8-#K@h&amtxyj7cq@u{mnpaV zc$XFNnv^9tq4Mm_wlo#-Rw^An-pV50D&=Y)Z&eYmS-HW-YcArgRvz;4Ru}PFlrQ^u zEk(RF%8z`!HATF&%CB7BTC`zp5pSJx&c|C<#A{Wm<&1cYxLj)yuT5$2@!E=b>y>sN zZ+#JOgYrioZ$lApqf(0_Kl_^N+gQZgq%`<=n~Hdwm1ZAra}lpyxyr|DFXC-cZt(H8 z6!Eqy5BYdom8S%A*6EHS$qwZ?SMpAJR^wBxf)~~Jta@kJQ2xirYxJcNly8Hg)A0zO zfiD{~Tz)v$pueqTnI6$U0eOv7ohiK{bkQst1?CBRuD$Mjh2H3)CsnN+# z0Y!5uETht7Sf$ZJVzWc}tuOzm$R8H_x5n81c~Y+M_Jy^~B3XW~M;R2nJi_otsboL=yWBf~x)Nhuy*Ly`{N z-#Z0MM)s*wYIyA${neADYR*pu4AVToDq1RVrNCB!TLkVB zm=u^2c&)&Gz;W1RbJ^npTNT#1OW+B>3+N?f_Lz(4tnwJXQ9S_}g&qTJ6u5#OpoK|hV2#+Qia#(WEu+sc1L6V!jvUq-%i%o)5xWrTl2Gu76r ze*@)nky@pWZmmoxGw5*DG9{|8d{p^Rq)C~eJRWIN-V)p1qH}r`y+!dVhHt@!3bM5> zl=@y(O8KtHOjWLo_9$nmw=65LUzsO9nWx-Tb-OZ8&4lk(W~x`j9#Up1WwDPcOVwE5 zWmvMR>c1%~rCn#~)#wu_d#6?f+g^=6i~Ot6(*pme!0!uuS>PK2e=YF00^b%`rgEvV z0w)3fcW9d}7Pd+)Thy-DB6T4s+timRRhiIssh=!s&}OPt;RRY!{YqpB@_#5>1$bH6 z8o;yKdcajxTeO9AvEHRTB4@#u=s?9z?IpU=xKevW&V!FAeDZrxc~07utK`-#jpe}Q zetC(m(^4qQ@z4)T6#5eK$8dAKxa?*6CVj=IqND0}LtjRoPi@QPrfs?0x-G}|G0$qN z#pW&Qo~jqL4fL(hn}FO4Gu7wE{6R}XTNRy9pDt(kp7H=-ODsSSs?%Z<^lj+nMtzU; z@(J~Ok+pg^{WiKx>858wyYw{l?AIStxi|YoPfk3RliI&apAURMpQ#+s9{^k$dj#K6 z==62K5WNIgK}!NLDpQUF{tU1IcY8Ml8}QEGalnamH{f)73b3A@0bD>Y0xqL}1#G5s zfa?WzP&m|pJN+4eUBbOaD7NGWs2B8O^ib%S=zm_|8G&bU<9Ac=EbgIS3z5RL#|1VE z>{6I}OyI`_J}>Z9ftt$P=>nTnF54{meUk5zd|vX$B!5!!AD8?o$v-doGm?K*@}#jI zO=Hb*$xoMjljNHvzfbaAlFv*2nB-4N{^OEACHdzie@61JN*;gD#QJs7FZt<`Z<2ho zmV*;B5?h}|7cv9dgfoBBbbzN?kz$StF1m*>v6nIMD z8G#fOd4Wv=_X*4kJSp&$z%v5z^%d(E*d%bDz`Ved0#6A%Baq5OUSN~JeFF0WPYOIG z@QgsbdBFMwHVND(FfZ_=z*7Rx2&8h67uY25OgVd=Di}6ZGIyWA)#Kie{7p3iYZ*?Z zO_anpCLh6!e~NVFsPYBnJ!*@(TkTY@Rqx&tC613`CGCwtjI*0qDO-GD!=lEApZ@V z2KqigmGF?9;)Ac;B=4Jebh5Olsvnr0R z-sjW$bWoh{f(yjY>1{&u%RQ%$7h18l1ojfe`kvDtl*;^NKdsk^99{##yWa-h-!||a zGrl>-6U7t5Q;i2}7ru4Fcbs@?@r=ha0nY_^CgQ2XGYQXRJX7$*@!-B5?*$r|e++!j z?X+_p)rhS>M*1{erv8ZT)qX=~HHBV>*Isv;xqL;}-o2|+c5gb_-<(d`_M*nUjWmL~ zxFq%BlGH`>N@^%cz1X2H-Md#ZTTR%}WwvAvq%!7;PM12u#Q7yFC{0~ZN_Nr0Qq;vI zsf!kslr2eJxVV(;qLS2w7nhVRNnKo$x^PKJ4JE0IOHvmuEvca-b+J&ln%DNFvZmc; zud&kIW|lh%@lqf++r@P5x_SHNQEN-4ugAv)tnXNs&X|u=7=NPGh=2`9*cEtJk&+a=@e>QZT6a(ZZp%>-_UGjP2sHU?xq7~Zf~o- z+U)E*fJi^kl3~YmQ%lC~%bIQWs=iEj+T3a$K+Dan$AEK3aF#dsWwWTM*~;*rv9Gvh z@7`6(u4|xlP0CDnlRKiE8Z&k-ndvfVt(j{rROXRagHiZ+meTpc2_pln-jEkxRZstUrLmAzeI-kVpFomBpclznND%IWVvhB^rh2+ zz_W;PGb>2Z?X^UESq|EEw{Ek#M*OyGUCvykev(}*$wE4KJ z`cmocjmZpL>L)jM?-zYONsKt@682)jo6-*H_l$U#TJ2`5rx&7j3f;mc>mmbIA>;TOiDwt1vMPXJIripW@~1@mF9pdExvD;%EGO44lS4O~^R3@2D9T_EZF&ndQ z3iJ84gNU(i%$Ws(Yej?WTjsQ`%k`w4Hxp@_!5@s zY;}ZrK_g85JQr}F%Xk)qOGu_Sna!n~*@6xY%^b8b+3N_(k}KJsBrgq;O?KLnVh3mS z&4x$|nQ)>vQ7bmA z=EK=P6lAuV``t|h4b7NCW|Pwyn|pcnr%jmJQ2?_Y7Q&XpU8Y0Ef`)ME-rS2l3d;>~ z4q_G)>(*wizFs;Y(487Lq%zl1JMUNzO3%0}M3$E>bAyG6Aj@rbbKZRF8W1Phgv&RPOI@axOpRnKMDDh#kgW!!82RgQ}5MU2- zbQe>daqgFE=wj)HE=e|8nG~Z#y)KdL49hl~cpl)b&B}BtX+z4{Z}Jr3P?+!J+}K^6 zN*>5qb}rRrd!x_U>@~D!7i-O9^c#i@^oSoewT`yC-V=t(RRDv-9M*5 zDA?!qITPVH_!$|@WH>WA#W;w(MZ=c4$A=Ez4B?cKLr~bn4R@U4sNwm@#w3oD19r(` z5ZC3c+BPO>*W_XJp(yqVdv^C1V%5jw#yfjP+edXKTpz)kLkj06$E@aqi1bmWWH!w| zGk8-DHyGoCZS{>A_Omm~Vh-?y3uEnOmNy>mG{;9OaZgt=N0^bYAtA?LYB`+BQ3srw zHaBP8F^t)}m#0cDCcbn>zLS^zx-+MEyT~Dj@evSRjgywQs=!QVZ|+1h?Zw)M#fCOw z7qH314|=(-;z2D{__m{160j?fT)f?#$ZoD_(-SZ@V5Y414QO{<|;63$z++tbCnMj*~r;y7~ zj#eY>l|0L5P`baUd>l7_Nhxj8VU)5_CXO@%n8ed1T5~9S7#!NW95-ci@NW<9-`gN@ z2#*b^EXwpk;u5*X?1B8HxKB&q&GF^nam|jsZ1W1dCO8Y6nUL6nUuBw$f2?x_DO>#d zo1xkMb2Tm~XuJY%*Eiyw`eF+QXdY){8|_TWKUUGVu_I*9sv zU=87&K&Kz(7RrnpqVz(7Z;&%Y^OEQb##4|>qI61naW49B4(c7*Ys!=%J*D{mnh!g+ zOc}inu@&LE{v$87Hyp2D|Ebp(BtHK155GfN9KSslSCmiyAUck-5eBNkc>Zg8P>F5S z<1`6Wid|V54=AxMk%&4jwo8eOi5osFb|nkXV7Z=9TvKAnm{}JLYHDn3Y^)Z9%5kwi zCDzY1#d-qx1D*W81R$KhgX@are=o?tp?0+{5LcDDx@ep!N4?TBHnh9*siz(NG~%hRWD_zLwS^k-EBA zFfcBfzZpWu<^LU#NXRiYe>^BA=Z}}gNo1-iRFFkGtBL8{sQkm@;TbnusHrMa1(UeB z)$C`LJ(D!zYV6ub1mAMSu7z3QpdQOV7*S*S6KwU-3BkA;iRJH$pm{1cEp~KLkaV;Q zR>gHC6pBO#1MqYH1SGlQI%rft#J5#S1gS`IrVJKA++}e&5s41gu?W-aL_~67bfqIK zT$XmYl9LJ_ah-9r46muO2Pab?3=571gKDf12El3h$GAVxKjWFWkgY_z#7&pGX_K3- zcGGoky1`A`-E^CqUhbxQ+;pFtcDw08H%+@~ubbxF^st-07ikFg*A~C|FLyeS?HVHivJ-P@T8)7;JkP1qG1abiWP{QzqGDL1{ ztQt`xMiBj2ra}ZVA@2Aes;UgM*wIUQ?3j8G934(=HQ3D8Rk%EV7aSdC-~Lia9Gjpa zd|?P=$~;bWm7-2|dq#+pNtGUYorAvxUG+MGQLV<`t*K>fv=$m4)MELkApCg+as4GV zQs*XNLNp)U0zzFm_q9uwc!zK`Rrm`DnWZB^SCB`C0_zjUjER0E3 zQv4;Nf{8KdBwL)MCqz73_Ef1A2+ZgpcOu4WWn2RiaWQXIYng~AB=ih^F`Lqjt7CfKc@h+H>B zj@0>!*5RARg8ER%Cl>OFfgwTk2qY`mO3q9|D>DmDkv0jT(qnPK2nBt$ zMyfocQIO|7XFR?j)3O=s;o07Fe0mlmFO0#d#PXcQ)Ls_R6Ido0n7V?l4=IRw{!HdS zw%n%H?93*6H~B7YaG!@uVg+(RmZx#}W6F+${2R#l@thU6acgO1bMciO?G2mETzun>X8i7hLNm z?2mDoxW|b@7A`CL6n+a@1+@;m#}DJ#O|-RRbq6i@{my}>W^Q`?sT;o+9Q| zkDcr~nB3Ey$?!A0*?aI4%6o!uY&a3yxoxmJ4e$hyo!uMwlz1?W>f9B7d&S^(X z3?D%fFKun%_nsZ@UNmrZI=vBB4bE4NrYSe6%zgVp=)n7jfBpRb;{pErZ~1Mrd~ut3 z$|IZvX#}0+*5SDvoZYfUGKKT}XIML^18>djpcdMSTq}NQeG~9jz%|aVuH$d~|MxlP zJsGYcjQ3@n^fFe053A&P!2ekdM1j^M&X8+xPD|s2*NQXTew^6&Geb&k12+k(EoZ#K zc}@iAN&OjKSz%KSQhfe9P(suFBGZU=JO38q1PdR~CuuFp@_Dxh|C~DmoJDCR^zccS zPyI}_-7_SggDun(2mK&8fBLq8OR)8<{W>Xojg+PyuoK+ELQkz#alsGUR*D;g#YHk zIU}>&pSnM3fL(kK!=DEE6@r4!9nhKee8v2EkXVXmo_L#o8h=DyWW+}?t}82j_v-6s9ojyf#(q7OZj z^Lg}qd^*&h^QAwFbq(*;;eC3EjP#Xgb@4sVJC$p2*Cf{+|n(!cEzr3p-Xpd*Y<i_#a=XdU$ncSHqkg0v- zz%ReK_nhDBJbveK&pmg3Q+=p4+`1y-FP~n1#TEYfi>v&FnORwb2VPXM*g;5B|D7lB zv*s+`aGYK0Z{MH4gd8~VVPE)b&VeG2O8miJbB6qt*?!IsvVE!!3_(R8Ak1<;{4gE( z!w<#btGj)F{t|P*tNds8mH%I2QG437Ju}-ka=_~=e2mR~?}j^`n|$pv&)xmwv+sOr zt|4HYiZ?fak6xD>f6I#P`}yaR177W)U7+p%c};ub@t=#0eR?L&fxhxjoHu_BF?Wn_ zXsvHw6>N!w$IrN9eEE!;nsA`LF;G*}(9)v*ls7jawj$UTY-tF#)UT_lnKFLH_{K=2 zEj(lT^iW_;=E|l>V|!hDI2fvLZHXXpW_|0b>7eJj!kR!RI6a=n^sM~+qF`2WATK*V zzb;r`Ur>}?RFGR=T$r8L5NIf_%gZY+3^wHCOrJ9T&O84a%HgB3@l@S)SAFm7e;vAQ zv2$R-6KT6&uDs^q6TkZL&}~=g>vJBx_hUm>{OizfPjQ@6{pWo9yjLb~-aqt$vQK_$ z&AFRC|I7%#DE^0aQ@?*p(eGdFH{-yP=U;sP-o;-Z@$~*Lq%UgxMR4F(7X19SkIKG2 zxyf^&x5_{C?8qN}_n)1s%j>q?Jtyl^pP2fOTm1j?|1SQ+4R1Zy_=|Td?oF;mU+hPf z1HD!L8*kftBK>&Tw{H9X|GqYG({HX{b;p+9ZTHRi%G3Y!`!^na{G1aXm3@73ljlHh zmH!WhZ+jrE=ogPJo;*48zUqBXz4yKEht|Jz>7gYXrayn-Wwn2kT#LTgk17XxtNf2{ zIMjUe9d$Q6@R{*DK6y#*>y=ApEPU@o{>;0^{jz5Mi&cj|D*O86CeMN1D*xTbZ*KYB z*9yNg;eQ5v_3MB0=H88C5AWXj=IgJNeeT2`UY(i#pUJi8i~Xo_pts7uzH0H{o8G%& z!|lK7cVqBA-|G{f``LTzpGy12FV_7iaP)@z1{=@4FFKv44&cDC#->%&A zyBqI(I6VEofAGZL*Y1C1?)M(})r!KqJ}UeAd^81dvcztNv=g->_?RYy;c6-OP})8J-<0t|N6IweDKKr2k!lP zzaWMRD_Qz^>J@!%A*C#i54)j*}?_aa$jsLrA;_!zan>p|gJAQf3S;URyMT+GwZ{SQy<5$UOh`}h(1ds z!}yz-tH7|nVMT}xSA${DW!O6kQnlXEjGRtir|2`)MSQjz3Wh4z;_+#?skJ41W;#Z5 zI3%2dF`Ro-=ib)z%<{B4vZ}c{*xc58dD#e#jf!J$k}Gt4)|Lo*Cn3{PLfsM8Dg zRs)W@y>M^Uy4Jc|YAS+t?JK9vUs|4DlX-3BlB!T(Rd7vf=+>!AgCV+|S*!Ch^D|4s z;oz#e=5?i!NT{iRIN08Y)(j+WIM$#m7*N+2E0{#mfcssUg&%nDn zPxjM$H5cLjm<-2x0rv-CGv5b*>kxMz(i{bpp}$`P_zLdVaI>FBC;;DW)-KN-;P-2Q z3D=n)9yk+nM|s@uyY;7)0~h~CmahTzVb0&3`p0Q$|L(JjyIYD5LMl!jNVjq(2a&qY zuzr&Fv7h^&XvA;UCDs?#ry#Hitdl0~f=%h`nO4LS*K0f*LT-0Vp_^2U&h&K z*&a;VAzyvua;9aSWLn1AY1vLp+MC`#uaIfkj+mBlc3QSElXk%`7B(|2+aJ?1&Q8nr zWzt@D^Vls+%XY`KjI-0SO`5bne)T(_WLmaWre&O+mTl0aZ65UWJ50;A$+V2K)3WWG zw9h?NyNPM(6PT89c3S!ZlXlFt$B!^AeFM`n&Q43eW6~absQoic`#6AU8E2=(WncIc z%>fjvQ|3$=O7&hA9R~^~y2WuaMk1E&%#ra|MQ}x+y*Z*Le0s9yI3t|1hLYE20O<|Q z`Obu)*Dqcdjs#a_&TDG94N;DL?}lIP|gW?oQ6 zj^q~vsxU1o0u`Fg{3CtL0RYpRH1wN|xc36!3f#X@Xvd5Hfe6?C$ZS<@ z)<-gBFZG$xl?`BG+W=pbrhE~cfbDy9Ir`kTYWtqSX!r0xC_|?M{zv(s^PEQ2){XC> z|I+#EbMxKifwJgeeYpUbM!}DR*BvoD=%;kv_*6dOQu&c){y?}8G{`DvBrx^C97E|t zzXqS>(65aHraxnT64SrY&r#1|&T{cr^A$#v3XC&8>v6dU8BPSiK5Z9ZAAoJ-IN&cO zu>I3++R8C61ILmK9IN()zw8`f`Ogr4#`?{GHsl7hHB}qf=A80SFc1kY3}8hi(5&2= zJMLrqG;~axXpC3Z9bX0dVF2pJKz_z&jlhNNgC{A)qOzGD?kDop$Bt3=gfJHxK$+@}RFJ4?W)F zIB$*O!S)I{&_D;(U*p0LC;}`o5fYyaF^^|U`EM4wB8#9N`LGZIRMgTyb5jFqKzVxz z&lMuoftH5mV92cllucTbeqgk}$9OW@_j`>E?E2z0PgsIwP^vYad^$aXDsK%1Gv~K9 zv^R6!nIuhlYs-qJl}KZcMKA~PtgmJcfN`?h2RDk%tb<+70od+yUpu4w2O)U$;Eq(IPP`%8c?im7{CLtvmi%_#TnfWGkO zmjibDKOpLlu*~=(+W#Y{SgLqu$^w9%JkPFMzC3$*R&+kF5p<+A>APyvy*2lzPo3b( z^(h^2sdVPJh5`PzqtIF4j`7UAl=b%+828FwY5isSn>ka1(D5zWNYpw&%yjEId1x{r``DfTUy zNZH;Tz!UhkP%x|>!7HD9I^z`%o8jYqgMiaI9#eb@q>nu+c}(AH0{nXh#~3Q=>R@C} zOE?l}sb^Q#qq!Z+3UbvtQ`!)8Ea;=mTy-z{B8KG}slU)KW1f+_B(zQsm2`VPhWmUj z!1N{!=c>bSuNH7V?%yu7>?c`|AlmIGn_C0OY2B|;kE-sB`@oNRCU&H;?WPS$zxJ$a z(>nI}I(~1)VP?FNC4CV4H51S$W_;P$gpq1E$Q=RJGh`HgPsV{p7wTo?oAL59BA;V4 z6EJ4Wc+JSy^M6Zt@5!FT3`d~fGXdkojCWEf+tlF(nJ0eE2nNO&Bx3~X1;jg!TJXU* zaY1{etv#Z~hf|^NXwZ?yq+@+*!2OZ9&oS9$LO%laiG1Lj-TJhabV$D*>4zab>t&{- zAA|Jx8I#`Zd%NoY<{<(9`HWc#oVtChgExJoh5Xu{XA-V= zp^I}3@C@L2z$L)f0)w|oKVLBJVHE9jEC5~v3~&|$*8pD+tnHbCD>KF(1G~;gx#XyO z!%;G$p!q1Qr;ltXq?e~1BYdWxQ&y?TwkjXq~07g4-Rsc^0 zM!OSTIPTx*@*|!0V?M~&;Vd8w_f%*TezyT%h&v&dPnpYuI%$98bN!U9(|X~713pxF z+~qiTj|a`&F0TpDX`IV%AL6v{@F_onOT;_Q9{xahK=^G3PQx|vxxg&PA#%<7^&sxk zriXwh0`CAW1O9^JY+4Px8`o=qNe^8V{r$j?<64*7FwCP^L)d;;4t)M`C153Bn7SU# z85aW6zXLo0pxd}l&F2`$@}NEps0rXb+Z~_snKDBO;5dH24`AP$1*itp0@ea{0`>u3 z1)Ky7LLp56Q~-EhYXp!iusqBd;eAQSwrB#zG8vn2q13_HWS;cv^u?sz3t)PaCfT^o zjQ7pF8mhtd>ChCu7OUi(r)u9H6K9Qw=;yjMmH@9|0P4tqtWooB)&t*g-!%Fq@-ns` zRt9QYn4SUcB;yfaFWY&O>m{_^q&ZW&X7^x>#B$*}LLz%uFBlka0{VfBXK~?W59dG2 zK*RJV&7XojY&-NLx_|UH#rgJnEg=F9W3f z!&WqgCjne@c@=OHkZvkA6P-aI8#AsuB7Er2P53zKOf8%!^>-ErBh@(f35#eEF2@-k zuoF?FG3mPF1Cq?`+;u#*&(qlxfb5BB+Z-D(u)i~*26nA6c9jQOD%S?br;+-5GvvQu1F!NpVK(&*!DqqJ>(>7e}+psik!$Cd&HTf}`f)U_1-GDhn#+dV@ zZm$eRmdm=fso&Ja(6cXPVB0jI7W5Mheb}4c?5%lI=!ZyMB#!{-ry2S=;qum1dhMaE zIjB@!lwo|e_5(iU6DY&>g(KkeCutT3w|6;`xRe4D_I@GL$^EcvFxRy(*$7pG1O1-%h9nceWPv(2Ca3DtUt9?%`oxmO$TE0i-qQ&DeXg z)wi0y(#(E_dMjBhHYw$#>o<8(nr(+biMVdI*d!iMAxQZZwLL< z`M~V+;YXBT;v9qh73Ub20pmA($;XxN8H^kltoA|9a(xl|FJ13^ZhfS?$pSE31bS9i zV$KbS>9a3@Kf4s3mb%ja>v|F06V(s*ssRjkA4nfM9z2+j6M?4!Lr>_)K5-ggrNYKn zl7}u!pF4rS-wYM2*beZ|22a5Ig>x?t^ayB8j4@0Nw*&ne{`VQwHZ0bOkm|1FqR_2|XDLw?KFJ zYqh4~Tb;3Ct8W9QFuMI808g!_Pw9+HjEM9eKmKm^>3X`#=WLpe>vmkT%@UUav)-1= zHO4T`YTU=Vu(JmE7T~qOw@Q3FFw@^9xC8ih#D50(4q&~0cmXnLBx-gE?$Qs8#WmN1 zIhH%$@v~J+7>f2r8dLZAo(Kcm!7VW3&Q|q-9W7mN{s5%hxtOK##Ysj^@1S@ye7?g#tI-W1bmW3Tpad@-5chw{HP#qD47_wEDENn;l~&JxdX>N3|L;WkZ%lFf*ZjT501vA6 zzt*$-ccJVL_LYCn3*Dk%`Nz-y?D7A4l>delj{h`o@3H?^F#en4-0E5O$58J5xp34M z&L{`$_W!PMve$pGMv-w-H~hbOCa@PgNN;G&8W7gDJ=cJ$k(TR5lK{I!j#&e;X&MmU z8*4zDd>b~I^@juCsrB?;1M9 zm}@|c_gVwO{#CUGG#$7%*MR(J7uDTt8+QBOMq?3%Xr%sfy{4g?>wkRjjI<`b{XD3v z_rL6CLGjOv%=-ZL`y`2;A=zonezW*{*Uypt|K9JB@!5N*wK{@;=U_ALKhDEotb z<$rpGZc(xPlkNZ6j`AO!;_;v6?>z?$VEi}NS>;&=j-mYf53-Eu``P0h@G5_M{r6s! z|A(F0fA5a}HGl7Q&bL(`;9dTHlznwy`S-ZYZ33_Ix5xhvq5QK_-2OFx?=fJDYX1?> z_Fs##-`rRJc5(Ljw^#Z5EiOAy{&Q1Y{+hq{7!dED!2fUbEdO07`-6SuZy7;bG3)d$r( z98g-lFHSm>);#ma;~C5xb!ZE$RPTM5ch~GVyilSTULR^!dC-$HmuLsx@i>e5gm>49 z(mJ+}puVH^3T2!9JbZ7J z!7fw0-<#s*zOY9_E_;^$o_3- z*}W!nnC$nrW#X;CjB`*=Q@`=3#o}GajFJ?}Y~#aq z1io`=0^T9b;Cq#+%G)+wEnTJy-bDzkx>(B(qw$GRjxtF9^?mQWs+V*BaZgGm`zASQWd$S>T zNs8spl*xcyuW}%_EyZ&8iCn*~Ug~cyKYPpn+@g1Tb$R=qkY za_#b+3AsVv$bCJh^MTQ)NfKubE4 z*4TofXv%HLg)PhD+RJDel-YHx61J3KJt$e*UjjQ014w7m8e3pU%5BN>D3{}6i{19C zV9T9d*y0D>4gl#)T4M`*Ps(k{_pqfwIz5?^K{I@^1h(u=V#}HK_ohF{k`4%CM>kYM z_KQhmvp;Br&c^_xGigmeUhqfA$3o=eytw-PzSx5P+YNJI%l~#^OCIQ+0g%q5HMU^* zm-0GM?>5Ae~8TY{Aej<+c=i*s|TPO|Z)aelg?r zE^IjrJ1Rg+I+NDef?-?AZJFU=OYJyqi``!4!Iu49*s=q53;`|aOj={hg?|KFuJo{F z>UeF7Jrpw`G=xEidE22!q{EFMuuAc45o=upKU{)O`*VawqzY}pPw z27&e(5Sg^b77RsFZc7Pli60A2x=dSO*Re&g<=Q)`6PgJCauXwbm1w_#|q@*{IuA$f&P=VE%umWDQx+t zE^H}59_$B@&ZITA`2PsDRC?I*s@PJcO;UevfGq>CSdpx)4g%dq0O?FxV+*?Pl$T4D zhb_CNXd~=$Sq57cbYaU8*s&0_q%&!aE$H@AZp+mkwlrU%ZL!=+GN z(wVfz7Ib?lx24*$#XR3Gnx<{psge5oG1&5Qye%>iWV>&J4acTpEe)wnI_{?=F}gh0 zjz;SnO|+AB){YOa3rB*h7PYoUxRICV!|OA*?KCUY`QGybZEe8@ol37u?tu2YW2X+A8nw|?$GDi&vnch{N$nc^+w-;vU#BU zS3J;lc3gn8aQ(ug$mOvO_x1i;pS#}^(+c%HP$1476c1Fp4(59%FdseO8MOM=CeN}$ z-$A`Q{J`13rNDW>@O|q0H6I5qz_r$&XYelvu!PKaXCB7&dO-BMGPF(av-PVZ$NHH3(fe4v^QTgH zz;~WF7l_nQ~v1vs^0m7F`}|< zF)-JwFgB6=sf)=Uz30?Bf1sb@0l%;E2R>7DY;f}jb~<|BX!ILNY||`12DEp@V>vME zU@h>4zyX)nf*8BVPwxedzSm>(s~3I^z-$*mV9aIJcS&{GE$+lm@ALHL*C_m&gx@W~ z?^a;Vudx$9y@%7sWn&d={Z->noj&mz<}SnWqo0=`Gr8odL9Pm zBd`cC=7>%^u-0!wCw_YGrO$n4W%FAj{MG_9-_`+B?(M+3{oLiG<1{wYhFBhYU!}Lb z>p@5T?gXa2p8%%4cL8htLY?%}dnUc@-5~t#2By9D0Mp)$z*@gHCtg3jAJSXD&7h-x zTY#zGeZbW3Q@~okwVm|SdmX*?`?T=;3^4WkEHL$h9~FL|?8Hy+WArYM&k4T=fLR{f zfmt38y8Q0%#82;0jK23|m+$9=-wt5v^)T=l;75RU{(aVo&p*BY(A(afpkw|$3QT(+ z118@u0_*&{yOVx;Z=tunyM^E5z_j-XVA}g6u-31`iPul>8}#-se+xS5_hn%EmuGw+aEkD{PqIVzdQ#_J^#+-*V(`5J$~N&_6fhQ3%|b?e&2BU1v=@c z_wISu=jVmrH-TB7zXin!@Cw_Xbop<^E zqwsqfnECcyV3zOqfOYwhoc@1gUy_aNw4zK4Km?+<`!?>_rG(# zmtO+w{M+or=bzqF=56nPf{yuj9GLc=04Cr60@nF=M<@OCUNLWbPYS89s1%#D4Ef@6Gb&;oyCC=9AB-d0^fl`7=94553PSnm4u{8N%ai@L-UdGi=6Jp96AyvrjuMi0Hm$U9Fb2#<;2!92a(9?`!e7&qLz%WBx1!J=+M+=qd(Y7Q^rM7=C(h ziO+o|5zlV|=!2l22)qKg6nJF}zx!hN>HQr(_qjkkKg@NVTR=Yz_*S2@Z4U7I7=A-y z_~|_s$?e6r-j%)lCKc_yFNWVeG5qvChven)Am~{h4*|10J|DyH6D~jcB)u=e=bVS% z40b!-1v>hZ-N39jj{|Fed!@^RbwuwO@ToO0TsrZ2#yav0XsHv=UC#&pDlm}ZGZ<&E z7(YpV`nkPNtq(FyJU_PO=Rwc9@=ajc@vRtskr;mZ*}2bs{vOZoQqX@N^b>%2E@TDp zp%{J}WBBRk*U9z6H#e1jJZFsh{WON(oiY6MGvws?cMSB*zc+xHfBzA~Z*2@e{Tw&B z4|ogo^Z`5rg+Ac7G5lI%_~~b<$?F{6DOdXOn_8@MAH?vBEf4)XGkKdBh_e~!1Ne;| zwuwQ&KuWK{&K)uO>1T?`eZVNt(+BXIAM^ni$ME}93_tx`FS!pG1A6*^v1!@|WX14{ z^#S_XT5@}5f}ZyBOk&zQJBD9u{nO94l9$IE(6heqj7yfsH8K3|a_wc^)X$^5eZUgX z(Fa@)OdqfmSo?sPE)TX@{j4Z?9j*l}b>g{8tiyG{K#I>`tb*7+KtHcZ-o{peo<4x* zxX_N)7=F=jbW=b5%p|!FxC8X`0qcS31MZCBcW;b-`ng4N{qT)?rQa4{>WA~CJAlOA zXc+bp2V72#?#7TDCW!RXlsqcgp!}1*ibj0M-Jw0iFTy-KJxJ zQ-EPZv9=DV2Gj!91GWSBKG0#naRAJAMg#Hy3jvLQjewni{eV{hCjsdfIF27s1Xu!S z25biG0=xit74SBoKRW9PfSG`0fHuHZz>|OjfFpo+0fSJXCILzSH2}Wjunn*W@G{^i z;1pm8CTvpy6@Xg6TEGs#Ucf7W{D30BGC&(( z8(G+_E~{ryoN)6s{(>Co?N!8LI|v>D=3 z!FLMIM!V#FAKD%9AxS?NZHL$E&^Cz8w`0GJddutkQQwFMpuQ0MP}am-P@crIpaby_ z`)Pa=(*OpXqwyTU=%3X6Vk3W`zAhE{UlIAwi@uxCzR2gdqQ`edk5bX2SoC;F^tes* zz&KseRc2^BV1UN>F0i`4MbZrr`ztXxr<^%*Jyi6#UgSQEw#4{$0P$|YCvd^*=f&<{ z3LYi;1mt>-*f~zpZ;@+^=`>%FKS1ukCiYi~9+yZyY!Q8Kl6?4)=yeEflzLnza_6Ak z@Orn%pDg+w!b3#fKOxtHMeeVqJbotSFbDpR>Bfn?Ps1gjI5Z%DfgNIvWqJWkq6K=2!a#~FVkc(-7T-BteGDETr?}|JuZx|( z7W}aAnrs?m>p#AOF>&(GPU|0UAEq#!19h?QLUsp4s9Vtp zKe9*NWCT=Ha~wwd0YgC10bBw|Lju~sZyToj5$|_0NZSGEI&^eS!b~8TEIrYHo#869>4*> zVF35jMAD%*U@Kq;AP@Ds1TYV<4A1~*1FQ#Z25blH0z3oQ4|o~y3g9T3)lwO3D^VJ2jF>FM*wdE z-Uhr6;CH-+0sMfefQ5h>KqDXm=m2a5>;OCo*b8_8Fb{t8IN)7CU+8%bT;@zfUb z0KA0I)*Pr0Rs>fB+M6Sb0=Kt@DqB`IweYOZ$yU0of-HoB0M~Ui{Xy_>>Emd0oY2&9 zIV37G&B?5fw1#5%r#U)pR$esiE8riWR^OAf`B7c+-~-ZN3lHR2+7w(v`;7iNPG|~v z9zawVtWJl5fe7}Ih9XUYW<_e~&~zQ2(JMPDYrnd$>EdPSIFXgZeYR0qXFE#+%}ouE zR^A>81zRH30iMtpvUR1*vqfe>F27kBr62F$V@#^BbY{=4TfRJdd6sJ@X~zp~@rr0Z z@_xFN=R+tkK`?6r38MRM|Apj$t`kyEtv1bmH{H297@5-&js#ljgRs}M2OTFgXwxI4 zZn7Uvcb0_K=_cW(*Y_oTeY7s`LtmTjpv8rQ)sa=r3+a9u)!y^4ORLieZ9}xp=AnO0 zmv(Lux^y~T=%Bo5>wCMP)A2%wwqxj~BBPieEx{1l?Sl45TYH36z~qUJ*L2xg(L8BH zpUpO>9G#V3-bFc@0V z8d@d2(<*drVem3-M5hs2s}7Gu{`Yq(n!;_ZVOA8AUf-AWRvqq0kY3-H^r*uopK0g0 z>@#Ce+hMnf(um5_=`?*IY_sXl>n8nq((e{RUz`5ID82iZr%}MzOZgWHeNi0!kSP6J zc|qKeyZJ@>Ax3^6R{qE+eNy=&jr=%ze>ds(B6R(hoQCp(asG2mH|fV1`Em4P zyGcLR(8GV4u^a1eKa3SuHsRez>jNgX-|Z)K*(f(dw+($-GCCcv`-^OAj^?0$BAw}O zV!yAT=h8Zj&?2u*KgxBeu4r`{p+&!KXqRC;&=svtBeYik{b~#?s)(hu$tRsgXwiQg zx%-g8QMulIDjeh9B=&R#}4*iiCn{wSODswJ}C~hCB zyO+%DsYZ4kACp7TyMh*r~3FYWGp~1M-&_t5Z z=r~Q213zqNjwhkfaYAFQkS?^PzE(Q!h9Hf4Ozut7Er+LRo!ZtAv<7do^j zBWqhcomIDWoY0^h8Jhlho)^^*vMd@MCp1PSMP933Y#RvX$QH1?s%re5nfp|RQ!_r|e&yl8Zs z(4Y;Odc7kFjgAu_LlD2Me1#&~ZXzwV$;~ zXmp&=SncQSBs4lsXsq_rI3lrbI!)T+xR)^6=FG4>+8671O_zr@ zWaxONY%)3>FLY=}hK}dACZp5wLWj0w=y+CYGCCbEbZAe8j{6pq(dl@hLz^;mJOeZt zosJhev@1jBADx^|#|s_WmZ3Y@1)Yu;I;)T3xs=KD)bT=xHfH4U49H}3I$r3k@gC1x zOh%{Ug$`}a$m7|D$>?;v(4oB(B=#s&+LoJL+f_WanWOc9WQifcZQDV z<0Yfh@j{2TXXxJVf=I$qP|TkVf$tR{zuJk(>2$o%S?!N! zhb5Dzj)A2%QwLhM*l}w(F7dor`@jRtoFyVqmt3-c%ie}AJ1t@ zMyKP2&T4-=6Db*;ju$$s{qYQM$>(JJIi;f3QG%$a&lx0 z;c#97p7|A&mRA+#=XTGRWU4)l)I25n6hfcU?Bd+gf?{;hDkRUJ?9}R01byBs>-t5 z;_UqH)#r(+)rbBtH@~tfFQ=+|?fvr9>O+5624|R?mEXPcn!@v`R7getRgepP3X5_I zD|2OL(N%k&mfHNGe=f++$*IWA>t6qrm0Er1pGzwXE3>jHyVt+xr&b^O!^+&Ug3|2D z?)9gIsnv)6FgvF*D?68uwNyy%e~MD85B*_9aannJmOH^nuFqvD)u)jDFgGW=xH!AK zd;P_v)apZjSXG>tpO;h6J^wS>d0EP}XZ@Gw+)q!yvTL)Q?4q*Vit=vGDMqQisZ_%c zz>-Exm{}2MS=rpwva+JJzFloHosyH2o12$g*Dx(mlwUk8FFTMstvIVbYg%!`ia>T& z_KNI4asF(lu&S)0G^c_qI@7+Hi6fPCz~h!qfpNT_D2{oF$FeFIKD^-XDgc(yREUpT zhP$$45;Hepx=n!Lib}sPC_h!YN0P)ni#VeTmP6b?o-YD!kUMrhGHwL^m^u+3HxF^k z6VQDbahVC?UPGK-BaAIK+;-llBKjY=`jBPw( z^wg8*WhaW`+1I9C$MY<~jnN5s@=R);L&OkI$MdBV@#I<2iFop?fJAhMlhE-z=0v*i zY-PXFva_B%CpnQWJo7ja&sxMKDhIx=pD3?+u5h9_o)MhL_JfE^r01K6OQa{y;Z4Mo zXX+;6$@6g&(ed5VM09P4OQZ|WpiNW`JWnj^|O% zPC&=AC94v|@tnv+blm@!NFL8`OqAC=i!qUXJXbMMzVNJ+M0w5g4zEdI1J5o@#FOU` zCenpx3MR^Ho)wtLcAoo}D9?C?U!uI`d3=fJc-}}Nc|0d?X9C-6@r?39iGxdY1Kax= zd49>8@o`Te&iL~9xEBytC!FHr#vsGc1XYNS;~7C{5}t9>5I2NMA;iOvOa=$HX-t4s2CG6LtgXB8W3%H^Z9=lM!YTvFJ=4W+dY_BF^aI=S>NWd&G(xXCi>p@DFFFBlz$fRj)SeMcV$DQ!to& z#607Y>C!NZO-BpQfCuT1(-H<^Ml(pAet90sjHhE>0K11nmkW{4qhRO77$02%TgEzm zj55Z9$C>{-i)TeUi=ol|(6tnB59Sd64a~`K4t_K6UMhhy9tB(onazOm88zWRePf`e zp{0cozb~XF9ICI0zsY?fHQs9u_2DyMX>UErJR?r6b+^=11nb&YPMg29JijLM+R7zW zp}?x(n%2;*QF}9JUKnb{){00|FkE9; z&uLlF+HIzb`B^n+*CQg&0cZ{+E=5}y1zZ>Tskew{bK^ftffK8Q2p>~38-Gd3PO zL(Yt$=VNoU8-x$u`T)n6Yd;I;6%BNZI*d@xQvZ&|_x<{#E&MVHB-Bz*X1ftirar4VHVW%0~W(Vt}}f0vgmpPE}dHBAkR;H62BXJ2$PV0J!p%N zk^FrGSu+pUgMfMV-wU|E5JkxS<%~OkG%o`T=5GVM$NjGa6Hgrf+rDRLiBDGH+(DGq zRQ#?*4T5Or3jnSwaUPNZ_#|L6U<=?rz^4FP0iOnZ2EcjlHUKNm=Kv1?wgVmnJOubW zUsmP;8MT@0B4(*0WJqH-zNj609e6k|1>}*U^*ZRkPXNIl`2aSi zLICSTF<=IOYm74itTSB4y9!VOC!1aKofExhI05<|Y2KYFj25=MLX25blEg%4>1JnZ=071YCz)C Date: Thu, 3 Apr 2025 15:56:07 +0200 Subject: [PATCH 019/146] restrore use of clientdependency.core.mvc --- .../DotNetNuke.Web.Client.csproj | 3 +++ .../DotNetNuke.Web.Client/MvcClientResourceManager.cs | 11 +++++------ .../DotNetNuke.Web.MvcPipeline.csproj | 3 +++ .../Skins/SkinHelpers.DnnCssInclude.cs | 3 ++- .../Skins/SkinHelpers.DnnJsInclude.cs | 4 +++- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj index 4b244bb11e0..d3587e1eb25 100644 --- a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj +++ b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj @@ -53,6 +53,9 @@ ..\..\packages\Dnn.ClientDependency.1.9.10\lib\net45\ClientDependency.Core.dll + + ..\..\Refs\ClientDependency.Core.Mvc.dll + False ..\DotNetNuke.Instrumentation\bin\DotNetNuke.Instrumentation.dll diff --git a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs index 32f828765df..f6eb19ac622 100644 --- a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs +++ b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs @@ -17,6 +17,7 @@ namespace DotNetNuke.Web.Client.ClientResourceManagement using ClientDependency.Core; using ClientDependency.Core.CompositeFiles.Providers; using ClientDependency.Core.Config; + using ClientDependency.Core.Mvc; using DotNetNuke.Instrumentation; using DotNetNuke.Internal.SourceGenerators; @@ -316,9 +317,8 @@ public static void RegisterScript( // include.HtmlAttributes["defer"] = "defer"; include.HtmlAttributes["nonce"] = HttpContext.Current.Items["CSP-NONCE"].ToString(); - // TODO: fix this as GetLoader is missing - // var loader = page.GetLoader(); - // loader.RegisterDependency(include, include.HtmlAttributes); + var loader = page.GetLoader(); + loader.RegisterDependency(include, include.HtmlAttributes); // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); } @@ -464,9 +464,8 @@ public static void RegisterStyleSheet(ControllerContext page, string filePath, i } } - // TODO: fix this as GetLoader is missing - // var loader = page.GetLoader(); - // loader.RegisterDependency(include); + var loader = page.GetLoader(); + loader.RegisterDependency(include); // page.FindControl("ClientResourceIncludes")?.Controls.Add(include); } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj index eb99062f39f..5c0c5b1c956 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/DotNetNuke.Web.MvcPipeline.csproj @@ -41,6 +41,9 @@ + + ..\..\Refs\ClientDependency.Core.Mvc.dll + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs index 1b8a324697d..904ea4f9e6b 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs @@ -8,6 +8,7 @@ namespace DotNetNuke.Web.MvcPipeline.Skins using System.Web.Mvc; using ClientDependency.Core; + using ClientDependency.Core.Mvc; using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Models; @@ -16,7 +17,7 @@ public static partial class SkinHelpers public static IHtmlString DnnCssInclude(this HtmlHelper helper, string filePath, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") { // TODO: ClientDependency Core is deprecated and will not load. - // helper.RequiresCss(filePath, pathNameAlias, priority); + helper.RequiresCss(filePath, pathNameAlias, priority); if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs index 33cd0a9760a..acc09cf6a9a 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnJsInclude.cs @@ -11,6 +11,8 @@ namespace DotNetNuke.Web.MvcPipeline.Skins using System.Web.Mvc; using ClientDependency.Core; + using ClientDependency.Core.Mvc; + using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Models; public static partial class SkinHelpers @@ -26,7 +28,7 @@ public static IHtmlString DnnJsInclude(this HtmlHelper helper, string } // TODO: ClientDependency Core is deprecated - // helper.RequiresJs(filePath, pathNameAlias, priority, htmlAttibs); + helper.RequiresJs(filePath, pathNameAlias, priority, htmlAttibs); if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) { From d17225af660a94d9937f9b7e2a50f91d3fa60af6 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 15:57:10 +0200 Subject: [PATCH 020/146] add use of css variables --- .../Framework/SkinModelFactory.cs | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs index 76756fdd6bf..a904d25c79d 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs @@ -17,8 +17,10 @@ namespace DotNetNuke.Web.MvcPipeline.Framework using DotNetNuke.Entities.Host; using DotNetNuke.Entities.Modules; using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Portals.Extensions; using DotNetNuke.Entities.Tabs; using DotNetNuke.Entities.Tabs.TabVersions; + using DotNetNuke.Framework; using DotNetNuke.Framework.JavaScriptLibraries; using DotNetNuke.Security.Permissions; using DotNetNuke.Services.Exceptions; @@ -30,6 +32,7 @@ namespace DotNetNuke.Web.MvcPipeline.Framework using DotNetNuke.UI.Skins; using DotNetNuke.UI.Skins.Controls; using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Controllers; using DotNetNuke.Web.MvcPipeline.Exceptions; using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; @@ -149,6 +152,10 @@ public SkinModel CreateSkinModel(DnnPageController page) } } + // register css variables + var cssVariablesStyleSheet = this.GetCssVariablesStylesheet(page.PortalSettings.PortalId, page.PortalSettings.GetStyles(), page.PortalSettings.HomeSystemDirectory); + skin.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = cssVariablesStyleSheet, FileOrder = FileOrder.Css.DefaultCss }); + // register the custom stylesheet of current page if (page.PortalSettings.ActiveTab.TabSettings.ContainsKey("CustomStylesheet") && !string.IsNullOrEmpty(page.PortalSettings.ActiveTab.TabSettings["CustomStylesheet"].ToString())) { @@ -248,7 +255,7 @@ private SkinModel LoadSkin(DnnPageController page, string skinPath) // Process the Panes attributes foreach (var key in ctlSkin.Panes.Keys) { - ctlSkin.Panes[key] = this.paneModelFactory.ProcessPane(ctlSkin.Panes[key]); + /*ctlSkin.Panes[key] =*/ this.paneModelFactory.ProcessPane(ctlSkin.Panes[key]); } // this.InvokeSkinEvents(SkinEventType.OnSkinPreRender); @@ -613,6 +620,7 @@ private void InjectControlPanel(SkinModel skin, HttpRequestBase request) { // ControlPanel processing skin.ControlPanelRazor = Path.GetFileNameWithoutExtension(Host.ControlPanel); + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); /* var controlPanel = ControlUtilities.LoadControl(this, Host.ControlPanel); @@ -709,5 +717,44 @@ private IFileInfo GetPageStylesheetInfoCallBack(CacheItemArgs itemArgs) var styleSheet = itemArgs.Params[0].ToString(); return FileManager.Instance.GetFile((int)itemArgs.Params[1], styleSheet); } + + + private string GetCssVariablesStylesheet(int portalId, Abstractions.Portals.IPortalStyles portalStyles, string homeSystemDirectory) + { + var cacheKey = string.Format(DataCache.PortalStylesCacheKey, portalId); + var cacheArgs = new CacheItemArgs( + cacheKey, + DataCache.PortalCacheTimeOut, + DataCache.PortalCachePriority, + portalStyles, + homeSystemDirectory); + string filePath = CBO.GetCachedObject(cacheArgs, this.GetCssVariablesStylesheetCallback); + return filePath; + } + + private string GetCssVariablesStylesheetCallback(CacheItemArgs args) + { + var portalStyles = (PortalStyles)args.Params[0]; + var directory = (string)args.Params[1]; + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var webPath = $"{directory}{portalStyles.FileName}"; + + var physicalPath = $"{directory}{portalStyles.FileName}"; + if (File.Exists(physicalPath)) + { + return webPath; + } + + var styles = portalStyles.ToString(); + File.WriteAllText(physicalPath, styles); + + return webPath; + } + } } From e903cda6706e66fd89cb1b300817e1d1a11ce4b2 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 17:26:57 +0200 Subject: [PATCH 021/146] add support for report-to --- .../ContentSecurityPolicy.cs | 112 ++++++++++++++--- .../CspDirectiveNameMapper.cs | 1 + .../CspDirectiveType.cs | 7 +- .../CspPolicyExample.cs | 2 +- .../DocumentCspContributor.cs | 5 + .../DotNetNuke.ContentSecurityPolicy.csproj | 50 +++++--- .../IContentSecurityPolicy.cs | 26 +++- .../ReportingCspContributor.cs | 26 +++- .../ReportingEndpointContributor.cs | 116 ++++++++++++++++++ 9 files changed, 303 insertions(+), 42 deletions(-) create mode 100644 DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingEndpointContributor.cs diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs index c5a58af5b03..93fd9a20689 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs @@ -93,6 +93,17 @@ public SourceCspContributor ConnectSource } } + /// + /// Gets the connect frame ancestors for managing connect-src directives. + /// + public SourceCspContributor FrameAncestors + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.FrameAncestors); + } + } + /// /// Gets the font source contributor for managing font-src directives. /// @@ -137,6 +148,17 @@ public SourceCspContributor FrameSource } } + /// + /// Gets the Form Action source contributor for managing frame-src directives. + /// + public SourceCspContributor FormAction + { + get + { + return this.GetOrCreateDirective(CspDirectiveType.FormAction); + } + } + /// /// Gets the base URI source contributor for managing base-uri directives. /// @@ -151,20 +173,12 @@ public SourceCspContributor BaseUriSource /// /// Gets collection of CSP contributors. /// - private List Contributors { get; } = new List(); + private List ContentSecurityPolicyContributors { get; } = new List(); /// - /// Generates the complete Content Security Policy. + /// Gets collection of CSP contributors. /// - /// The complete Content Security Policy. - public string GeneratePolicy() - { - return string.Join( - "; ", - this.Contributors - .Select(c => c.GenerateDirective()) - .Where(d => !string.IsNullOrEmpty(d))); - } + private List ReportingEndpointsContributors { get; } = new List(); /// /// Supprime les sources de script du type spécifié de la politique CSP. @@ -216,10 +230,12 @@ public void AddFrameAncestors(CspSourceType sourceType, string value) /// /// Ajoute une URI de rapport à la politique CSP. /// + /// Le nom où les rapports de violation seront envoyés. /// L'URI où les rapports de violation seront envoyés. - public void AddReportUri(string value) + public void AddReportEndpoint(string name, string value) { this.AddReportingDirective(CspDirectiveType.ReportUri, value); + this.AddReportingEndpointsDirective(name, value); } /// @@ -231,9 +247,43 @@ public void AddReportTo(string value) this.AddReportingDirective(CspDirectiveType.ReportTo, value); } + /// + /// Upgrade Insecure Requests. + /// + public void UpgradeInsecureRequests() + { + this.SetDocumentDirective(CspDirectiveType.UpgradeInsecureRequests, string.Empty); + } + + /// + /// Generates the complete Content Security Policy. + /// + /// The complete Content Security Policy. + public string GeneratePolicy() + { + return string.Join( + "; ", + this.ContentSecurityPolicyContributors + .Select(c => c.GenerateDirective()) + .Where(d => !string.IsNullOrEmpty(d))); + } + + /// + /// Génère la politique de sécurité complète. + /// + /// Reporting Endpoints sous forme de chaîne. + public string GenerateReportingEndpoints() + { + return string.Join( + "; ", + this.ReportingEndpointsContributors + .Select(c => c.GenerateDirective()) + .Where(d => !string.IsNullOrEmpty(d))); + } + private SourceCspContributor GetOrCreateDirective(CspDirectiveType directiveType) { - var directive = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + var directive = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; if (directive == null) { directive = new SourceCspContributor(directiveType); @@ -249,13 +299,23 @@ private SourceCspContributor GetOrCreateDirective(CspDirectiveType directiveType private void AddContributor(BaseCspContributor contributor) { // Remove any existing contributor of the same directive type - this.Contributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType); - this.Contributors.Add(contributor); + this.ContentSecurityPolicyContributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType); + this.ContentSecurityPolicyContributors.Add(contributor); + } + + /// + /// Adds a contributor to the policy. + /// + private void AddReportingEndpointsContributors(BaseCspContributor contributor) + { + // Remove any existing contributor of the same directive type + this.ReportingEndpointsContributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType); + this.ReportingEndpointsContributors.Add(contributor); } private void AddSource(CspDirectiveType directiveType, CspSourceType sourceType, string value = null) { - var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; if (contributor == null) { contributor = new SourceCspContributor(directiveType); @@ -272,7 +332,7 @@ private void AddSource(CspDirectiveType directiveType, CspSourceType sourceType, private void RemoveSources(CspDirectiveType directiveType, CspSourceType sourceType) { - var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; + var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor; if (contributor == null) { contributor = new SourceCspContributor(directiveType); @@ -284,7 +344,7 @@ private void RemoveSources(CspDirectiveType directiveType, CspSourceType sourceT private void SetDocumentDirective(CspDirectiveType directiveType, string value) { - var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; + var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; if (contributor == null) { contributor = new DocumentCspContributor(directiveType, value); @@ -296,7 +356,7 @@ private void SetDocumentDirective(CspDirectiveType directiveType, string value) private void AddDocumentDirective(CspDirectiveType directiveType, string value) { - var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; + var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor; if (contributor == null) { contributor = new DocumentCspContributor(directiveType, value); @@ -308,7 +368,7 @@ private void AddDocumentDirective(CspDirectiveType directiveType, string value) private void AddReportingDirective(CspDirectiveType directiveType, string value) { - var contributor = this.Contributors.FirstOrDefault(c => c.DirectiveType == directiveType) as ReportingCspContributor; + var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as ReportingCspContributor; if (contributor == null) { contributor = new ReportingCspContributor(directiveType); @@ -317,5 +377,17 @@ private void AddReportingDirective(CspDirectiveType directiveType, string value) contributor.AddReportingEndpoint(value); } + + private void AddReportingEndpointsDirective(string name, string value) + { + var contributor = this.ReportingEndpointsContributors.FirstOrDefault(c => c.DirectiveType == CspDirectiveType.ReportUri) as ReportingEndpointContributor; + if (contributor == null) + { + contributor = new ReportingEndpointContributor(CspDirectiveType.ReportUri); + this.AddReportingEndpointsContributors(contributor); + } + + contributor.AddReportingEndpoint(name, value); + } } } diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs index 1eb64598195..dd1abeab348 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs @@ -36,6 +36,7 @@ public static string GetDirectiveName(CspDirectiveType directiveType) CspDirectiveType.FrameAncestors => "frame-ancestors", CspDirectiveType.ReportUri => "report-uri", CspDirectiveType.ReportTo => "report-to", + CspDirectiveType.UpgradeInsecureRequests => "upgrade-insecure-requests", _ => throw new ArgumentException("Unknown directive type") }; } diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs index b314adc1650..889db63ea4e 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs @@ -88,5 +88,10 @@ public enum CspDirectiveType /// Directive qui spécifie où envoyer les rapports de violation au format JSON. /// ReportTo, - } + + /// + /// Directive qui spécifie UpgradeInsecureRequests. + /// + UpgradeInsecureRequests, +} } diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs index dbf817712c4..c8d424e6d98 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs @@ -28,7 +28,7 @@ public static void Example() csp.AddSandboxDirective("allow-scripts allow-same-origin"); // Add a reporting contributor - csp.AddReportUri("https://example.com/csp-report"); + csp.AddReportEndpoint("name", "https://example.com/csp-report"); // Generate the complete policy string policy = csp.GeneratePolicy(); diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs index 4db01cd407f..3457dfe3ce9 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs @@ -44,6 +44,11 @@ public void SetDirectiveValue(string value) /// The directive string. public override string GenerateDirective() { + if (this.DirectiveType == CspDirectiveType.UpgradeInsecureRequests) + { + return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)}"; + } + if (string.IsNullOrWhiteSpace(this.DirectiveValue)) { return string.Empty; diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj index 7aff6a81d10..14e0b2ee646 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj @@ -1,25 +1,41 @@ - + + - DotNetNuke.ContentSecurityPolicy netstandard2.0 + false + true + $(MSBuildProjectDirectory)\..\.. + bin/$(Configuration)/$(TargetFramework)/DotNetNuke.ContentSecurityPolicy.xml + + true latest - bin - false - false - Sacha Trauwaen - Dnn - ContentSecurityPolicy - 2025 - ContentSecurityPolicy - 0.0.1.0 - 0.0.1.0 - DNN MVC-pipeline project - en-US - - Library + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + + + SolutionInfo.cs + + + + + + + + + + + + + diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs index aff12349503..08f9a531776 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs @@ -59,6 +59,16 @@ public interface IContentSecurityPolicy /// SourceCspContributor FrameSource { get; } + /// + /// Gets the frame ancestors contributor. + /// + SourceCspContributor FrameAncestors { get; } + + /// + /// Gets the Form action source contributor. + /// + SourceCspContributor FormAction { get; } + /// /// Gets the base URI source contributor. /// @@ -99,8 +109,9 @@ public interface IContentSecurityPolicy /// /// Ajoute une URI de rapport à la politique. /// - /// L'URI où envoyer les rapports de violation. - void AddReportUri(string value); + /// Le nom où les rapports de violation seront envoyés. + /// L'URI où les rapports de violation seront envoyés. + public void AddReportEndpoint(string name, string value); /// /// Ajoute une destination de rapport à la politique. @@ -113,5 +124,16 @@ public interface IContentSecurityPolicy /// /// La politique de sécurité complète sous forme de chaîne. string GeneratePolicy(); + + /// + /// Génère la politique de sécurité complète. + /// + /// Reporting Endpoints sous forme de chaîne. + string GenerateReportingEndpoints(); + + /// + /// Upgrade Insecure Requests. + /// + void UpgradeInsecureRequests(); } } diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs index 1a6eabaeefc..a848bdb7a23 100644 --- a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingCspContributor.cs @@ -7,6 +7,7 @@ namespace DotNetNuke.ContentSecurityPolicy using System; using System.Collections.Generic; using System.Linq; + using System.Net; using System.Text.RegularExpressions; ///
    @@ -72,7 +73,30 @@ public override string GenerateDirective() /// /// Validates reporting endpoint. /// - private void ValidateReportingEndpoint(string endpoint) + private void ValidateReportingEndpoint(string value) + { + switch (this.DirectiveType) + { + case CspDirectiveType.ReportUri: + this.ValidateReportUri(value); + break; + case CspDirectiveType.ReportTo: + this.ValidateReportTo(value); + break; + + // Add more specific validations as needed + } + } + + private void ValidateReportTo(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Reporting to cannot be empty"); + } + } + + private void ValidateReportUri(string endpoint) { if (string.IsNullOrWhiteSpace(endpoint)) { diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingEndpointContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingEndpointContributor.cs new file mode 100644 index 00000000000..932cb504658 --- /dev/null +++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ReportingEndpointContributor.cs @@ -0,0 +1,116 @@ +// 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.ContentSecurityPolicy +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Text.RegularExpressions; + + /// + /// Contributor for reporting directives. + /// + public class ReportingEndpointContributor : BaseCspContributor + { + /// + /// Initializes a new instance of the class. + /// + /// Le type de directive de rapport (ReportUri). + public ReportingEndpointContributor(CspDirectiveType directiveType) + { + if (directiveType != CspDirectiveType.ReportUri) + { + throw new ArgumentException("Invalid reporting directive type"); + } + + this.DirectiveType = directiveType; + } + + /// + /// Gets collection of reporting endpoints. + /// + private Dictionary ReportingEndpoints { get; } = new Dictionary(); + + /// + /// Adds a reporting endpoint. + /// + /// Le nom de l'endpoint où envoyer les rapports. + /// L'URL de l'endpoint où envoyer les rapports. + public void AddReportingEndpoint(string name, string endpoint) + { + this.ValidateReportingEndpoint(endpoint); + if (!this.ReportingEndpoints.ContainsKey(name)) + { + this.ReportingEndpoints.Add(name, endpoint); + } + } + + /// + /// Removes a reporting endpoint. + /// + /// The endpoint to remove. + public void RemoveReportingEndpoint(string name) + { + this.ReportingEndpoints.Remove(name); + } + + /// + /// Generates the directive string. + /// + /// The directive string. + public override string GenerateDirective() + { + if (!this.ReportingEndpoints.Any()) + { + return string.Empty; + } + + var endpoints = this.ReportingEndpoints.Select(ep => $"{ep.Key}=\"{ep.Value}\"").ToList(); + return $"{string.Join(" ", endpoints)}"; + } + + /// + /// Validates reporting endpoint. + /// + private void ValidateReportingEndpoint(string value) + { + switch (this.DirectiveType) + { + case CspDirectiveType.ReportUri: + this.ValidateReportUri(value); + break; + case CspDirectiveType.ReportTo: + this.ValidateReportTo(value); + break; + + // Add more specific validations as needed + } + } + + private void ValidateReportTo(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Reporting to cannot be empty"); + } + } + + private void ValidateReportUri(string endpoint) + { + if (string.IsNullOrWhiteSpace(endpoint)) + { + throw new ArgumentException("Reporting endpoint cannot be empty"); + } + + // URL validation regex + var urlRegex = new Regex(@"^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$"); + if (!urlRegex.IsMatch(endpoint)) + { + throw new ArgumentException($"Invalid reporting endpoint: {endpoint}"); + } + } + } +} From 55308ab4f579faf412a685ec52d358ccc6fb9be5 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 17:27:45 +0200 Subject: [PATCH 022/146] update web.config for mvc --- .../DotNetNuke.Web.Client.csproj | 1 + .../MvcClientResourceManager.cs | 7 +++- .../Providers/DnnStandardRenderer.cs | 32 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 DNN Platform/DotNetNuke.Web.Client/Providers/DnnStandardRenderer.cs diff --git a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj index d3587e1eb25..d20e0ccb9ff 100644 --- a/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj +++ b/DNN Platform/DotNetNuke.Web.Client/DotNetNuke.Web.Client.csproj @@ -126,6 +126,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs index f6eb19ac622..cf6fccf60c9 100644 --- a/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs +++ b/DNN Platform/DotNetNuke.Web.Client/MvcClientResourceManager.cs @@ -102,6 +102,12 @@ public static void AddConfiguration() + + + + + + @@ -316,7 +322,6 @@ public static void RegisterScript( // include.HtmlAttributes["defer"] = "defer"; include.HtmlAttributes["nonce"] = HttpContext.Current.Items["CSP-NONCE"].ToString(); - var loader = page.GetLoader(); loader.RegisterDependency(include, include.HtmlAttributes); diff --git a/DNN Platform/DotNetNuke.Web.Client/Providers/DnnStandardRenderer.cs b/DNN Platform/DotNetNuke.Web.Client/Providers/DnnStandardRenderer.cs new file mode 100644 index 00000000000..83c0d9bb86d --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.Client/Providers/DnnStandardRenderer.cs @@ -0,0 +1,32 @@ +// 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.Web.Client.Providers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + using ClientDependency.Core.FileRegistration.Providers; + + public class DnnStandardRenderer : StandardRenderer + { + private readonly ClientResourceSettings dnnSettingsHelper = new ClientResourceSettings(); + + /// + /// Gets a value indicating whether checks if the composite files option is set for the current portal (DNN site settings). + /// If not enabled at the portal level it defers to the core CDF setting (web.config). + /// + public override bool EnableCompositeFiles + { + get + { + var settingsVersion = this.dnnSettingsHelper.AreCompositeFilesEnabled(); + return settingsVersion.HasValue ? settingsVersion.Value : base.EnableCompositeFiles; + } + } + } +} From e107bd430c6d24f6b6a9b3afc48ba2eb9fb1b67f Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 17:31:25 +0200 Subject: [PATCH 023/146] port all what is needed to run from dnn9 version --- .../Containers/SkinHelpers.Content.cs | 4 +- .../Controllers/DnnPageController.cs | 9 + .../Entities/Urls/MvcUrlRewriterController.cs | 7 +- .../Framework/ContainerModelFactory.cs | 10 +- .../Framework/SkinModelFactory.cs | 3 + .../Models/ContainerModel.cs | 12 +- .../Models/SkinModel.cs | 7 + .../Routing/MvcRoutingManager.cs | 2 +- .../Skins/SkinHelpers.Pane.cs | 11 +- .../Controllers/DefaultController.cs | 27 +- .../Controllers/ModuleActionsController.cs | 6 +- .../ModuleActionsViewController.cs | 13 +- .../Controllers/PrivacyViewController.cs} | 6 +- .../Controllers/TermsViewController.cs} | 6 +- .../DotNetNuke.Web.MvcWebsite.csproj | 63 ++ .../Models/ModuleActionsDeleteModel.cs | 2 +- .../Models/ModuleActionsModel.cs | 2 +- .../DotNetNuke.Web.MvcWebsite/dnn.json | 16 + .../Library/Properties/AssemblyInfoMvc.cs | 1 + .../Website/DotNetNuke.Website.csproj | 27 + .../Website/Views/Default/Layout.cshtml | 130 +++ .../Views/ModuleActionsView/Invoke.cshtml | 209 +++++ .../Views/ModulePermissionsGrid/Index.cshtml | 132 ++++ .../Views/ModuleSettingsView/Invoke.cshtml | 394 +++++++++ .../Views/PersonaBarContainer/Index.cshtml | 44 ++ .../Website/Views/PrivacyView/Invoke.cshtml | 5 + .../Website/Views/Shared/_Layout.cshtml | 13 + .../Views/Shared/_PermissionTriState.cshtml | 8 + .../Website/Views/TermsView/Invoke.cshtml | 5 + DNN Platform/Website/Views/web.config | 33 + DNN Platform/Website/js/mvc.js | 181 +++++ DNN_Platform.sln | 27 + .../Dnn.EditBar.UI/Dnn.EditBar.UI.csproj | 10 + .../Mvc/MvcContentEditorManager.cs | 745 ++++++++++++++++++ .../Dnn.PersonaBar.UI.csproj | 10 + .../Mvc/PersonaBarContainerController.cs | 98 +++ .../Mvc/PersonaBarContainerModel.cs | 18 + 37 files changed, 2247 insertions(+), 49 deletions(-) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website => DotNetNuke.Web.MvcWebsite}/Controllers/DefaultController.cs (91%) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website => DotNetNuke.Web.MvcWebsite}/Controllers/ModuleActionsController.cs (89%) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website => DotNetNuke.Web.MvcWebsite}/Controllers/ModuleActionsViewController.cs (97%) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs => DotNetNuke.Web.MvcWebsite/Controllers/PrivacyViewController.cs} (78%) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs => DotNetNuke.Web.MvcWebsite/Controllers/TermsViewController.cs} (78%) create mode 100644 DNN Platform/DotNetNuke.Web.MvcWebsite/DotNetNuke.Web.MvcWebsite.csproj rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website => DotNetNuke.Web.MvcWebsite}/Models/ModuleActionsDeleteModel.cs (71%) rename DNN Platform/{DotNetNuke.Web.MvcPipeline/Website => DotNetNuke.Web.MvcWebsite}/Models/ModuleActionsModel.cs (94%) create mode 100644 DNN Platform/DotNetNuke.Web.MvcWebsite/dnn.json create mode 100644 DNN Platform/Website/Views/Default/Layout.cshtml create mode 100644 DNN Platform/Website/Views/ModuleActionsView/Invoke.cshtml create mode 100644 DNN Platform/Website/Views/ModulePermissionsGrid/Index.cshtml create mode 100644 DNN Platform/Website/Views/ModuleSettingsView/Invoke.cshtml create mode 100644 DNN Platform/Website/Views/PersonaBarContainer/Index.cshtml create mode 100644 DNN Platform/Website/Views/PrivacyView/Invoke.cshtml create mode 100644 DNN Platform/Website/Views/Shared/_Layout.cshtml create mode 100644 DNN Platform/Website/Views/Shared/_PermissionTriState.cshtml create mode 100644 DNN Platform/Website/Views/TermsView/Invoke.cshtml create mode 100644 DNN Platform/Website/Views/web.config create mode 100644 DNN Platform/Website/js/mvc.js create mode 100644 Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Mvc/MvcContentEditorManager.cs create mode 100644 Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerController.cs create mode 100644 Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerModel.cs diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs index d66db32ea5f..3d8409df8a0 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Containers/SkinHelpers.Content.cs @@ -19,7 +19,7 @@ namespace DotNetNuke.Web.MvcPipeline.Containers public static partial class SkinHelpers { - public static IHtmlString Content(this HtmlHelper htmlHelper, PortalSettings portalSettings) + public static IHtmlString Content(this HtmlHelper htmlHelper) { var model = htmlHelper.ViewData.Model; if (model == null) @@ -33,7 +33,7 @@ public static IHtmlString Content(this HtmlHelper htmlHelper, Po moduleContentPaneDiv.AddCssClass(model.ContentPaneCssClass); } - if (!ModuleHostModel.IsViewMode(model.ModuleConfiguration, portalSettings) && htmlHelper.ViewContext.HttpContext.Request.QueryString["dnnprintmode"] != "true") + if (!ModuleHostModel.IsViewMode(model.ModuleConfiguration, model.PortalSettings) && htmlHelper.ViewContext.HttpContext.Request.QueryString["dnnprintmode"] != "true") { MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); if (model.EditMode && model.ModuleConfiguration.ModuleID > 0) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs index 2a15fbc9789..248d67bc965 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Controllers/DnnPageController.cs @@ -10,6 +10,7 @@ namespace DotNetNuke.Web.MvcPipeline.Controllers using DotNetNuke.Entities.Portals; using DotNetNuke.Entities.Tabs; using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.Framework; public abstract class DnnPageController : Controller, IMvcController { @@ -64,5 +65,13 @@ protected override void Initialize(RequestContext requestContext) // this.Url = new DnnUrlHelper(requestContext, this); } + + public static void RegisterAjaxScript(ControllerContext context) + { + if (MvcServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) + { + MvcServicesFrameworkInternal.Instance.RegisterAjaxScript(context); + } + } } } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs index 0429afaec0c..8973bea92e1 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Entities/Urls/MvcUrlRewriterController.cs @@ -10,7 +10,7 @@ internal class MvcUrlRewriterController { internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, HttpContext context, int tabId, int portalId) { - var mvcCtls = new[] { "Module", "Terms", "Privacy" }; + var mvcCtls = new[] { /*"Module",*/ "Terms", "Privacy" }; bool mvcCtl = false; /* bool mvcSkin = false; @@ -31,7 +31,7 @@ internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, { mvcCtl = mvcCtl || result.RewritePath.Contains("&ctl=" + item); } - + /* if (mvcCtl && result.RewritePath.Contains("&ctl=Module")) { TabInfo tab = null; @@ -46,6 +46,7 @@ internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, // mvcCtl = queryStringCol["ReturnURL"] != null && queryStringCol["ReturnURL"].EndsWith("mvc"); } + */ } else { @@ -58,8 +59,6 @@ internal static bool IsMvc(UrlAction result, NameValueCollection queryStringCol, mvcCtl = tab.GetTags().Contains("mvc"); } } - - // mvcCtl = result.RawUrl.EndsWith("mvc"); } mvcCtl = mvcCtl && !result.RewritePath.Contains("mvcpage=no") && queryStringCol["mvcpage"] != "no"; diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs index 693f7699c34..167cf9e9148 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/ContainerModelFactory.cs @@ -23,7 +23,7 @@ public ContainerModelFactory() public ContainerModel CreateContainerModel(ModuleInfo configuration, PortalSettings portalSettings) { - var container = new ContainerModel(configuration); + var container = new ContainerModel(configuration, portalSettings); container = this.ProcessModule(container, portalSettings); return container; } @@ -187,9 +187,11 @@ private ContainerModel ProcessStylesheets(ContainerModel container, bool include // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, container.ContainerPath + "container.css", FileOrder.Css.ContainerCss); container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = container.ContainerPath + "container.css", FileOrder = FileOrder.Css.ContainerCss }); - // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, container.ContainerSrc.Replace(".ascx", ".css"), FileOrder.Css.SpecificContainerCss); - container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = container.ContainerSrc.Replace(".ascx", ".css"), FileOrder = FileOrder.Css.SpecificContainerCss }); - + if (!string.IsNullOrEmpty(container.ContainerSrc)) + { + // MvcClientResourceManager.RegisterStyleSheet(this.Page.ControllerContext, container.ContainerSrc.Replace(".ascx", ".css"), FileOrder.Css.SpecificContainerCss); + container.RegisteredStylesheets.Add(new RegisteredStylesheet { Stylesheet = container.ContainerSrc.Replace(".ascx", ".css"), FileOrder = FileOrder.Css.SpecificContainerCss }); + } // process the base class module properties if (includeModuleCss) { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs index a904d25c79d..5fee7b2d3e6 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Framework/SkinModelFactory.cs @@ -338,9 +338,12 @@ private ModuleMessageModel GetModuleMessage(string heading, string message, Modu private void LoadPanes(PortalSettings portalSettings) { + /* portalSettings.ActiveTab.Panes.Add("HeaderPane"); portalSettings.ActiveTab.Panes.Add("ContentPane"); portalSettings.ActiveTab.Panes.Add("ContentPaneLower"); + */ + /* // iterate page controls foreach (Control ctlControl in this.Controls) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs index d39fcabb9c1..4cca7dda7f4 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/ContainerModel.cs @@ -7,19 +7,29 @@ namespace DotNetNuke.Web.MvcPipeline.Models using System.IO; using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Portals; using DotNetNuke.UI.Modules; public class ContainerModel { private ModuleInfo moduleConfiguration; private ModuleHostModel moduleHost; + private PortalSettings portalSettings; - public ContainerModel(ModuleInfo moduleConfiguration) + public ContainerModel(ModuleInfo moduleConfiguration, PortalSettings portalSettings) { this.moduleConfiguration = moduleConfiguration; this.moduleHost = new ModuleHostModel(moduleConfiguration); + this.portalSettings = portalSettings; } + public PortalSettings PortalSettings + { + get + { + return this.portalSettings; + } + } public ModuleHostModel ModuleHost { get diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs index 2edff0580bf..ad2b4390b9a 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Models/SkinModel.cs @@ -13,6 +13,13 @@ namespace DotNetNuke.Web.MvcPipeline.Models public class SkinModel { private Dictionary panes; + + public SkinModel() + { + this.PageMessages = new List(); + this.ModuleMessages = new List(); + } + /* private PageModel pageModel; diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs index 12e4c0c69db..c231eaa3e90 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Routing/MvcRoutingManager.cs @@ -51,7 +51,7 @@ private static bool IsTracingEnabled() private void RegisterSystemRoutes() { var dataTokens = new RouteValueDictionary(); - var ns = new string[] { "DotNetNuke.Web.MvcPipeline.Website.Controllers" }; + var ns = new string[] { "DotNetNuke.Web.MvcWebsite.Controllers" }; dataTokens["Namespaces"] = ns; var route = new Route( diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs index ea0cdf2750a..3b196662a62 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.Pane.cs @@ -38,15 +38,14 @@ public static IHtmlString Pane(this HtmlHelper htmlHelper, string id, if (model.IsEditMode) { editDiv.AddCssClass(cssClass); - - // paneDiv.AddCssClass(model.Skin.PaneCssClass); + paneDiv.AddCssClass(model.Skin.PaneCssClass); paneDiv.Attributes["data-name"] = id; } else { paneDiv.AddCssClass(cssClass); } - + id = id.ToLower(); if (model.Skin.Panes.ContainsKey(id)) { var pane = model.Skin.Panes[id]; @@ -84,6 +83,10 @@ public static IHtmlString Pane(this HtmlHelper htmlHelper, string id, else { paneDiv.AddCssClass("DNNEmptyPane"); + if (model.IsEditMode) + { + paneDiv.AddCssClass("EditBarEmptyPane"); + } } if (model.IsEditMode) @@ -93,7 +96,7 @@ public static IHtmlString Pane(this HtmlHelper htmlHelper, string id, } else { - return MvcHtmlString.Create(paneDiv.InnerHtml); + return MvcHtmlString.Create(paneDiv.ToString()); } } } diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/DefaultController.cs similarity index 91% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/DefaultController.cs index 2df02c5008c..c926b68eaf4 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/DefaultController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/DefaultController.cs @@ -2,13 +2,13 @@ // 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.MvcPipeline.Website.Controllers +namespace DotNetNuke.Web.MvcWebsite.Controllers { using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; - + using Dnn.EditBar.UI.Mvc; using DotNetNuke.Abstractions; using DotNetNuke.Common.Utilities; using DotNetNuke.ContentSecurityPolicy; @@ -45,14 +45,6 @@ public DefaultController(IContentSecurityPolicy contentSecurityPolicy, INavigati this.pageModelFactory = pageModelFactory; } - public static void RegisterAjaxScript(ControllerContext context) - { - if (MvcServicesFrameworkInternal.Instance.IsAjaxScriptSupportRequired) - { - MvcServicesFrameworkInternal.Instance.RegisterAjaxScript(context); - } - } - public ActionResult Page(int tabid, string language) { this.HttpContext.Items.Add("CSP-NONCE", this.contentSecurityPolicy.Nonce); @@ -62,10 +54,13 @@ public ActionResult Page(int tabid, string language) this.contentSecurityPolicy.FontSource.AddSelf(); this.contentSecurityPolicy.StyleSource.AddSelf(); this.contentSecurityPolicy.FrameSource.AddSelf(); + this.contentSecurityPolicy.FormAction.AddSelf(); + this.contentSecurityPolicy.FrameAncestors.AddSelf(); this.contentSecurityPolicy.ObjectSource.AddNone(); this.contentSecurityPolicy.BaseUriSource.AddNone(); this.contentSecurityPolicy.ScriptSource.AddNonce(this.contentSecurityPolicy.Nonce); - this.contentSecurityPolicy.AddReportUri(this.Request.Url.Scheme + "://" + this.Request.Url.Host + "/mvc/Csp/Report"); + this.contentSecurityPolicy.AddReportTo("csp-endpoint"); + this.contentSecurityPolicy.AddReportEndpoint("csp-endpoint", this.Request.Url.Scheme + "://" + this.Request.Url.Host + "/DesktopModules/Csp/Report"); if (this.Request.IsAuthenticated) { @@ -83,7 +78,7 @@ public ActionResult Page(int tabid, string language) if (PortalSettings.Current.UserId > 0) { // TODO: should we do this? It creates a dependency towards the PersonaBar which is probably not a great idea - // MvcContentEditorManager.CreateManager(this); + MvcContentEditorManager.CreateManager(this); } // Configure the ActiveTab with Skin/Container information @@ -113,7 +108,7 @@ public ActionResult Page(int tabid, string language) this.RegisterScriptsAndStylesheets(model); // this.Response.AddHeader("Content-Security-Policy", $"default-src 'self';base-uri 'self';form-action 'self';object-src 'none'; img-src *; style-src 'self' 'unsafe-inline';font-src *; script-src * 'unsafe-inline';"); - return this.View(model.Skin.RazorFile, model); + return this.View(model.Skin.RazorFile, "Layout", model); } private void RegisterScriptsAndStylesheets(PageModel page) @@ -145,11 +140,11 @@ private void InitializePage(PageModel page) // redirect to a specific tab based on name if (!string.IsNullOrEmpty(this.Request.QueryString["tabname"])) { - TabInfo tab = TabController.Instance.GetTabByName(this.Request.QueryString["TabName"], this.PortalSettings.PortalId); + var tab = TabController.Instance.GetTabByName(this.Request.QueryString["TabName"], this.PortalSettings.PortalId); if (tab != null) { var parameters = new List(); // maximum number of elements - for (int intParam = 0; intParam <= this.Request.QueryString.Count - 1; intParam++) + for (var intParam = 0; intParam <= this.Request.QueryString.Count - 1; intParam++) { switch (this.Request.QueryString.Keys[intParam].ToLowerInvariant()) { @@ -174,7 +169,7 @@ private void InitializePage(PageModel page) } } - string cacheability = this.Request.IsAuthenticated ? Host.AuthenticatedCacheability : Host.UnauthenticatedCacheability; + var cacheability = this.Request.IsAuthenticated ? Host.AuthenticatedCacheability : Host.UnauthenticatedCacheability; switch (cacheability) { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsController.cs similarity index 89% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsController.cs index f48c395d8b2..12d870d193a 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsController.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.MvcPipeline.Website.Controllers +namespace DotNetNuke.Web.MvcWebsite.Controllers { using System.Web.Mvc; @@ -11,7 +11,7 @@ namespace DotNetNuke.Web.MvcPipeline.Website.Controllers using DotNetNuke.Entities.Users; using DotNetNuke.Services.Localization; using DotNetNuke.Services.Log.EventLog; - using DotNetNuke.Web.MvcPipeline.Website.Models; + using DotNetNuke.Web.MvcWebsite.Models; public class ModuleActionsController : Controller { @@ -29,7 +29,7 @@ public ActionResult Delete(ModuleActionsDeleteModel model) var user = UserController.Instance.GetCurrentUserInfo(); if (!module.IsShared) { - foreach (ModuleInfo instance in ModuleController.Instance.GetTabModulesByModule(module.ModuleID)) + foreach (var instance in ModuleController.Instance.GetTabModulesByModule(module.ModuleID)) { if (instance.IsShared) { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsViewController.cs similarity index 97% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsViewController.cs index 226fe4304b3..5c6ce7fdee4 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/ModuleActionsViewController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/ModuleActionsViewController.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.MvcPipeline.Website.Controllers +namespace DotNetNuke.Web.MvcWebsite.Controllers { using System; using System.Collections.Generic; @@ -27,7 +27,7 @@ namespace DotNetNuke.Web.MvcPipeline.Website.Controllers using DotNetNuke.Web.Client.ClientResourceManagement; using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; using DotNetNuke.Web.MvcPipeline.Models; - using DotNetNuke.Web.MvcPipeline.Website.Models; + using DotNetNuke.Web.MvcWebsite.Models; public class ModuleActionsViewController : Controller { @@ -169,7 +169,7 @@ protected void OnLoad(ModuleInfo moduleInfo) { // base.OnLoad(e); this.ModuleContext = new ModuleInstanceContext() { Configuration = moduleInfo }; - ModuleActionCollection moduleActions = new ModuleActionCollection(); + var moduleActions = new ModuleActionCollection(); var desktopModule = DesktopModuleController.GetDesktopModule(moduleInfo.DesktopModuleID, moduleInfo.PortalID); if (!string.IsNullOrEmpty(desktopModule.BusinessControllerClass)) { @@ -213,11 +213,12 @@ protected void OnLoad(ModuleInfo moduleInfo) } */ moduleSpecificActions.Actions.Add(action); - + if (!UIUtilities.IsLegacyUI(this.ModuleContext.ModuleId, action.ControlKey, this.ModuleContext.PortalId) && action.Url.Contains("ctl")) { action.ClientScript = UrlUtils.PopUpUrl(action.Url, null, this.PortalSettings, true, false); } + } } @@ -263,8 +264,8 @@ protected void OnLoad(ModuleInfo moduleInfo) { if (action.Visible) { - if ((this.EditMode && Globals.IsAdminControl() == false) || - (action.Secure != SecurityAccessLevel.Anonymous && action.Secure != SecurityAccessLevel.View)) + if (this.EditMode && Globals.IsAdminControl() == false || + action.Secure != SecurityAccessLevel.Anonymous && action.Secure != SecurityAccessLevel.View) { if (!action.Icon.Contains("://") && !action.Icon.StartsWith("/") diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/PrivacyViewController.cs similarity index 78% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/PrivacyViewController.cs index 836e9a137c7..0702fa530e2 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/PrivacyController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/PrivacyViewController.cs @@ -2,16 +2,16 @@ // 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.MvcPipeline.Website.Controllers +namespace DotNetNuke.Web.MvcWebsite.Controllers { using System.Web.Mvc; using DotNetNuke.Entities.Portals; using DotNetNuke.Services.Localization; - public class PrivacyController : Controller + public class PrivacyViewController : Controller { - public ActionResult Index() + public ActionResult Invoke() { var privacy = Localization.GetSystemMessage(PortalSettings.Current, "MESSAGE_PORTAL_PRIVACY"); return this.View("Index", string.Empty, privacy); diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/TermsViewController.cs similarity index 78% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/TermsViewController.cs index ad6f026e64e..9aca37156c3 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Controllers/TermsController.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Controllers/TermsViewController.cs @@ -2,16 +2,16 @@ // 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.MvcPipeline.Website.Controllers +namespace DotNetNuke.Web.MvcWebsite.Controllers { using System.Web.Mvc; using DotNetNuke.Entities.Portals; using DotNetNuke.Services.Localization; - public class TermsController : Controller + public class TermsViewController : Controller { - public ActionResult Index() + public ActionResult Invoke() { var terms = Localization.GetSystemMessage(PortalSettings.Current, "MESSAGE_PORTAL_TERMS"); return this.View("Index", string.Empty, terms); diff --git a/DNN Platform/DotNetNuke.Web.MvcWebsite/DotNetNuke.Web.MvcWebsite.csproj b/DNN Platform/DotNetNuke.Web.MvcWebsite/DotNetNuke.Web.MvcWebsite.csproj new file mode 100644 index 00000000000..7112c72113a --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/DotNetNuke.Web.MvcWebsite.csproj @@ -0,0 +1,63 @@ + + + DotNetNuke.Web.MvcWebsite + net48 + true + latest + bin + false + false + Sacha Trauwaen + Dnn + MvcWebsite + 2025 + MvcWebsite + 0.0.1.0 + 0.0.1.0 + DNN MVC-Website project + en-US + + Library + + Portable + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\Refs\ClientDependency.Core.Mvc.dll + + + + + + + + + + + + + + + + diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsDeleteModel.cs similarity index 71% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsDeleteModel.cs index e0e1defe7fe..718cf228467 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsDeleteModel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsDeleteModel.cs @@ -1,4 +1,4 @@ -namespace DotNetNuke.Web.MvcPipeline.Website.Models +namespace DotNetNuke.Web.MvcWebsite.Models { public class ModuleActionsDeleteModel { diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs b/DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsModel.cs similarity index 94% rename from DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs rename to DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsModel.cs index 9780f85abdf..0d8d9bc76f3 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Website/Models/ModuleActionsModel.cs +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/Models/ModuleActionsModel.cs @@ -1,4 +1,4 @@ -namespace DotNetNuke.Web.MvcPipeline.Website.Models +namespace DotNetNuke.Web.MvcWebsite.Models { using System.Collections.Generic; diff --git a/DNN Platform/DotNetNuke.Web.MvcWebsite/dnn.json b/DNN Platform/DotNetNuke.Web.MvcWebsite/dnn.json new file mode 100644 index 00000000000..19ed13eca26 --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.MvcWebsite/dnn.json @@ -0,0 +1,16 @@ +{ + "projectType": "library", + "name": "Dnn_DotNetNukeWebMvcWebsite", + "friendlyName": "Dnn DotNetNukeWebMvcWebsite", + "description": "Dnn DotNetNukeWebMvcWebsite Library", + "packageName": "Dnn_DotNetNukeWebMvcWebsite", + "folder": "Dnn/DotNetNukeWebMvcWebsite", + "library": {}, + "pathsAndFiles": { + "pathToAssemblies": "./bin", + "pathToScripts": "./Server/SqlScripts", + "assemblies": ["DotNetNuke.Web.MvcWebsite.dll"], + "releaseFiles": [], + "zipName": "Dnn.DotNetNukeWebMvcWebsite" + } +} diff --git a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs index f394d157580..88c78f385a2 100644 --- a/DNN Platform/Library/Properties/AssemblyInfoMvc.cs +++ b/DNN Platform/Library/Properties/AssemblyInfoMvc.cs @@ -7,4 +7,5 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: InternalsVisibleTo("DotNetNuke.Web.MvcPipeline")] +[assembly: InternalsVisibleTo("DotNetNuke.Web.MvcWebsite")] [assembly: InternalsVisibleTo("DotNetNuke.Web.NewDDRMenu")] // Once Globals is refactored to Dependency Injection we should be able to remove this diff --git a/DNN Platform/Website/DotNetNuke.Website.csproj b/DNN Platform/Website/DotNetNuke.Website.csproj index dd952e228e9..8e179818d0e 100644 --- a/DNN Platform/Website/DotNetNuke.Website.csproj +++ b/DNN Platform/Website/DotNetNuke.Website.csproj @@ -77,6 +77,9 @@ ..\..\packages\Dnn.ClientDependency.1.9.10\lib\net45\ClientDependency.Core.dll + + ..\..\Refs\ClientDependency.Core.Mvc.dll + False ..\Controls\DotNetNuke.WebControls\bin\DotNetNuke.WebControls.dll @@ -1268,6 +1271,18 @@ + + + + + + + + + + + + web.config @@ -3507,4 +3522,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/DNN Platform/Website/Views/Default/Layout.cshtml b/DNN Platform/Website/Views/Default/Layout.cshtml new file mode 100644 index 00000000000..afe6e7ef881 --- /dev/null +++ b/DNN Platform/Website/Views/Default/Layout.cshtml @@ -0,0 +1,130 @@ +@using ClientDependency.Core +@using ClientDependency.Core.Mvc +@using DotNetNuke.Web.MvcPipeline +@model DotNetNuke.Web.MvcPipeline.Models.PageModel +@{ + var SkinPath = Path.GetDirectoryName(Model.Skin.SkinSrc).Replace("\\","/"); + DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries.MvcJavaScript.Register(this.ViewContext); + + + //DotNetNuke.Framework.Controllers.DefaultController.RegisterAjaxScript(this.ViewContext.Controller.ControllerContext); +} + + + + + @if (!string.IsNullOrEmpty(Model.MetaRefresh)) + { + + } + @if (!string.IsNullOrEmpty(Model.Description)) + { + + } + @if (!string.IsNullOrEmpty(Model.KeyWords)) + { + + } + @if (!string.IsNullOrEmpty(Model.Copyright)) + { + + } + @if (!string.IsNullOrEmpty(Model.Generator)) + { + + } + @**@ + @if (!string.IsNullOrEmpty(Model.MetaRobots)) + { + + } + @if (!string.IsNullOrEmpty(Model.CanonicalLinkUrl)) + { + + } + @if (!string.IsNullOrEmpty(Model.FavIconLink)) + { + @Html.Raw(Model.FavIconLink) + } + [MVC] @Model.Title + @Html.RegisterAjaxScriptIfRequired() + @RenderSection("head", required: false) + @Html.RenderCssHere(new BasicPath("SkinPath", SkinPath), new BasicPath("SharedScripts", "~/Resources/Shared/Scripts/")) + @RenderSection("stylesheets", required: false) + @Html.RenderJsHere(new BasicPath("SkinPath", SkinPath), new BasicPath("SharedScripts", "~/Resources/Shared/Scripts/")) + @RenderSection("scripts", required: false) + + +
    +
    + + + @*@Html.Raw(Model.AntiForgery)*@ + @Html.AntiForgeryIfRequired() +
    + + @if (!string.IsNullOrEmpty(Model.Skin.ControlPanelRazor)) + { + @Html.Action("Index", Model.Skin.ControlPanelRazor, Model); + } + + @if (!string.IsNullOrEmpty(Model.Skin.SkinError)) + { +
    Model.Skin.SkinError
    + } + + @if (Model.Skin.PageMessages.Any() || Model.Skin.ModuleMessages.Any()) + { +
    + @foreach (var msg in Model.Skin.PageMessages) + { +
    +
    @msg.Heading
    +
    @msg.Text
    +
    + } + @foreach (var msg in Model.Skin.ModuleMessages) + { +
    +
    +
    @msg.Heading
    +
    @msg.Text
    +
    +
    + } +
    + + } + @RenderBody() + @if (Model.IsEditMode) + { + + } + @if (Model.StartupScripts.Any()) + { + + } + + diff --git a/DNN Platform/Website/Views/ModuleActionsView/Invoke.cshtml b/DNN Platform/Website/Views/ModuleActionsView/Invoke.cshtml new file mode 100644 index 00000000000..a151ead03c9 --- /dev/null +++ b/DNN Platform/Website/Views/ModuleActionsView/Invoke.cshtml @@ -0,0 +1,209 @@ +@model DotNetNuke.Web.MvcWebsite.Models.ModuleActionsModel + +@using DotNetNuke.Web.MvcPipeline +@using DotNetNuke.UI.Modules +@using DotNetNuke.Services.Localization + +@{ + +} + +
    + +@if (Model.SupportsQuickSettings) +{ + +} + + \ No newline at end of file diff --git a/DNN Platform/Website/Views/ModulePermissionsGrid/Index.cshtml b/DNN Platform/Website/Views/ModulePermissionsGrid/Index.cshtml new file mode 100644 index 00000000000..288b22b3293 --- /dev/null +++ b/DNN Platform/Website/Views/ModulePermissionsGrid/Index.cshtml @@ -0,0 +1,132 @@ +@using DotNetNuke.Web.MvcPipeline.Security.Models +@using DotNetNuke.Services.Localization +@using DotNetNuke.MvcPipeline + +@model ModulePermissionsGridViewModel + +
    + + @* Role Groups Filter *@ +
    +
    + + @Html.DropDownList("RoleGroups", new SelectList(Model.RoleGroups, "RoleGroupID", "RoleGroupName"), + new { @class = "roleGroupsDropDown" }) +
    +
    + + @Html.DropDownList("Roles", new SelectList(Model.Roles, "RoleID", "RoleName"), + new { @class = "rolesDropDown" }) + @Localization.GetString("Add") +
    +
    + + @* Roles Grid *@ +
    +
    ").css({padding:0,margin:0}).append(i),r.append(u),u},DDR.Menu.setTableColumns=function(t,i){var u=t.children("tbody");u.length==0&&(u=t);var f=u.children("tr"),e=f.children("td"),r=n("
    + + + + @foreach (var permission in Model.Permissions) + { + + } + + + + + @foreach (var role in Model.RolePermissions) + { + + + + @foreach (var permission in role.Permissions) + { + + } + + + } + +
    @Localization.GetString("PermissionRoleHeader.Text")@permission.PermissionName @Localization.GetString(permission.PermissionName + ".Permission")Actions @Localization.GetString("Actions")
    @role.RoleName + @Html.PermissionTriState(permission.Key, + permission.Value.State, + permission.Value.IsFullControl, + permission.Value.IsView, + permission.Value.Locked, + permission.Value.SupportsDenyMode, + permission.Value.PermissionKey) + + @*@Html.Partial("_PermissionTriState", new PermissionTriStateModel + { + //Permission = permission, + RoleId = role.RoleId + })*@ + @*@permission.Value.State + @permission.Value.Enabled*@ + + +
    + + + @* Users Grid *@ + @if (Model.Users.Count > 0) + { +
    + + + + + @foreach (var permission in Model.Permissions) + { + + } + + + + + @foreach (var user in Model.Users) + { + + + @foreach (var permission in Model.Permissions) + { + + } + + + } + +
    @Localization.GetString("PermissionUserHeader.Text")@Localization.GetString(permission.PermissionName + ".Permission")@Localization.GetString("Actions")
    @user.DisplayName + @Html.Partial("_PermissionTriState", new PermissionTriStateModel + { + Permission = permission, + UserId = user.UserID + }) + + +
    +
    + } + + @* Add User Section *@ +
    + + + + @Localization.GetString("Add") +
    + + +@*@section Scripts { + + }*@ \ No newline at end of file diff --git a/DNN Platform/Website/Views/ModuleSettingsView/Invoke.cshtml b/DNN Platform/Website/Views/ModuleSettingsView/Invoke.cshtml new file mode 100644 index 00000000000..368a6907388 --- /dev/null +++ b/DNN Platform/Website/Views/ModuleSettingsView/Invoke.cshtml @@ -0,0 +1,394 @@ +@using DotNetNuke.Services.Localization +@using System.Web.Mvc +@using System.Linq +@using System.Linq.Expressions +@using DotNetNuke.Web.MvcPipeline.Models +@using DotNetNuke.Web.MvcPipeline.Modules +@using DotNetNuke.Web.MvcPipeline + +@model ModuleSettingsModel + +@{ + var LocalResourceFile = "~/admin/Modules/" + Localization.LocalResourceDirectory + "/ModuleSettings.ascx"; + var controllerName = "ModuleSettings"; + var actionName = "UpdateDefaultSettings"; + if (!string.IsNullOrEmpty(Model.ModuleControllerName)) + { + controllerName = Model.ModuleControllerName; + actionName = "UpdateSettings"; + } +} + +
    + + + @using (Html.BeginForm(actionName, controllerName, new { ModuleId = Model.ModuleId })) + { + @Html.AntiForgeryToken() + @Html.ValidationSummary(true, "", new { @class = "dnnFormMessage dnnFormError" }) + @Html.HiddenFor(model => model.TabId) + @Html.HiddenFor(model => model.ModuleId) + +
    + +
    +

    + @Localization.GetString("GeneralDetails", LocalResourceFile) +

    +
    +
    + @Html.DnnLabelFor(model => model.ModuleId, LocalResourceFile) + @Html.DisplayFor(model => model.ModuleId) +
    +
    + @Html.DnnLabelFor(model => model.CultureCode, LocalResourceFile) + @Model.CultureCode +
    +
    + @Html.DnnLabelFor(model => model.FriendlyName, LocalResourceFile) + @Html.TextBoxFor(model => model.FriendlyName, new { disabled = "disabled", @class = "aspNetDisabled" }) +
    +
    + @Html.DnnLabelFor(model => model.ModuleTitle, LocalResourceFile) + @Html.EditorFor(model => model.ModuleTitle) + @Html.ValidationMessageFor(model => model.ModuleTitle, "", new { @class = "dnnFormMessage dnnFormError" }) +
    +
    + @Html.DnnLabelFor(model => model.Tags, LocalResourceFile) + @Html.EditorFor(model => model.Tags) +
    +
    + +

    + @Localization.GetString("Security", LocalResourceFile) +

    +
    +
    + @Html.DnnLabelFor(model => model.AllTabs, LocalResourceFile) + @Html.EditorFor(model => model.AllTabs) +
    +
    + @Html.DnnLabelFor(model => model.NewTabs, LocalResourceFile) + @Html.EditorFor(model => model.NewTabs) +
    +
    + @Html.DnnLabelFor(model => model.AllowIndex, LocalResourceFile) + @Html.EditorFor(model => model.AllowIndex) +
    +
    + @Html.DnnLabelFor(model => model.IsShareable, LocalResourceFile) + @Html.EditorFor(model => model.IsShareable) +
    +
    + @Html.DnnLabelFor(model => model.IsShareableViewOnly, LocalResourceFile) + @Html.EditorFor(model => model.IsShareableViewOnly) +
    +
    + @Html.DnnLabelFor(model => model.AdminBorder, LocalResourceFile) + @Html.EditorFor(model => model.AdminBorder) +
    +
    + @Html.DnnLabelFor(model => model.Header, LocalResourceFile) + @Html.TextAreaFor(model => model.Header, new { rows = "6" }) +
    +
    + @Html.DnnLabelFor(model => model.Footer, LocalResourceFile) + @Html.TextAreaFor(model => model.Footer, new { rows = "6" }) +
    +
    + @Html.DnnLabelFor(model => model.StartDate, LocalResourceFile) + @Html.TextBoxFor(model => model.StartDate, new { type = "datetime-local" }) +
    +
    + @Html.DnnLabelFor(model => model.EndDate, LocalResourceFile) + @Html.TextBoxFor(model => model.EndDate, new { type = "datetime-local" }) +
    +
    + @Html.DnnLabelFor(model => model.Moniker, LocalResourceFile) + @Html.EditorFor(model => model.Moniker) +
    +
    + +

    + @Localization.GetString("ModuleInstalledOn", LocalResourceFile) +

    +
    +
    +
    + @if (Model.InstalledOnTabs != null && Model.InstalledOnTabs.Any()) + { + + + + + + + + + @foreach (var item in Model.InstalledOnTabs) + { + + + + + } + +
    SitePage
    @item.InstalledOnSite@item.InstalledOnLink
    + } + else + { +
    + +
    + } +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + @Html.Action("Index", "ModulePermissionsGrid", new { tabId = Model.TabId, moduleId = Model.ModuleId, inheritViewPermissionsFromTab = Model.InheritViewPermissions }) +
    + @Html.EditorFor(model => model.InheritViewPermissions) @Html.Raw(Localization.GetString("InheritPermissions", LocalResourceFile)) +
    +
    +
    +
    + +
    + +
    +

    + @Localization.GetString("Appearance", LocalResourceFile) +

    +
    +
    + @Html.DnnLabelFor(model => model.IconFile, LocalResourceFile) + @Html.EditorFor(model => model.IconFile) +
    +
    + @Html.DnnLabelFor(model => model.Alignment, LocalResourceFile) + @Html.DropDownListFor(model => model.Alignment, new SelectList( + new List{ + new { value = "left" , text = "Left" }, + new { value = "center" , text = "Center" }, + new { value = "right" , text = "Right"}, + new { value = "" , text = "Not_Specified"} + }, + "value", + "text", + Model.Alignment + ), new { @class = "dnnFixedSizeComboBox" }) + +
    + @Html.DnnLabelFor(model => model.Color, LocalResourceFile) + @Html.EditorFor(model => model.Color) +
    +
    + @Html.DnnLabelFor(model => model.Border, LocalResourceFile) + @Html.EditorFor(model => model.Border) +
    +
    + @Html.DnnLabelFor(model => model.Visibility, LocalResourceFile) + @Html.EnumDropDownListFor(model => model.Visibility, new { @class = "dnnFixedSizeComboBox" }) +
    +
    + @Html.DnnLabelFor(model => model.DisplayTitle, LocalResourceFile) + @Html.EditorFor(model => model.DisplayTitle) +
    +
    + @Html.DnnLabelFor(model => model.DisplayPrint, LocalResourceFile) + @Html.EditorFor(model => model.DisplayPrint) +
    +
    + @Html.DnnLabelFor(model => model.DisplaySyndicate, LocalResourceFile) + @Html.EditorFor(model => model.DisplaySyndicate) +
    + @*
    + @Html.DnnLabelFor(model => model.IsWebSlice, LocalResourceFile) + @Html.EditorFor(model => model.IsWebSlice) +
    +
    + @Html.DnnLabelFor(model => model.WebSliceTitle, LocalResourceFile) + @Html.EditorFor(model => model.WebSliceTitle) +
    +
    + @Html.DnnLabelFor(model => model.WebSliceExpiryDate, LocalResourceFile) + @Html.EditorFor(model => model.WebSliceExpiryDate) +
    +
    + @Html.DnnLabelFor(model => model.WebSliceTTL, LocalResourceFile) + @Html.EditorFor(model => model.WebSliceTTL) +
    *@ +
    + @Html.DnnLabelFor(model => model.ContainerSrc, LocalResourceFile) + @Html.DropDownListFor(model => model.ContainerSrc, new SelectList(Model.ContainerOptions, "Value", "Text"), "<" + Localization.GetString("None_Specified", LocalResourceFile) + ">", new { @class = "dnnFixedSizeComboBox" }) + @Localization.GetString("ContainerPreview", LocalResourceFile) +
    + + +

    + @Localization.GetString("CacheSettings", LocalResourceFile) +

    +
    +
    + @Html.DnnLabelFor(model => model.CacheProvider, LocalResourceFile) + @Html.DropDownListFor(model => model.CacheProvider, new SelectList(new[] { "None" }@*.Concat(Model.CacheProvider != null ? new[] { Model.CacheProvider } : Enumerable.Empty()*@), "None"), new { class = "dnnFixedSizeComboBox" }) + @if (Model.CacheInheritedVisible) + { + @Localization.GetString("CacheInherited", LocalResourceFile) + } +
    +
    + @if (Model.CacheWarningVisible) + { +
    + @Localization.GetString("CacheDurationWarning", LocalResourceFile) +
    + } + @Html.DnnLabelFor(model => model.CacheTime, LocalResourceFile) + @Html.EditorFor(model => model.CacheTime, new { @class = "msCacheDuration" }) +
    +
    + +

    + + @Localization.GetString("OtherSettings", LocalResourceFile) + +

    +
    +
    + @Html.DnnLabelFor(model => model.IsDefaultModule, LocalResourceFile) + @Html.EditorFor(model => model.IsDefaultModule) +
    +
    + @Html.DnnLabelFor(model => model.AllModules, LocalResourceFile) + @Html.EditorFor(model => model.AllModules) +
    +
    + @Html.DnnLabelFor(model => model.TabId, LocalResourceFile) + @Html.DropDownListFor(model => model.TabId, new SelectList(Model.AvailableTabs, "Id", "Name"), new { @class = "dnnFixedSizeComboBox" }) +
    +
    + + + + if (!string.IsNullOrEmpty(Model.ModuleActionName)) + { +
    +
    +
    + @Html.Control(Model.ModuleControlSrc, Model.ModuleId) +
    +
    +
    + } + + } + +
    + @*@Html.Action("AuditControl", "ModuleSettings", new { moduleId = Model.ModuleId })*@ + + @*
    + + @Model.CreatedByUserName +
    +
    + + @Model.CreatedOnDate.ToString("g") +
    +
    + + @Model.LastModifiedByUserName +
    +
    + + @Model.LastModifiedOnDate.ToString("g") +
    *@ +
    + + + diff --git a/DNN Platform/Website/Views/PersonaBarContainer/Index.cshtml b/DNN Platform/Website/Views/PersonaBarContainer/Index.cshtml new file mode 100644 index 00000000000..8071a467bbe --- /dev/null +++ b/DNN Platform/Website/Views/PersonaBarContainer/Index.cshtml @@ -0,0 +1,44 @@ +@using DotNetNuke.Web.MvcPipeline +@model Dnn.PersonaBar.UI.Controllers.PersonaBarContainerModel +@{} + +
    +
    + +
    + + +
    diff --git a/DNN Platform/Website/Views/PrivacyView/Invoke.cshtml b/DNN Platform/Website/Views/PrivacyView/Invoke.cshtml new file mode 100644 index 00000000000..ed9bfb3556d --- /dev/null +++ b/DNN Platform/Website/Views/PrivacyView/Invoke.cshtml @@ -0,0 +1,5 @@ +@model string + +
    + @Html.Raw(Model) +
    \ No newline at end of file diff --git a/DNN Platform/Website/Views/Shared/_Layout.cshtml b/DNN Platform/Website/Views/Shared/_Layout.cshtml new file mode 100644 index 00000000000..a9d4a219b7f --- /dev/null +++ b/DNN Platform/Website/Views/Shared/_Layout.cshtml @@ -0,0 +1,13 @@ +@using DotNetNuke.Web.Mvc.Helpers + + +@Html.Login() + + +@Html.Login( + text: "Se connecter", + cssClass: "custom-login", + logoffText: "Se déconnecter", + legacyMode: false, + showInErrorPage: true +) \ No newline at end of file diff --git a/DNN Platform/Website/Views/Shared/_PermissionTriState.cshtml b/DNN Platform/Website/Views/Shared/_PermissionTriState.cshtml new file mode 100644 index 00000000000..94dddc46d7c --- /dev/null +++ b/DNN Platform/Website/Views/Shared/_PermissionTriState.cshtml @@ -0,0 +1,8 @@ +@using DotNetNuke.Security.Permissions.Models +@model PermissionTriStateModel + + \ No newline at end of file diff --git a/DNN Platform/Website/Views/TermsView/Invoke.cshtml b/DNN Platform/Website/Views/TermsView/Invoke.cshtml new file mode 100644 index 00000000000..7aeb1009b11 --- /dev/null +++ b/DNN Platform/Website/Views/TermsView/Invoke.cshtml @@ -0,0 +1,5 @@ +@model string +@{ + +} +
    @Html.Raw(Model)
    diff --git a/DNN Platform/Website/Views/web.config b/DNN Platform/Website/Views/web.config new file mode 100644 index 00000000000..b9c29a8fc27 --- /dev/null +++ b/DNN Platform/Website/Views/web.config @@ -0,0 +1,33 @@ + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DNN Platform/Website/js/mvc.js b/DNN Platform/Website/js/mvc.js new file mode 100644 index 00000000000..c569d7c0bd1 --- /dev/null +++ b/DNN Platform/Website/js/mvc.js @@ -0,0 +1,181 @@ +// Enhanced registerNamespace implementation +const registerNamespace = (function () { + // Private function to handle namespace creation + function createNamespace(namespace) { + const parts = namespace.split('.'); + let current = window || global; + + for (let i = 0; i < parts.length; i++) { + // Create the namespace if it doesn't exist + current[parts[i]] = current[parts[i]] || {}; + current = current[parts[i]]; + } + + return current; + } + + // The main function with additional functionality + function registerNS(namespace, initialValue) { + const ns = createNamespace(namespace); + + // If an initial value is provided, extend the namespace + if (initialValue !== undefined) { + Object.assign(ns, initialValue); + } + + return ns; + } + + return registerNS; +})(); + +// More comprehensive implementation +function registerClass(config) { + const { + typeName, + baseType, + interfaceTypes = [], + constructor + } = config; + + // Create the class + function Type(...args) { + // Interface contract checking + interfaceTypes.forEach(interfaceType => { + for (let method in interfaceType.prototype) { + if (typeof interfaceType.prototype[method] === 'function' + && !this[method]) { + throw new Error(`Must implement interface method: ${method}`); + } + } + }); + + // Base type constructor + if (baseType) { + baseType.apply(this, args); + } + + // Own constructor + if (constructor) { + constructor.apply(this, args); + } + } + + // Inheritance + if (baseType) { + Type.prototype = Object.create(baseType.prototype); + Type.prototype.constructor = Type; + } + + // Interface implementation + interfaceTypes.forEach(interfaceType => { + Object.assign(Type.prototype, interfaceType.prototype); + }); + + return Type; +} + +if (!window) this.window = this; + +registerNamespace('Type'); + +Type.registerNamespace = registerNamespace; + +Function.prototype.registerClass = registerClass; + +Type.registerNamespace('Sys'); + +Sys.Browser = {}; + +Sys.Browser.InternetExplorer = {}; +Sys.Browser.Firefox = {}; +Sys.Browser.Safari = {}; +Sys.Browser.Opera = {}; + +Sys.Browser.agent = null; +Sys.Browser.hasDebuggerStatement = false; +Sys.Browser.name = navigator.appName; +Sys.Browser.version = parseFloat(navigator.appVersion); + +if (navigator.userAgent.indexOf(' MSIE ') > -1) { + Sys.Browser.agent = Sys.Browser.InternetExplorer; + Sys.Browser.version = parseFloat(navigator.userAgent.match(/MSIE (\d+\.\d+)/)[1]); + Sys.Browser.hasDebuggerStatement = true; +} +else if (navigator.userAgent.indexOf(' Firefox/') > -1) { + Sys.Browser.agent = Sys.Browser.Firefox; + Sys.Browser.version = parseFloat(navigator.userAgent.match(/ Firefox\/(\d+\.\d+)/)[1]); + Sys.Browser.name = 'Firefox'; + Sys.Browser.hasDebuggerStatement = true; +} +else if (navigator.userAgent.indexOf(' Safari/') > -1) { + Sys.Browser.agent = Sys.Browser.Safari; + Sys.Browser.version = parseFloat(navigator.userAgent.match(/ Safari\/(\d+\.\d+)/)[1]); + Sys.Browser.name = 'Safari'; +} +else if (navigator.userAgent.indexOf('Opera/') > -1) { + Sys.Browser.agent = Sys.Browser.Opera; +} + + +Sys.WebForms = {}; +Sys.WebForms.PageRequestManager = { + getInstance: function () { + return { + add_beginRequest: function (par) { }, + add_endRequest: function (par) { } + } + } +}; + +Sys.Application = { + add_load: function () { + + } +} + +window.$find = function (id) { + throw new Error('Not implemented'); // for making popups working +} + +window.$get = function (id, element) { + + if (!element) return document.getElementById(id); + if (element.getElementById) return element.getElementById(id); + + var nodeQueue = []; + var childNodes = element.childNodes; + for (var i = 0; i < childNodes.length; i++) { + var node = childNodes[i]; + if (node.nodeType == 1) { + nodeQueue[nodeQueue.length] = node; + } + } + + while (nodeQueue.length) { + node = nodeQueue.shift(); + if (node.id == id) { + return node; + } + childNodes = node.childNodes; + for (i = 0; i < childNodes.length; i++) { + node = childNodes[i]; + if (node.nodeType == 1) { + nodeQueue[nodeQueue.length] = node; + } + } + } + + return null; +} + +Sys.Serialization = { + JavaScriptSerializer: { + serialize : function (object) { + return JSON.stringify(object); + }, + deserialize : function (data) { + return JSON.parse(data); + } + } +} \ No newline at end of file diff --git a/DNN_Platform.sln b/DNN_Platform.sln index 5e71dc5999c..6567cde0186 100644 --- a/DNN_Platform.sln +++ b/DNN_Platform.sln @@ -695,6 +695,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Web.MvcPipeline" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Modules.NewDDRMenu", "DNN Platform\Modules\NewDDRMenu\DotNetNuke.Modules.NewDDRMenu.csproj", "{4366908E-2852-40EB-8675-610E0DA2AB5D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetNuke.Web.MvcWebsite", "DNN Platform\DotNetNuke.Web.MvcWebsite\DotNetNuke.Web.MvcWebsite.csproj", "{78CA212D-B471-A777-9C3D-B4E43280181E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Cloud_Debug|Any CPU = Cloud_Debug|Any CPU @@ -2487,6 +2489,30 @@ Global {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|Any CPU.Build.0 = Release|Any CPU {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|x86.ActiveCfg = Release|Any CPU {4366908E-2852-40EB-8675-610E0DA2AB5D}.Release-Net45|x86.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Debug|Any CPU.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Debug|Any CPU.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Debug|x86.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Debug|x86.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Release|Any CPU.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Release|Any CPU.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Release|x86.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Cloud_Release|x86.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug|x86.ActiveCfg = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug|x86.Build.0 = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug-Net45|Any CPU.ActiveCfg = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug-Net45|Any CPU.Build.0 = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug-Net45|x86.ActiveCfg = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Debug-Net45|x86.Build.0 = Debug|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release|Any CPU.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release|x86.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release|x86.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release-Net45|Any CPU.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release-Net45|Any CPU.Build.0 = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release-Net45|x86.ActiveCfg = Release|Any CPU + {78CA212D-B471-A777-9C3D-B4E43280181E}.Release-Net45|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2633,6 +2659,7 @@ Global {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} {4366908E-2852-40EB-8675-610E0DA2AB5D} = {FDDC95FE-3341-4AED-A93E-7A5DF85A55C2} + {78CA212D-B471-A777-9C3D-B4E43280181E} = {1DFA65CE-5978-49F9-83BA-CFBD0C7A1814} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46B6A641-57EB-4B19-B199-23E6FC2AB40B} diff --git a/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Dnn.EditBar.UI.csproj b/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Dnn.EditBar.UI.csproj index 0e72b165eea..bbdb9566c1c 100644 --- a/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Dnn.EditBar.UI.csproj +++ b/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Dnn.EditBar.UI.csproj @@ -105,6 +105,7 @@ + Code @@ -240,6 +241,10 @@ {03e3afa5-ddc9-48fb-a839-ad4282ce237e} DotNetNuke.Web.Client + + {aa3ee19b-81a0-3766-e8f4-424c2425a8d3} + DotNetNuke.Web.MvcPipeline + {4912f062-f8a8-4f9d-8f8e-244ebee1acbd} DotNetNuke.WebUtility @@ -308,12 +313,17 @@ ..\..\..\packages\Microsoft.AspNet.WebApi.Core.5.3.0\lib\net45\System.Web.Http.dll + + False + ..\..\..\Packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + + diff --git a/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Mvc/MvcContentEditorManager.cs b/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Mvc/MvcContentEditorManager.cs new file mode 100644 index 00000000000..f6fc03ced14 --- /dev/null +++ b/Dnn.AdminExperience/EditBar/Dnn.EditBar.UI/Mvc/MvcContentEditorManager.cs @@ -0,0 +1,745 @@ +// 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 Dnn.EditBar.UI.Mvc +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Web; + using System.Web.Mvc; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + + using Dnn.EditBar.UI.Controllers; + using DotNetNuke.Collections; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Definitions; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI; + using DotNetNuke.UI.Containers; + using DotNetNuke.UI.Skins; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.UI.Utilities; + using DotNetNuke.Web.UI.WebControls; + using Newtonsoft.Json; + + using Globals = DotNetNuke.Common.Globals; + + /// Content Editor Manager. + public class MvcContentEditorManager + { + public const string ControlFolder = "~/DesktopModules/admin/Dnn.EditBar/Resources"; + private const int CssFileOrder = 40; + + private bool supportAjax = true; + + public ControllerContext Context { get; set; } + + public Skin Skin { get; set; } + + public bool IsHostMenu + { + get + { + return Globals.IsHostTab(this.PortalSettings.ActiveTab.TabID); + } + } + + public PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + private string LocalResourcesFile + { + get { return Path.Combine(ControlFolder, "ContentEditorManager/App_LocalResources/SharedResources.resx"); } + } + + private bool SupportAjax + { + get + { + return /*ScriptManager.GetCurrent(this.Page) != null &&*/ this.supportAjax; + } + + set + { + this.supportAjax = value; + } + } + + public static void CreateManager(Controller controller) + { + if (Host.DisableEditBar) + { + return; + } + + var request = controller.Request; + var isSpecialPageMode = request.QueryString["dnnprintmode"] == "true" || request.QueryString["popUp"] == "true"; + if (isSpecialPageMode + || Globals.IsAdminControl()) + { + return; + } + + if (!Globals.IsAdminControl()) + { + if (PortalSettings.Current.UserId > 0) + { + var manager = new MvcContentEditorManager(); + manager.Context = controller.ControllerContext; + if (manager.OnInit()) + { + manager.OnPreRender(); + } + } + } + } + + public static bool HasTabPermission(string permissionKey) + { + var principal = Thread.CurrentPrincipal; + if (!principal.Identity.IsAuthenticated) + { + return false; + } + + var currentPortal = PortalController.Instance.GetCurrentPortalSettings(); + + bool isAdminUser = currentPortal.UserInfo.IsSuperUser || PortalSecurity.IsInRole(currentPortal.AdministratorRoleName); + if (isAdminUser) + { + return true; + } + + return TabPermissionController.HasTabPermission(permissionKey); + } + + /* + internal static MvcContentEditorManager GetCurrent(Page page) + { + if (page.Items.Contains("ContentEditorManager")) + { + return page.Items["ContentEditorManager"] as ContentEditorManager; + } + + return null; + } + */ + + protected bool OnInit() + { + /* + if (GetCurrent(this.Page) != null) + { + throw new Exception("Instance has already initialized"); + } + */ + this.AutoSetUserMode(); + + var user = this.PortalSettings.UserInfo; + + if (user.UserID > 0) + { + MvcClientAPI.RegisterClientVariable("dnn_current_userid", this.PortalSettings.UserInfo.UserID.ToString(), true); + } + + if (Personalization.GetUserMode() != PortalSettings.Mode.Edit + || !this.IsPageEditor() + || Controllers.EditBarController.Instance.GetMenuItems().Count == 0) + { + // this.Parent.Controls.Remove(this); + return false; + } + + this.RegisterClientResources(); + + this.RegisterEditBarResources(); + + // this.Page.Items.Add("ContentEditorManager", this); + + // if there is pending work cookie, then reset it + this.CheckPendingData(); + + // if there is callback data cookie, then process the module for drag. + this.CheckCallbackData(); + + // this.EnsureChildControls(); + return true; + } + + protected void OnPreRender() + { + // base.OnPreRender(e); + this.RemoveEmptyPaneClass(); + this.RegisterInitScripts(); + } + + protected void CreateChildControls() + { + /* + base.CreateChildControls(); + foreach (string paneId in this.PortalSettings.ActiveTab.Panes) + { + var pane = this.Skin.FindControl(paneId) as HtmlContainerControl; + if (pane == null) + { + continue; + } + + // create update panel + var updatePanel = new UpdatePanel + { + UpdateMode = UpdatePanelUpdateMode.Conditional, + ID = pane.ID + "_SyncPanel", + ChildrenAsTriggers = true, + }; + + try + { + // find update panels in pane and fire the unload event for a known issue: CONTENT-4039 + + var updatePanels = this.GetUpdatePanelsInPane(pane); + + updatePanels.ForEach(p => p.Unload += this.UpdatePanelUnloadEvent); + updatePanel.Unload += this.UpdatePanelUnloadEvent; + + var paneIndex = pane.Parent.Controls.IndexOf(pane); + pane.Parent.Controls.AddAt(paneIndex, updatePanel); + + var templateContainer = updatePanel.ContentTemplateContainer; + templateContainer.Controls.Add(pane); + catch (Exception) + { + // this.SupportAjax = false; + return; + } + + updatePanel.Attributes.Add("class", $"DnnAjaxPanel {pane.Attributes["class"]}"); + pane.Attributes["class"] = string.Empty; + + var scriptManager = ScriptManager.GetCurrent(this.Page); + if (scriptManager != null && scriptManager.IsInAsyncPostBack + && updatePanel.ClientID == this.Request.Form["__EVENTTARGET"] + && !string.IsNullOrEmpty(this.Request.Form["__EVENTARGUMENT"]) + && this.Request.Form["__EVENTARGUMENT"].ToLowerInvariant() != "undefined" + && this.Request.Form["__EVENTARGUMENT"].ToLowerInvariant().StartsWith("module-")) + { + var moduleId = Convert.ToInt32(this.Request.Form["__EVENTARGUMENT"].Substring(7)); + + var moduleContainer = this.FindModuleContainer(moduleId); + if (moduleContainer != null) + { + var moduleControl = this.FindModuleControl(moduleId); + var moduleInfo = this.FindModuleInfo(moduleId); + + if (moduleControl != null && moduleInfo != null && moduleContainer.Parent is HtmlContainerControl) + { + ((HtmlContainerControl)moduleContainer.Parent).Attributes["data-module-title"] = moduleInfo.ModuleTitle; + + if (this.HaveContentLayoutModuleOnPage()) + { + this.Page.Items[typeof(ProxyPage)] = moduleControl; + } + else + { + moduleControl.Page = new ProxyPage(this.Page); + } + + this.ProcessDragTipShown(moduleContainer); + } + } + } + } + */ + } + + protected void Render(HtmlTextWriter writer) + { + /* + var scripts = ScriptManager.GetCurrent(this.Page).GetRegisteredStartupScripts() + .Where(s => s.Control is ProxyPage).ToList(); + foreach (var script in scripts) + { + ScriptManager.RegisterStartupScript(this.Page, script.Type, script.Key, script.Script, script.AddScriptTags); + } + + base.Render(writer); + */ + } + + private void RegisterClientResources() + { + ClientResourceManager.EnableAsyncPostBackHandler(); + + // register drop down list required resources + MvcClientResourceManager.RegisterStyleSheet(this.Context, "~/Resources/Shared/components/DropDownList/dnn.DropDownList.css", FileOrder.Css.ResourceCss); + MvcClientResourceManager.RegisterStyleSheet(this.Context, "~/Resources/Shared/scripts/jquery/dnn.jScrollBar.css", FileOrder.Css.ResourceCss); + + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/dnn.extensions.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/dnn.jquery.extensions.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/dnn.DataStructures.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/jquery/jquery.mousewheel.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/jquery/dnn.jScrollBar.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/TreeView/dnn.TreeView.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/scripts/TreeView/dnn.DynamicTreeView.js"); + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/Components/DropDownList/dnn.DropDownList.js"); + + MvcClientResourceManager.RegisterScript(this.Context, Path.Combine(ControlFolder, "ContentEditorManager/Js/ModuleManager.js")); + MvcClientResourceManager.RegisterScript(this.Context, Path.Combine(ControlFolder, "ContentEditorManager/Js/ModuleDialog.js")); + MvcClientResourceManager.RegisterScript(this.Context, Path.Combine(ControlFolder, "ContentEditorManager/Js/ExistingModuleDialog.js")); + MvcClientResourceManager.RegisterScript(this.Context, Path.Combine(ControlFolder, "ContentEditorManager/Js/ModuleService.js")); + MvcClientResourceManager.RegisterScript(this.Context, Path.Combine(ControlFolder, "ContentEditorManager/Js/ContentEditor.js")); + MvcClientResourceManager.RegisterStyleSheet( + this.Context, + Path.Combine(ControlFolder, "ContentEditorManager/Styles/ContentEditor.css"), + CssFileOrder); + ServicesFramework.Instance.RequestAjaxScriptSupport(); + + JavaScript.RequestRegistration(CommonJs.DnnPlugins); + + // We need to add the Dnn JQuery plugins because the Edit Bar removes the Control Panel from the page + JavaScript.RequestRegistration(CommonJs.KnockoutMapping); + + MvcClientResourceManager.RegisterScript(this.Context, "~/Resources/Shared/Components/Tokeninput/jquery.tokeninput.js"); + MvcClientResourceManager.RegisterStyleSheet( + this.Context, + "~/Resources/Shared/Components/Tokeninput/Themes/token-input-facebook.css"); + } + + private void RegisterEditBarResources() + { + JavaScript.RequestRegistration(CommonJs.jQuery); + ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); + + MvcClientAPI.RegisterClientVariable("editbar_isAdmin", this.IsAdmin().ToString(), true); + + var settings = EditBarController.Instance.GetConfigurations(this.PortalSettings.PortalId); + var settingsScript = "window.editBarSettings = " + JsonConvert.SerializeObject(settings) + ";"; + + // this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "EditBarSettings", settingsScript, true); + MvcClientAPI.RegisterStartupScript("EditBarSettings", settingsScript); + MvcClientResourceManager.RegisterScript(this.Context, "~/DesktopModules/admin/Dnn.EditBar/scripts/editBarContainer.js"); + + MvcClientResourceManager.RegisterStyleSheet(this.Context, "~/DesktopModules/admin/Dnn.EditBar/css/editBarContainer.css"); + } + + private bool IsPageEditor() + { + return HasTabPermission("EDIT"); + } + + private IEnumerable> GetPaneClientIdCollection() + { + var panelClientIds = new List>(this.PortalSettings.ActiveTab.Panes.Count); + + try + { + // var skinControl = this.Page.FindControl("SkinPlaceHolder").Controls[0]; + foreach (var pane in this.PortalSettings.ActiveTab.Panes.Cast()) + { + var foundControls = new List(); + + // FindControlRecursive(skinControl, pane, foundControls); + panelClientIds.Add((from control in foundControls select control.ClientID).ToList()); + } + } + catch (Exception ex) + { + Exceptions.LogException(ex); + } + + return panelClientIds; + } + + /// + /// remove default empty pane class as some skin have special style on it + /// and it may caught style issues with content editor feature, then we use a + /// new style to cover it. + /// + private void RemoveEmptyPaneClass() + { + foreach (string paneId in this.PortalSettings.ActiveTab.Panes) + { + /* + var paneControl = this.Skin.FindControl(paneId) as HtmlContainerControl; + if (paneControl != null + && !string.IsNullOrEmpty(paneControl.Attributes["class"]) + && paneControl.Attributes["class"].Contains("DNNEmptyPane")) + { + paneControl.Attributes["class"] = $"{paneControl.Attributes["class"]} EditBarEmptyPane"; + } + */ + } + } + + private void RegisterInitScripts() + { + this.RegisterLocalResources(); + + MvcClientAPI.RegisterClientVariable("cem_loginurl", Globals.LoginURL(HttpContext.Current.Request.RawUrl, false), true); + var panes = string.Join(",", this.PortalSettings.ActiveTab.Panes.Cast()); + var panesClientIds = this.GetPanesClientIds(this.GetPaneClientIdCollection()); + const string scriptFormat = @"dnn.ContentEditorManager.init({{type: 'moduleManager', panes: dnn.panes.join(','), panesClientIds: dnn.panesClientIds.join(';'), supportAjax: {1}}});"; + var script = string.Format( + scriptFormat, + panes, + this.SupportAjax ? "true" : "false", + panesClientIds); + + /* + if (ScriptManager.GetCurrent(this.Page) != null) + { + // respect MS AJAX + ScriptManager.RegisterStartupScript(this.Page, this.GetType(), "ContentEditorManager", script, true); + } + else + { + this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ContentEditorManager", script, true); + } + */ + + MvcClientAPI.RegisterStartupScript("ContentEditorManager", script); + } + + private string GetPanesClientIds(IEnumerable> panelCliendIdCollection) + { + return string.Join(";", panelCliendIdCollection.Select(x => string.Join(",", x))); + } + + private void RegisterLocalResources() + { + const string scriptFormat = @"dnn.ContentEditorManagerResources = {{ + title: '{0}', + nomodules: '{1}', + dragtip: '{2}', + pendingsave: '{3}', + confirmTitle: '{4}', + confirmYes: '{5}', + confirmNo: '{6}', + cancelConfirm: '{7}', + deleteModuleConfirm: '{8}', + cancel: '{9}', + searchPlaceHolder: '{10}', + categoryRecommended: '{11}', + categoryAll: '{12}', + pagePicker_clearButtonTooltip: '{13}', + pagePicker_loadingResultText: '{14}', + pagePicker_resultsText: '{15}', + pagePicker_searchButtonTooltip: '{16}', + pagePicker_searchInputPlaceHolder: '{17}', + pagePicker_selectedItemCollapseTooltip: '{18}', + pagePicker_selectedItemExpandTooltip: '{19}', + pagePicker_selectItemDefaultText: '{20}', + pagePicker_sortAscendingButtonTitle: '{21}', + pagePicker_sortAscendingButtonTooltip: '{22}', + pagePicker_sortDescendingButtonTooltip: '{23}', + pagePicker_unsortedOrderButtonTooltip: '{24}', + site: '{25}', + page: '{26}', + addExistingModule: '{27}', + makeCopy: '{28}' + }};"; + + var script = string.Format( + scriptFormat, + Localization.GetSafeJSString("AddModule.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("NoModules.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("DragTip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("PendingSave.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("ConfirmTitle.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("ConfirmYes.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("ConfirmNo.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("CancelConfirm.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("DeleteModuleConfirm.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("Cancel.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("SearchPlaceHolder.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("Category_Recommended.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("Category_All.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_clearButtonTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_loadingResultText.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_resultsText.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_searchButtonTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_searchInputPlaceHolder.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_selectedItemCollapseTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_selectedItemExpandTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_selectItemDefaultText.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_sortAscendingButtonTitle.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_sortAscendingButtonTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_sortDescendingButtonTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("pagePicker_unsortedOrderButtonTooltip.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("Site.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("Page.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("AddExistingModule.Text", this.LocalResourcesFile), + Localization.GetSafeJSString("MakeCopy.Text", this.LocalResourcesFile)); + /* + if (ScriptManager.GetCurrent(this.Page) != null) + { + // respect MS AJAX + ScriptManager.RegisterStartupScript(this.Page, this.GetType(), "ContentEditorManagerResources", script, true); + } + else + { + this.Page.ClientScript.RegisterStartupScript(this.GetType(), "ContentEditorManagerResources", script, true); + } + */ + + MvcClientAPI.RegisterStartupScript("ContentEditorManagerResources", script); + } + + private void CheckPendingData() + { + /* + if (this.Request.Cookies["cem_pending"] != null) + { + var cookie = this.Request.Cookies["cem_pending"]; + var pendingData = cookie.Value; + if (!string.IsNullOrEmpty(pendingData)) + { + var tabId = this.PortalSettings.ActiveTab.TabID; + int moduleId; + if (pendingData.StartsWith("module-") + && int.TryParse(pendingData.Substring(7), out moduleId)) + { + var module = ModuleController.Instance.GetModule(moduleId, tabId, false); + if (module != null) + { + this.RemoveTabModule(tabId, moduleId); + + // remove related modules + ModuleController.Instance.GetTabModules(tabId).Values + .Where(m => m.CreatedOnDate > module.CreatedOnDate && m.CreatedByUserID == module.CreatedByUserID) + .ForEach(m => + { + this.RemoveTabModule(tabId, m.ModuleID); + }); + } + } + } + + cookie.Expires = DateTime.Now.AddDays(-1); + this.Response.Cookies.Add(cookie); + } + */ + } + + private void RemoveTabModule(int tabId, int moduleId) + { + ModuleController.Instance.DeleteTabModule(tabId, moduleId, false); + /* + // remove that module control + var moduleControl = ControlUtilities.FindFirstDescendent( + this.Skin, + c => c.ID == "ctr" + moduleId); + + if (moduleControl != null) + { + moduleControl.Parent.Parent.Controls.Remove(moduleControl.Parent); + } + */ + } + + private void CheckCallbackData() + { + /* + if (this.Request.Cookies["CEM_CallbackData"] != null) + { + var cookie = this.Request.Cookies["CEM_CallbackData"]; + var callbackData = cookie.Value; + if (!string.IsNullOrEmpty(callbackData) && callbackData.StartsWith("module-")) + { + var moduleId = Convert.ToInt32(callbackData.Substring(7)); + + var moduleContainer = this.FindModuleContainer(moduleId); + var moduleInfo = this.FindModuleInfo(moduleId); + if (moduleContainer != null && moduleInfo != null && moduleContainer.Parent is HtmlContainerControl) + { + ((HtmlContainerControl)moduleContainer.Parent).Attributes["data-module-title"] = moduleInfo.ModuleTitle; + this.ProcessDragTipShown(moduleContainer); + } + } + } + */ + } + + /* + private void ProcessDragTipShown(Container moduleContainer) + { + var dragTipShown = Convert.ToString(Personalization.GetProfile("Usability", "DragTipShown" + this.PortalSettings.PortalId)); + if (string.IsNullOrEmpty(dragTipShown) && moduleContainer.Parent is HtmlContainerControl && this.Request.Cookies["noFloat"] == null) + { + Personalization.SetProfile("Usability", "DragTipShown" + this.PortalSettings.PortalId, "true"); + ((HtmlContainerControl)moduleContainer.Parent).Attributes["class"] += " dragtip"; + } + } + + private Container FindModuleContainer(int moduleId) + { + return ControlUtilities.FindFirstDescendent(this.Skin, c => c.ID == "ctr" + moduleId); + } + + private Control FindModuleControl(int moduleId) + { + var moduleContainer = this.FindModuleContainer(moduleId); + if (moduleContainer != null) + { + var moduleInfo = this.FindModuleInfo(moduleId); + if (moduleInfo != null) + { + var controlId = Path.GetFileNameWithoutExtension(moduleInfo.ModuleControl.ControlSrc); + return ControlUtilities.FindFirstDescendent(moduleContainer, c => c.ID == controlId); + } + } + + return null; + } + */ + private ModuleInfo FindModuleInfo(int moduleId) + { + return this.PortalSettings.ActiveTab.Modules.Cast() + .FirstOrDefault(m => m.ModuleID == moduleId); + } + + /* + private List GetUpdatePanelsInPane(Control parent) + { + var panels = new List(); + if (parent is UpdatePanel) + { + panels.Add(parent as UpdatePanel); + } + else if (parent != null && !this.IsListControl(parent)) + { + foreach (Control childControl in parent.Controls) + { + panels.AddRange(this.GetUpdatePanelsInPane(childControl)); + } + } + + return panels; + } + + private bool IsListControl(Control control) + { + return control is DataBoundControl || control is Repeater || control is DataGrid; + } + + private void UpdatePanelUnloadEvent(object sender, EventArgs e) + { + try + { + var methodInfo = typeof(ScriptManager).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(i => i.Name.Equals("System.Web.UI.IScriptManagerInternal.RegisterUpdatePanel")); + methodInfo.Invoke( + ScriptManager.GetCurrent(this.Page), + new[] { sender }); + } + catch (Exception ex) + { + Exceptions.LogException(ex); + } + } + */ + private bool HaveContentLayoutModuleOnPage() + { + var moduleDefinition = + ModuleDefinitionController.GetModuleDefinitions().Values + .FirstOrDefault(m => m.DefinitionName == "Content Layout"); + if (moduleDefinition != null) + { + return this.PortalSettings.ActiveTab.Modules.Cast().Any(m => m.ModuleDefID == moduleDefinition.ModuleDefID); + } + + return false; + } + + /* + private void SetLastPageHistory(string pageId) + { + this.Response.Cookies.Add(new HttpCookie("LastPageId", pageId) { Path = !string.IsNullOrEmpty(Globals.ApplicationPath) ? Globals.ApplicationPath : "/" }); + } + + private string GetLastPageHistory() + { + var cookie = this.Request.Cookies["LastPageId"]; + if (cookie != null) + { + return cookie.Value; + } + + return "NEW"; + } + */ + private void SetUserMode(string userMode) + { + Personalization.SetProfile("Usability", "UserMode" + this.PortalSettings.PortalId, userMode.ToUpper()); + } + + private void AutoSetUserMode() + { + int tabId = this.PortalSettings.ActiveTab.TabID; + int portalId = PortalSettings.Current.PortalId; + string pageId = string.Format("{0}:{1}", portalId, tabId); + /* + HttpCookie cookie = this.Request.Cookies["StayInEditMode"]; + if (cookie != null && cookie.Value == "YES") + { + if (Personalization.GetUserMode() != PortalSettings.Mode.Edit) + { + this.SetUserMode("EDIT"); + this.SetLastPageHistory(pageId); + this.Response.Redirect(this.Request.RawUrl, true); + } + + return; + } + + string lastPageId = this.GetLastPageHistory(); + var isShowAsCustomError = this.Request.QueryString.AllKeys.Contains("aspxerrorpath"); + + if (lastPageId != pageId && !isShowAsCustomError) + { + // navigate between pages + if (Personalization.GetUserMode() != PortalSettings.Mode.View) + { + this.SetUserMode("VIEW"); + this.SetLastPageHistory(pageId); + this.Response.Redirect(this.Request.RawUrl, true); + } + } + + if (!isShowAsCustomError) + { + this.SetLastPageHistory(pageId); + } + */ + } + + private bool IsAdmin() + { + var user = this.PortalSettings.UserInfo; + return user.IsSuperUser || PortalSecurity.IsInRole(this.PortalSettings.AdministratorRoleName); + } + } +} diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/Dnn.PersonaBar.UI.csproj b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/Dnn.PersonaBar.UI.csproj index 58b4a103b23..365704bc4d0 100644 --- a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/Dnn.PersonaBar.UI.csproj +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/Dnn.PersonaBar.UI.csproj @@ -111,6 +111,10 @@ ..\..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.3.0\lib\net45\System.Web.Http.WebHost.dll + + False + ..\..\..\Packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + @@ -123,6 +127,8 @@ + + PersonaBarContainer.ascx ASPXCodeBehind @@ -298,6 +304,10 @@ {03e3afa5-ddc9-48fb-a839-ad4282ce237e} DotNetNuke.Web.Client + + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} + DotNetNuke.Web.MvcPipeline + {4912f062-f8a8-4f9d-8f8e-244ebee1acbd} DotNetNuke.WebUtility diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerController.cs b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerController.cs new file mode 100644 index 00000000000..d75c3c294ec --- /dev/null +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerController.cs @@ -0,0 +1,98 @@ +// 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.Framework.Controllers +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Net.NetworkInformation; + using System.Security.Policy; + using System.Web; + using System.Web.Mvc; + + using Dnn.PersonaBar.Library.Containers; + using Dnn.PersonaBar.Library.Controllers; + using Dnn.PersonaBar.UI.Controllers; + using DotNetNuke.Entities.Host; + using DotNetNuke.Entities.Portals; + using DotNetNuke.Entities.Users; + using DotNetNuke.Framework; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.UI.ControlPanels; + using DotNetNuke.UI.Utilities; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.MvcPipeline.Framework.JavascriptLibraries; + using Newtonsoft.Json; + + using Globals = DotNetNuke.Common.Globals; + + public class PersonaBarContainerController : Controller + { + private readonly IPersonaBarContainer personaBarContainer = Dnn.PersonaBar.Library.Containers.PersonaBarContainer.Instance; + + public string PersonaBarSettings => JsonConvert.SerializeObject(this.personaBarContainer.GetConfiguration()); + + public string AppPath => Globals.ApplicationPath; + + public string BuildNumber => Host.CrmVersion.ToString(CultureInfo.InvariantCulture); + + protected PortalSettings PortalSettings + { + get + { + return PortalController.Instance.GetCurrentPortalSettings(); + } + } + + public ActionResult Index() + { + return this.View(new PersonaBarContainerModel() + { + PersonaBarSettings = this.PersonaBarSettings, + AppPath = this.AppPath, + BuildNumber = this.BuildNumber, + Visible = this.InjectPersonaBar(), + }); + } + + private bool InjectPersonaBar() + { + if (!this.personaBarContainer.Visible) + { + return false; + } + + // copied this logic from DotNetNuke.UI.Skins.Skin.InjectControlPanel + if (this.Request.QueryString["dnnprintmode"] == "true" || this.Request.QueryString["popUp"] == "true") + { + return false; + } + + var menuStructure = PersonaBarController.Instance.GetMenu(this.PortalSettings, UserController.Instance.GetCurrentUserInfo()); + if (menuStructure.MenuItems == null || !menuStructure.MenuItems.Any()) + { + return false; + } + + this.RegisterPersonaBarStyleSheet(); + + MvcJavaScript.RegisterClientReference(this.ControllerContext, ClientAPI.ClientNamespaceReferences.dnn); + MvcJavaScript.RequestRegistration(CommonJs.DnnPlugins); // We need to add the Dnn JQuery plugins because the Edit Bar removes the Control Panel from the page + MvcJavaScript.RequestRegistration(CommonJs.KnockoutMapping); + + // ServicesFramework.Instance.RequestAjaxAntiForgerySupport(); // to later add this line + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/Resources/Shared/Components/Tokeninput/jquery.tokeninput.js"); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Resources/Shared/Components/Tokeninput/Themes/token-input-facebook.css"); + + return true; + } + + private void RegisterPersonaBarStyleSheet() + { + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/DesktopModules/admin/Dnn.PersonaBar/css/personaBarContainer.css"); + } + } +} diff --git a/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerModel.cs b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerModel.cs new file mode 100644 index 00000000000..0fe33d534c2 --- /dev/null +++ b/Dnn.AdminExperience/Library/Dnn.PersonaBar.UI/admin/personaBar/Mvc/PersonaBarContainerModel.cs @@ -0,0 +1,18 @@ +// 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 Dnn.PersonaBar.UI.Controllers +{ + using System.Web.Mvc; + + public class PersonaBarContainerModel + { + public string PersonaBarSettings { get; internal set; } + + public string BuildNumber { get; internal set; } + + public string AppPath { get; internal set; } + + public bool Visible { get; internal set; } + } +} From c91f68a98b9441b351690567e91fb1abcaa1e953 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Thu, 3 Apr 2025 17:37:55 +0200 Subject: [PATCH 024/146] first part of the html module : view control --- .../HTML/Controllers/DNN_HTMLController.cs | 388 ++++++++++++++++++ .../Controllers/HTMLEditHTMLViewController.cs | 137 +++++++ .../HTMLHtmlModuleViewController.cs | 107 +++++ .../Controllers/HTMLMyWorkViewController.cs | 64 +++ .../Controllers/HTMLSettingsViewController.cs | 75 ++++ .../HTML/DotNetNuke.Modules.Html.csproj | 17 + .../Modules/HTML/Models/EditHtmlViewModel.cs | 73 ++++ .../Modules/HTML/Models/HtmlModuleModel.cs | 11 + .../HTML/Models/HtmlModuleSettingsModel.cs | 36 ++ .../Modules/HTML/Models/HtmlTextUserModel.cs | 17 + .../Modules/HTML/Models/MyWorkModel.cs | 19 + .../Modules/HTML/Mvc/HtmlModuleControl.cs | 69 ++++ .../Modules/HTML/Views/EditHTML.cshtml | 168 ++++++++ .../Modules/HTML/Views/HtmlModule.cshtml | 6 + .../Modules/HTML/Views/LoadSettings.cshtml | 40 ++ DNN Platform/Modules/HTML/Views/MyWork.cshtml | 42 ++ DNN Platform/Modules/HTML/Views/_Edit.cshtml | 42 ++ .../Modules/HTML/Views/_History.cshtml | 72 ++++ DNN Platform/Modules/HTML/Views/web.config | 33 ++ 19 files changed, 1416 insertions(+) create mode 100644 DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs create mode 100644 DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs create mode 100644 DNN Platform/Modules/HTML/Controllers/HTMLHtmlModuleViewController.cs create mode 100644 DNN Platform/Modules/HTML/Controllers/HTMLMyWorkViewController.cs create mode 100644 DNN Platform/Modules/HTML/Controllers/HTMLSettingsViewController.cs create mode 100644 DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs create mode 100644 DNN Platform/Modules/HTML/Models/HtmlModuleModel.cs create mode 100644 DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs create mode 100644 DNN Platform/Modules/HTML/Models/HtmlTextUserModel.cs create mode 100644 DNN Platform/Modules/HTML/Models/MyWorkModel.cs create mode 100644 DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs create mode 100644 DNN Platform/Modules/HTML/Views/EditHTML.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/HtmlModule.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/LoadSettings.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/MyWork.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/_Edit.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/_History.cshtml create mode 100644 DNN Platform/Modules/HTML/Views/web.config diff --git a/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs b/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs new file mode 100644 index 00000000000..73640e21a9f --- /dev/null +++ b/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs @@ -0,0 +1,388 @@ +// 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.Modules.Html.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Content.Workflow.Entities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Modules.Html; + using DotNetNuke.Modules.Html.Components; + using DotNetNuke.Modules.Html.Models; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client.ClientResourceManagement; + + // using DotNetNuke.Web.Mvc; + using DotNetNuke.Web.Mvc.Page; + using DotNetNuke.Website.Controllers; + using Microsoft.Extensions.DependencyInjection; + + public class DNN_HTMLController : ModuleSettingsController + { + // private readonly INavigationManager navigationManager; + private readonly HtmlTextController htmlTextController; + private readonly HtmlTextLogController htmlTextLogController = new HtmlTextLogController(); + private readonly WorkflowStateController workflowStateController = new WorkflowStateController(); + private readonly HtmlModuleSettingsRepository settingsRepository; + + public DNN_HTMLController(IContentSecurityPolicy csp, INavigationManager navigationManager) + : base(navigationManager) + { + // this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + this.htmlTextController = new HtmlTextController(this.NavigationManager); + this.settingsRepository = new HtmlModuleSettingsRepository(); + } + + public enum WorkflowType + { +#pragma warning disable SA1602 // Enumeration items should be documented + DirectPublish = 1, +#pragma warning restore SA1602 // Enumeration items should be documented +#pragma warning disable SA1602 // Enumeration items should be documented + ContentStaging = 2, +#pragma warning restore SA1602 // Enumeration items should be documented + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult Save(EditHtmlViewModel model) + { + try + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); + + htmlContent.Content = model.EditorContent; + + var draftStateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); + var publishedStateID = this.workflowStateController.GetLastWorkflowStateID(workflowID); + + switch (model.WorkflowType) + { + case WorkflowType.DirectPublish: + this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + break; + case WorkflowType.ContentStaging: + if (model.Publish) + { + // if it's already published set it to draft + if (htmlContent.StateID == publishedStateID) + { + htmlContent.StateID = draftStateID; + } + else + { + htmlContent.StateID = publishedStateID; + + // here it's in published mode + } + } + else + { + // if it's already published set it back to draft + if (htmlContent.StateID != draftStateID) + { + htmlContent.StateID = draftStateID; + } + } + + this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + break; + } + + return new EmptyResult(); + } + catch (Exception exc) + { + // Gérer l'exception + // return this.View("Error", new ErrorViewModel { Message = exc.Message }); + throw new Exception(exc.Message, exc); + } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult ShowHistory(EditHtmlViewModel model) + { + model.ShowHistoryView = true; + model.LocalResourceFile = "DesktopModules\\HTML\\App_LocalResources/EditHTML"; + model.RedirectUrl = this.NavigationManager.NavigateURL(model.TabId); + + // model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(this.ActiveModule.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(this.ActiveModule.ModuleControl.ControlSrc)); + try + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); + + var maxVersions = this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId); + model.MaxVersions = maxVersions; + + // var htmlLogging = this.htmlTextLogController.GetHtmlTextLog(htmlContent.ItemID); + var versions = this.htmlTextController.GetAllHtmlText(model.ModuleId); + model.VersionItems = versions.Cast().ToList(); + + // return this.PartialView(this.ActiveModule, "_History", model); + return this.PartialView(this.ActiveModule, "EditHtml", model); + } + catch (Exception exc) + { + // Gérer l'exception + // return this.View("Error", new ErrorViewModel { Message = exc.Message }); + throw new Exception(exc.Message, exc); + } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult ShowPreview(EditHtmlViewModel model) + { + model.ShowPreviewView = true; + model.LocalResourceFile = "DesktopModules\\HTML\\App_LocalResources/EditHTML"; + model.RedirectUrl = this.NavigationManager.NavigateURL(model.TabId); + + // model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(this.ActiveModule.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(this.ActiveModule.ModuleControl.ControlSrc)); + try + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); + + var moduleSettings = this.settingsRepository.GetSettings(this.ActiveModule); + model.PreviewContent = HtmlTextController.FormatHtmlText(model.ModuleId, htmlContent.Content, moduleSettings, this.PortalSettings, null); + + // return this.PartialView(this.ActiveModule, "_History", model); + return this.PartialView(this.ActiveModule, "EditHtml", model); + } + catch (Exception exc) + { + // Gérer l'exception + // return this.View("Error", new ErrorViewModel { Message = exc.Message }); + throw new Exception(exc.Message, exc); + } + } + + public ActionResult ShowEdit(EditHtmlViewModel model) + { + model.ShowEditView = true; + model.LocalResourceFile = "DesktopModules\\HTML\\App_LocalResources/EditHTML"; + model.RedirectUrl = this.NavigationManager.NavigateURL(model.TabId); + try + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); + + model.ShowPublishOption = model.WorkflowType != WorkflowType.DirectPublish; + model.ShowCurrentVersion = model.WorkflowType != WorkflowType.DirectPublish; + + var workflowStates = this.workflowStateController.GetWorkflowStates(workflowID); + var maxVersions = this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId); + + var htmlContentItemID = -1; + + if (htmlContent != null) + { + htmlContentItemID = htmlContent.ItemID; + var html = System.Web.HttpUtility.HtmlDecode(htmlContent.Content); + model.EditorContent = html; + } + + model.MaxVersions = maxVersions; + + model.WorkflowType = workflowStates.Count == 1 ? WorkflowType.DirectPublish : WorkflowType.ContentStaging; + if (htmlContentItemID != -1) + { + this.PopulateModelWithContent(model, htmlContent); + } + else + { + this.PopulateModelWithInitialContent(model, workflowStates[0] as WorkflowStateInfo); + } + + // return this.PartialView(this.ActiveModule, "_Edit", model); + return this.PartialView(this.ActiveModule, "EditHtml", model); + } + catch (Exception exc) + { + // Gérer l'exception + // return this.View("Error", new ErrorViewModel { Message = exc.Message }); + throw new Exception(exc.Message, exc); + } + } + + public ActionResult HistoryRemove(EditHtmlViewModel model) + { + // int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + // var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); + this.htmlTextController.DeleteHtmlText(model.ModuleId, model.ItemID); + + return this.ShowEdit(model); + } + + public ActionResult HistoryRollback(EditHtmlViewModel model) + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.htmlTextController.GetHtmlText(model.ModuleId, model.ItemID); + htmlContent.ItemID = -1; + htmlContent.ModuleID = model.ModuleId; + htmlContent.WorkflowID = workflowID; + htmlContent.StateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); + this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + return this.ShowEdit(model); + } + + public ActionResult HistoryPreview(EditHtmlViewModel model) + { + model.ShowPreviewView = true; + model.LocalResourceFile = "DesktopModules\\HTML\\App_LocalResources/EditHTML"; + model.RedirectUrl = this.NavigationManager.NavigateURL(model.TabId); + + // model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(this.ActiveModule.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(this.ActiveModule.ModuleControl.ControlSrc)); + try + { + int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; + var htmlContent = this.htmlTextController.GetHtmlText(model.ModuleId, model.ItemID); + + var moduleSettings = this.settingsRepository.GetSettings(this.ActiveModule); + model.PreviewContent = HtmlTextController.FormatHtmlText(model.ModuleId, htmlContent.Content, moduleSettings, this.PortalSettings, null); + + // return this.PartialView(this.ActiveModule, "_History", model); + return this.PartialView(this.ActiveModule, "EditHtml", model); + } + catch (Exception exc) + { + // Gérer l'exception + // return this.View("Error", new ErrorViewModel { Message = exc.Message }); + throw new Exception(exc.Message, exc); + } + } + + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult UpdateSettings(HtmlModuleSettingsModel model) + { + // if (ModelState.IsValid) + { + try + { + // Récupérer les paramètres existants + var moduleSettings = this.settingsRepository.GetSettings(this.ActiveModule); + + // Mettre à jour les paramètres dans le repository + moduleSettings.ReplaceTokens = model.ReplaceTokens; + moduleSettings.UseDecorate = model.UseDecorate; + moduleSettings.SearchDescLength = model.SearchDescLength; + + // Sauvegarder les paramètres mis à jour + this.settingsRepository.SaveSettings(this.ActiveModule, moduleSettings); + + // Gérer le CacheTime + this.UpdateCacheTime(model.ReplaceTokens); + + // Mettre à jour les workflows selon la sélection + this.UpdateWorkflow(model.SelectedWorkflow, model.ApplyTo, model.Replace); + } + catch (Exception exc) + { + // Gérer les exceptions + // Exceptions.ProcessModuleLoadException(this, exc); + throw new Exception("sModuleLoadException", exc); + } + + return this.UpdateDefaultSettings(model); + } + } + + private List GetWorkflows() + { + // Récupérer les workflows disponibles + var workflowStateController = new WorkflowStateController(); + var workflows = workflowStateController.GetWorkflows(this.ActiveModule.PortalID); + return workflows.Cast().Where(w => !w.IsDeleted).ToList(); // Filtrer les workflows non supprimés + } + + private void UpdateWorkflow(string selectedWorkflow, string applyTo, bool replace) + { + var htmlTextController = new HtmlTextController(this.NavigationManager); + var workflow = this.htmlTextController.GetWorkflow(this.ActiveModule.ModuleID, this.ActiveModule.TabID, this.ActiveModule.PortalID); + + // Mettre à jour le workflow selon la sélection + switch (applyTo) + { + case "Module": + htmlTextController.UpdateWorkflow(this.ActiveModule.ModuleID, applyTo, int.Parse(selectedWorkflow), replace); + break; + case "Page": + htmlTextController.UpdateWorkflow(this.ActiveModule.TabID, applyTo, int.Parse(selectedWorkflow), replace); + break; + case "Site": + htmlTextController.UpdateWorkflow(this.ActiveModule.PortalID, applyTo, int.Parse(selectedWorkflow), replace); + break; + } + } + + private void UpdateCacheTime(bool replaceTokens) + { + // Récupérer le module actuel + var module = ModuleController.Instance.GetModule(this.ActiveModule.ModuleID, this.ActiveModule.TabID, false); + if (replaceTokens) + { + // Désactiver le cache si ReplaceTokens est activé + module.CacheTime = 0; + } + else + { + // Réinitialiser le CacheTime à sa valeur par défaut si nécessaire + module.CacheTime = 60; // ou toute autre valeur par défaut + } + + // Mettre à jour le module avec le nouveau CacheTime + ModuleController.Instance.UpdateModule(module); + } + + private HtmlTextInfo GetLatestHTMLContent(int workflowID, int moduleId) + { + var htmlContent = this.htmlTextController.GetTopHtmlText(moduleId, false, workflowID); + if (htmlContent == null) + { + htmlContent = new HtmlTextInfo(); + htmlContent.ItemID = -1; + htmlContent.StateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); + htmlContent.WorkflowID = workflowID; + htmlContent.ModuleID = moduleId; + } + + return htmlContent; + } + + private void PopulateModelWithContent(EditHtmlViewModel model, HtmlTextInfo htmlContent) + { + model.CurrentWorkflowInUse = htmlContent.WorkflowName; + model.CurrentWorkflowState = htmlContent.StateName; + model.CurrentVersion = htmlContent.Version.ToString(); + + // model.Content = this.FormatContent(htmlContent.Content); + } + + private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowStateInfo firstState) + { + // model.EditorContent = this.LocalizeString("AddContent"); + model.CurrentWorkflowInUse = firstState.WorkflowName; + model.ShowCurrentWorkflowState = false; + model.ShowCurrentVersion = false; + } + } +} diff --git a/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs b/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs new file mode 100644 index 00000000000..ffd469cdca0 --- /dev/null +++ b/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs @@ -0,0 +1,137 @@ +// 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.Modules.Html.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Content.Workflow.Entities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Modules.Html; + using DotNetNuke.Modules.Html.Components; + using DotNetNuke.Modules.Html.Models; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.Mvc; + + // using DotNetNuke.Web.Mvc; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Website.Controllers; + using Microsoft.Extensions.DependencyInjection; + + using static DotNetNuke.Modules.Html.Controllers.DNN_HTMLController; + + public class HTMLEditHTMLViewController : ModuleViewControllerBase + { + private readonly INavigationManager navigationManager; + private readonly HtmlTextController htmlTextController; + private readonly WorkflowStateController workflowStateController = new WorkflowStateController(); + private readonly IContentSecurityPolicy contentSecurityPolicy; + + public HTMLEditHTMLViewController(IContentSecurityPolicy csp) + { + this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + this.htmlTextController = new HtmlTextController(this.navigationManager); + + // this.contentSecurityPolicy = Globals.DependencyProvider.GetRequiredService(); + this.contentSecurityPolicy = csp; + } + + protected override object ViewModel(ModuleInfo module) + { + var model = new EditHtmlViewModel(); + + // model.LocalResourceFile = "/DesktopModules/Html/" + Localization.LocalResourceDirectory + "/EditHTML"; + model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(module.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(module.ModuleControl.ControlSrc)); + model.ShowEditView = true; + model.ModuleId = module.ModuleID; + model.TabId = module.TabID; + model.PortalId = this.PortalSettings.PortalId; + model.RedirectUrl = this.navigationManager.NavigateURL(); + int workflowID = this.htmlTextController.GetWorkflow(module.ModuleID, module.TabID, module.PortalID).Value; + + try + { + var htmlContentItemID = -1; + var htmlContent = this.htmlTextController.GetTopHtmlText(module.ModuleID, false, workflowID); + + if (htmlContent != null) + { + htmlContentItemID = htmlContent.ItemID; + var html = System.Web.HttpUtility.HtmlDecode(htmlContent.Content); + model.EditorContent = html; + } + + var workflowStates = this.workflowStateController.GetWorkflowStates(workflowID); + var maxVersions = this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId); + + model.MaxVersions = maxVersions; + + model.WorkflowType = workflowStates.Count == 1 ? WorkflowType.DirectPublish : WorkflowType.ContentStaging; + if (htmlContentItemID != -1) + { + this.PopulateModelWithContent(model, htmlContent); + } + else + { + this.PopulateModelWithInitialContent(model, workflowStates[0] as WorkflowStateInfo); + } + + model.ShowPublishOption = model.WorkflowType != WorkflowType.DirectPublish; + model.ShowCurrentVersion = model.WorkflowType != WorkflowType.DirectPublish; + model.ShowPreviewVersion = model.WorkflowType != WorkflowType.DirectPublish; + model.ShowHistoryView = false; + model.ShowMasterContentButton = false; + + // model.RenderOptions = this.GetRenderOptions(); + } + catch (Exception exc) + { + // Gérer l'exception + // Exceptions.ProcessModuleLoadException(this, exc); + throw new Exception("EditHTML", exc); + } + + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/Resources/Shared/scripts/jquery/jquery.form.min.js"); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Portals/_default/Skins/_default/WebControlSkin/Default/GridView.default.css"); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/DesktopModules/HTML/edit.css"); + MvcClientResourceManager.RegisterScript(this.ControllerContext, "~/DesktopModules/HTML/js/edit.js"); + + this.contentSecurityPolicy.StyleSource.AddInline(); + this.contentSecurityPolicy.ScriptSource.AddSelf().AddInline(); + this.contentSecurityPolicy.ImgSource.AddScheme("data:"); + return model; + } + + private void PopulateModelWithContent(EditHtmlViewModel model, HtmlTextInfo htmlContent) + { + model.CurrentWorkflowInUse = htmlContent.WorkflowName; + model.CurrentWorkflowState = htmlContent.StateName; + model.CurrentVersion = htmlContent.Version.ToString(); + + // model.Content = this.FormatContent(htmlContent.Content); + } + + private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowStateInfo firstState) + { + // model.EditorContent = this.LocalizeString("AddContent"); + model.CurrentWorkflowInUse = firstState.WorkflowName; + model.ShowCurrentWorkflowState = false; + model.ShowCurrentVersion = false; + } + } +} diff --git a/DNN Platform/Modules/HTML/Controllers/HTMLHtmlModuleViewController.cs b/DNN Platform/Modules/HTML/Controllers/HTMLHtmlModuleViewController.cs new file mode 100644 index 00000000000..944d14bf3d1 --- /dev/null +++ b/DNN Platform/Modules/HTML/Controllers/HTMLHtmlModuleViewController.cs @@ -0,0 +1,107 @@ +// 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.Modules.Html.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Entities.Content.Workflow.Entities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Modules.Html; + using DotNetNuke.Modules.Html.Components; + using DotNetNuke.Modules.Html.Models; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Services.Personalization; + using DotNetNuke.UI.Modules; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.Mvc; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Website.Controllers; + using Microsoft.Extensions.DependencyInjection; + + public class HTMLHtmlModuleViewController : ModuleViewControllerBase + { + private readonly INavigationManager navigationManager; + private readonly HtmlTextController htmlTextController; + + public HTMLHtmlModuleViewController() + { + this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + this.htmlTextController = new HtmlTextController(this.navigationManager); + } + + protected override object ViewModel(ModuleInfo module) + { + int workflowID = this.htmlTextController.GetWorkflow(module.ModuleID, module.TabID, module.PortalID).Value; + this.ModuleActionPublish(module, workflowID); + HtmlTextInfo content = this.htmlTextController.GetTopHtmlText(module.ModuleID, true, workflowID); + + var html = string.Empty; + if (content != null) + { + html = System.Web.HttpUtility.HtmlDecode(content.Content); + } + + return new HtmlModuleModel() + { + Html = html, + }; + } + + private void ModuleActionPublish(ModuleInfo module, int workflowID) + { + try + { + if (this.Request.QueryString["act"]?.ToString() == "publish" && this.Request.QueryString["mod"]?.ToString() == module.ModuleID.ToString()) + { + var moduleContext = new ModuleInstanceContext(); + moduleContext.Configuration = module; + + // verify security + if (moduleContext.IsEditable && Personalization.GetUserMode() == DotNetNuke.Entities.Portals.PortalSettings.Mode.Edit) + { + // get content + var objHTML = new HtmlTextController(this.navigationManager); + HtmlTextInfo objContent = objHTML.GetTopHtmlText(module.ModuleID, false, workflowID); + /* + var objWorkflow = new WorkflowStateController(); + if (objContent.StateID == objWorkflow.GetFirstWorkflowStateID(workflowID)) + { + // if not direct publish workflow + if (objWorkflow.GetWorkflowStates(workflowID).Count > 1) + { + // publish content + objContent.StateID = objWorkflow.GetNextWorkflowStateID(objContent.WorkflowID, objContent.StateID); + + // save the content + objHTML.UpdateHtmlText(objContent, objHTML.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + + // refresh page + // this.Response.Redirect(this.navigationManager.NavigateURL(), true); + } + } + */ + } + } + } + catch (Exception exc) + { + // Exceptions.ProcessModuleLoadException(this, exc); + throw new Exception("HTML Module Publish", exc); + } + } + } +} diff --git a/DNN Platform/Modules/HTML/Controllers/HTMLMyWorkViewController.cs b/DNN Platform/Modules/HTML/Controllers/HTMLMyWorkViewController.cs new file mode 100644 index 00000000000..4141386f0e6 --- /dev/null +++ b/DNN Platform/Modules/HTML/Controllers/HTMLMyWorkViewController.cs @@ -0,0 +1,64 @@ +// 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.Modules.Html.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Entities.Content.Workflow.Entities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Modules.Html; + using DotNetNuke.Modules.Html.Components; + using DotNetNuke.Modules.Html.Models; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.Mvc; + using DotNetNuke.Web.MvcPipeline.Controllers; + using DotNetNuke.Website.Controllers; + using Microsoft.Extensions.DependencyInjection; + + public class HTMLMyWorkViewController : ModuleViewControllerBase + { + private readonly INavigationManager navigationManager; + + public HTMLMyWorkViewController() + { + this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + } + + protected override object ViewModel(ModuleInfo module) + { + var objHtmlTextUsers = new HtmlTextUserController(); + var lst = objHtmlTextUsers.GetHtmlTextUser(this.UserInfo.UserID).Cast(); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/DesktopModules/HTML/edit.css"); + MvcClientResourceManager.RegisterStyleSheet(this.ControllerContext, "~/Portals/_default/Skins/_default/WebControlSkin/Default/GridView.default.css"); + return new MyWorkModel() + { + LocalResourceFile = Path.Combine(Path.GetDirectoryName(module.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(module.ModuleControl.ControlSrc)), + ModuleId = module.ModuleID, + TabId = module.TabID, + RedirectUrl = this.navigationManager.NavigateURL(), + HtmlTextUsers = lst.Select(u => new HtmlTextUserModel() + { + Url = this.navigationManager.NavigateURL(u.TabID), + ModuleID = u.ModuleID, + ModuleTitle = u.ModuleTitle, + StateName = u.StateName, + }).ToList(), + }; + } + } +} diff --git a/DNN Platform/Modules/HTML/Controllers/HTMLSettingsViewController.cs b/DNN Platform/Modules/HTML/Controllers/HTMLSettingsViewController.cs new file mode 100644 index 00000000000..fba0267ff22 --- /dev/null +++ b/DNN Platform/Modules/HTML/Controllers/HTMLSettingsViewController.cs @@ -0,0 +1,75 @@ +// 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.Modules.Html.Controllers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.NetworkInformation; + using System.Web; + using System.Web.Mvc; + + using DotNetNuke.Abstractions; + using DotNetNuke.Common; + using DotNetNuke.Entities.Content.Workflow.Entities; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Framework.JavaScriptLibraries; + using DotNetNuke.Modules.Html; + using DotNetNuke.Modules.Html.Components; + using DotNetNuke.Modules.Html.Models; + using DotNetNuke.Mvc; + using DotNetNuke.Services.Exceptions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.Client.ClientResourceManagement; + using DotNetNuke.Web.Mvc; + + using DotNetNuke.Web.MvcPipeline.Controllers; + using Microsoft.Extensions.DependencyInjection; + + public class HTMLSettingsViewController : ModuleControllerBase + { + private readonly INavigationManager navigationManager; + private readonly HtmlTextController htmlTextController; + private readonly WorkflowStateController workflowStateController = new WorkflowStateController(); + private readonly HtmlModuleSettingsRepository settingsRepository; + + public HTMLSettingsViewController() + { + this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + this.htmlTextController = new HtmlTextController(this.navigationManager); + this.settingsRepository = new HtmlModuleSettingsRepository(); + } + + [HttpGet] + [ChildActionOnly] + public ActionResult Invoke(int moduleId) + { + var moduleSettings = this.settingsRepository.GetSettings(this.ActiveModule); + var workflow = this.htmlTextController.GetWorkflow(this.ActiveModule.ModuleID, this.ActiveModule.TabID, this.ActiveModule.PortalID); + + var model = new HtmlModuleSettingsModel + { + // Assigner les valeurs des paramètres au modèle directement depuis le repository + ReplaceTokens = moduleSettings.ReplaceTokens, + UseDecorate = moduleSettings.UseDecorate, + SearchDescLength = moduleSettings.SearchDescLength, + Workflows = this.GetWorkflows(), // Récupérer les workflows disponibles + ApplyTo = workflow.Key, + SelectedWorkflow = workflow.Value.ToString(), + }; + + return this.PartialView(this.ActiveModule, "LoadSettings", model); + } + + private List GetWorkflows() + { + // Récupérer les workflows disponibles + var workflows = this.workflowStateController.GetWorkflows(this.ActiveModule.PortalID); + return workflows.Cast().Where(w => !w.IsDeleted).ToList(); // Filtrer les workflows non supprimés + } + } +} diff --git a/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj b/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj index b0647921e79..15d9ba1c631 100644 --- a/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj +++ b/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj @@ -122,6 +122,9 @@ + + ..\..\..\Packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + @@ -142,6 +145,7 @@ + EditHtml.ascx ASPXCodeBehind @@ -156,6 +160,8 @@ HtmlModule.ascx + + MyWork.ascx ASPXCodeBehind @@ -221,6 +227,13 @@ + + + + + + + @@ -231,6 +244,10 @@ {6928A9B1-F88A-4581-A132-D3EB38669BB0} DotNetNuke.Abstractions + + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} + DotNetNuke.Web.MvcPipeline + {ee1329fe-fd88-4e1a-968c-345e394ef080} DotNetNuke.Web diff --git a/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs b/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs new file mode 100644 index 00000000000..cd351ece8e8 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.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.Modules.Html.Models +{ + using System.Collections.Generic; + using System.Web.Mvc; + + using DotNetNuke.Web.Mvc.Page; + using DotNetNuke.Web.MvcPipeline.Models; + + using static DotNetNuke.Modules.Html.Controllers.DNN_HTMLController; + + public class EditHtmlViewModel : ModuleModelBase + { + [AllowHtml] + public string EditorContent { get; set; } + + public bool EditorEnabled { get; set; } + + public bool UseDecorate { get; set; } + + // public List ToolbarButtons { get; set; } + public string CurrentView { get; set; } + + public bool ShowEditView { get; set; } + + public bool ShowMasterContent { get; set; } + + public string MasterContent { get; set; } + + public bool ShowCurrentVersion { get; set; } + + public string CurrentWorkflowState { get; set; } + + public string CurrentVersion { get; set; } + + public string CurrentWorkflowInUse { get; set; } + + public bool ShowPublishOption { get; set; } + + public bool Publish { get; set; } + + public WorkflowType WorkflowType { get; set; } + + public int MaxVersions { get; set; } + + public bool ShowCurrentWorkflowState { get; set; } + + public List RenderOptions { get; set; } + + public string SelectedRenderOption { get; set; } + + public bool ShowPreviewVersion { get; set; } + + public bool ShowPreviewView { get; set; } + + public string RedirectUrl { get; set; } + + public bool ShowHistoryView { get; set; } + + public bool ShowMasterContentButton { get; set; } + + public List VersionItems { get; set; } + + public int ItemID { get; set; } + + public int PortalId { get; set; } + + public string PreviewContent { get; set; } + } +} diff --git a/DNN Platform/Modules/HTML/Models/HtmlModuleModel.cs b/DNN Platform/Modules/HTML/Models/HtmlModuleModel.cs new file mode 100644 index 00000000000..ec09b08ce67 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/HtmlModuleModel.cs @@ -0,0 +1,11 @@ +// 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.Modules.Html.Models +{ + public class HtmlModuleModel + { + public string Html { get; set; } + } +} diff --git a/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs b/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs new file mode 100644 index 00000000000..f87feebe452 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs @@ -0,0 +1,36 @@ +// 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.Modules.Html.Models +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Web.Mvc; + + using DotNetNuke.Modules.Html; + using DotNetNuke.Web.MvcPipeline.Models; + + public class HtmlModuleSettingsModel : ModuleSettingsModel + { + [Display(Name = "plReplaceTokens")] // ID de Settings.ascx + public bool ReplaceTokens { get; set; } + + [Display(Name = "plDecorate")] // ID de Settings.ascx + public bool UseDecorate { get; set; } + + [Display(Name = "plSearchDescLength")] // ID de Settings.ascx + public int SearchDescLength { get; set; } + + public List Workflows { get; set; } + + [Display(Name = "plWorkflow")] // ID de Settings.ascx + public string SelectedWorkflow { get; set; } + + [Display(Name = "plApplyTo")] // ID de Settings.ascx + public string ApplyTo { get; set; } + + [Display(Name = "chkReplace")] // ID de Settings.ascx + public bool Replace { get; set; } + } +} diff --git a/DNN Platform/Modules/HTML/Models/HtmlTextUserModel.cs b/DNN Platform/Modules/HTML/Models/HtmlTextUserModel.cs new file mode 100644 index 00000000000..747b4491718 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/HtmlTextUserModel.cs @@ -0,0 +1,17 @@ +// 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.Modules.Html.Models +{ + public class HtmlTextUserModel + { + public string Url { get; set; } + + public int ModuleID { get; set; } + + public string ModuleTitle { get; set; } + + public string StateName { get; set; } + } +} diff --git a/DNN Platform/Modules/HTML/Models/MyWorkModel.cs b/DNN Platform/Modules/HTML/Models/MyWorkModel.cs new file mode 100644 index 00000000000..cfcde9e9cb0 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/MyWorkModel.cs @@ -0,0 +1,19 @@ +// 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.Modules.Html.Models +{ + using System.Collections.Generic; + using System.Web.Mvc; + + using DotNetNuke.Modules.Html; + using DotNetNuke.Web.Mvc.Page; + using DotNetNuke.Web.MvcPipeline.Models; + + public class MyWorkModel : ModuleModelBase + { + public IEnumerable HtmlTextUsers { get; set; } + + public string RedirectUrl { get; internal set; } + } +} diff --git a/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs b/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs new file mode 100644 index 00000000000..d0c9fd96e0f --- /dev/null +++ b/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs @@ -0,0 +1,69 @@ +// 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.Modules.Html +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + using DotNetNuke.Abstractions; + using DotNetNuke.Entities.Modules; + using DotNetNuke.Entities.Modules.Actions; + using DotNetNuke.Security; + using DotNetNuke.Security.Permissions; + using DotNetNuke.Services.Localization; + using DotNetNuke.Web.MvcPipeline.ModuleControl; + using Microsoft.Extensions.DependencyInjection; + + public class HtmlModuleControl : ModuleControlBase, IActionable + { + private readonly INavigationManager navigationManager; + + // private bool editorEnabled; + // private int workflowID; + public HtmlModuleControl() + { + this.navigationManager = this.DependencyProvider.GetRequiredService(); + this.ControlPath = "DesktopModules/HTML"; + this.ID = "HtmlModule.ascx"; + } + + /// Gets moduleActions is an interface property that returns the module actions collection for the module. + public ModuleActionCollection ModuleActions + { + get + { + // add the Edit Text action + var actions = new ModuleActionCollection(); + actions.Add( + this.GetNextActionID(), + Localization.GetString(ModuleActionType.AddContent, this.LocalResourceFile), + ModuleActionType.AddContent, + string.Empty, + string.Empty, + this.EditUrl(/*"mvcpage", "yes"*/), + false, + SecurityAccessLevel.Edit, + true, + false); + + // add mywork to action menu + actions.Add( + this.GetNextActionID(), + Localization.GetString("MyWork.Action", this.LocalResourceFile), + "MyWork.Action", + string.Empty, + "view.gif", + this.EditUrl(/*"mvcpage", "yes",*/ "MyWork"), + false, + SecurityAccessLevel.Edit, + true, + false); + + return actions; + } + } + } +} diff --git a/DNN Platform/Modules/HTML/Views/EditHTML.cshtml b/DNN Platform/Modules/HTML/Views/EditHTML.cshtml new file mode 100644 index 00000000000..42829c76868 --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/EditHTML.cshtml @@ -0,0 +1,168 @@ +@using DotNetNuke.Modules.Html.Models +@using DotNetNuke.Web.MvcPipeline +@using DotNetNuke.Services.Localization +@model EditHtmlViewModel + +
    + @using (Html.BeginForm("Save", "DNN_HTML")) + { + @Html.AntiForgeryToken() + @Html.HiddenFor(x => x.TabId) + @Html.HiddenFor(x => x.ModuleId) + @Html.HiddenFor(x => x.WorkflowType) +
    +
    + @if (Model.ShowEditView) + { + @Html.Partial("~/DeskTopModules/Html/Views/_Edit.cshtml", Model) + } + @if (Model.ShowPreviewView) + { +
    + @if (Model.ShowPreviewVersion) + { +
    + + Model.PreviewVersion +
    +
    + + Model.PreviewWorkflowInUse +
    +
    + + Model.PreviewWorkflowState +
    + } +
    + @Html.Raw(Model.PreviewContent) +
    +
    + @* +

    @Localization.GetString("dshHistory", Model.LocalResourceFile)

    +
    + Html.Partial("_HistoryGrid", Model.HistoryItems) +
    + *@ + } + + @if (Model.ShowHistoryView) + { +
    +
    +
    + + @Model.MaxVersions +
    + @Html.Partial("~/DesktopModules/HTML/Views/_History.cshtml", Model) +
    +
    + } +
    +
    +
    +
      +
    • + +
    • +
    • + @Localization.GetString("cmdCancel", Model.LocalResourceFile) +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • + @*
    • +
      + @Html.DropDownListFor(m => m.SelectedRenderOption, Model.RenderOptions, new { @class = "dnnSecondaryAction", id = "ddlRender" }) +
      +
    • *@ + @if (Model.ShowMasterContentButton) + { +
    • + +
    • +
    • + +
    • + } +
    +
    + } +
    + +@* + @section Scripts { + + } +*@ \ No newline at end of file diff --git a/DNN Platform/Modules/HTML/Views/HtmlModule.cshtml b/DNN Platform/Modules/HTML/Views/HtmlModule.cshtml new file mode 100644 index 00000000000..9539ad7dd6c --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/HtmlModule.cshtml @@ -0,0 +1,6 @@ +@using DotNetNuke.Modules.Html +@using DotNetNuke.Entities.Modules +@model DotNetNuke.Modules.Html.Models.HtmlModuleModel +@{} + +
    @Html.Raw(Model.Html)
    \ No newline at end of file diff --git a/DNN Platform/Modules/HTML/Views/LoadSettings.cshtml b/DNN Platform/Modules/HTML/Views/LoadSettings.cshtml new file mode 100644 index 00000000000..cfa1125880c --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/LoadSettings.cshtml @@ -0,0 +1,40 @@ +@using DotNetNuke.Modules.Html.Models +@using DotNetNuke.Services.Localization +@using DotNetNuke.Entities.Modules +@using DotNetNuke.Web.MvcPipeline.Modules +@model HtmlModuleSettingsModel +@{ + var LocalResourceFile = "~/DesktopModules/HTML/" + Localization.LocalResourceDirectory + "/Settings.ascx"; + +} + +
    + +
    + @Html.DnnLabelFor(m => m.ReplaceTokens, LocalResourceFile) + @Html.CheckBoxFor(m => m.ReplaceTokens) +
    +
    + @Html.DnnLabelFor(m => m.UseDecorate, LocalResourceFile) + @Html.CheckBoxFor(m => m.UseDecorate) +
    +
    + @Html.DnnLabelFor(m => m.SearchDescLength, LocalResourceFile) + @Html.TextBoxFor(m => m.SearchDescLength) + @Html.ValidationMessageFor(m => m.SearchDescLength) +
    +
    + @Html.DnnLabelFor(m => m.SelectedWorkflow, LocalResourceFile) + @Html.DropDownListFor(m => m.SelectedWorkflow, new SelectList(Model.Workflows, "WorkflowID", "WorkflowName")) +
    +
    + @Html.DnnLabelFor(m => m.ApplyTo, LocalResourceFile) + @Html.RadioButtonFor(m => m.ApplyTo, "Module") Module + @Html.RadioButtonFor(m => m.ApplyTo, "Page") Page + @Html.RadioButtonFor(m => m.ApplyTo, "Site") Site +
    + @Html.DnnLabelFor(m => m.Replace, LocalResourceFile) + @Html.CheckBoxFor(m => m.Replace, new { @class = "inline" }) +
    +
    +
    \ No newline at end of file diff --git a/DNN Platform/Modules/HTML/Views/MyWork.cshtml b/DNN Platform/Modules/HTML/Views/MyWork.cshtml new file mode 100644 index 00000000000..955877a2c51 --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/MyWork.cshtml @@ -0,0 +1,42 @@ +@using DotNetNuke.Modules.Html.Mvc +@using DotNetNuke.Entities.Modules +@using DotNetNuke.Services.Localization +@model MyWorkModel +@{} + + +
    + @if (Model.HtmlTextUsers.Any()) + { + + + + + + + + @foreach (var item in Model.HtmlTextUsers) + { + + + + } + +
    @Localization.GetString("Page", Model.LocalResourceFile)
    + @item.ModuleTitle ( @item.StateName ) +
    + } + else + { +
    + @Localization.GetString("lblNoRecords", Model.LocalResourceFile) +
    + } + +
    + +@section Styles { + +} \ No newline at end of file diff --git a/DNN Platform/Modules/HTML/Views/_Edit.cshtml b/DNN Platform/Modules/HTML/Views/_Edit.cshtml new file mode 100644 index 00000000000..4ea1037589b --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/_Edit.cshtml @@ -0,0 +1,42 @@ +@using DotNetNuke.Modules.Html.Models +@using DotNetNuke.Web.Mvc.Page +@using DotNetNuke.Services.Localization +@using DotNetNuke.Web.MvcPipeline.Containers +@model EditHtmlViewModel + +
    +
    + @if (Model.ShowMasterContent) + { +
    +
    + @Html.Raw(Model.MasterContent) +
    +
    + } + @* Html.TextAreaFor(x => x.EditorContent, new { @class = "dnnTextEditor", rows = "10", cols = "100" })*@ + @Html.TextEditorFor(x => x.EditorContent) +
    + + @if (Model.ShowCurrentVersion) + { +
    +
    +
    + +
    + @Localization.GetString(Model.CurrentWorkflowState, Model.LocalResourceFile) + (@Localization.GetString("plCurrentWorkVersion", Model.LocalResourceFile) @Model.CurrentVersion) (@Localization.GetString(Model.CurrentWorkflowInUse, Model.LocalResourceFile)) +
    + @if (Model.ShowPublishOption) + { +
    +
    + +
    + @Html.CheckBoxFor(m => m.Publish) @Localization.GetString("chkPublish", Model.LocalResourceFile) +
    + } +
    + } +
    diff --git a/DNN Platform/Modules/HTML/Views/_History.cshtml b/DNN Platform/Modules/HTML/Views/_History.cshtml new file mode 100644 index 00000000000..3dc239922ef --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/_History.cshtml @@ -0,0 +1,72 @@ +@using DotNetNuke.Modules.Html.Models +@using DotNetNuke.Web.MvcPipeline +@using DotNetNuke.Services.Localization +@model EditHtmlViewModel + + +@if (Model.ShowHistoryView) +{ +
    +
    +
    +
    + +
    + @Model.MaxVersions +
    + + + + + + + + + + + @foreach (var item in Model.VersionItems) + { + + + + + + + + } +
    + Version @Localization.GetString("Version", Model.LocalResourceFile) + + Date + + User + + State + + Actions +
    + @item.Version + + @item.LastModifiedOnDate + + @item.DisplayName + + @item.StateName + + + + + + + +
    + + + + + +
    +
    +
    +
    +} diff --git a/DNN Platform/Modules/HTML/Views/web.config b/DNN Platform/Modules/HTML/Views/web.config new file mode 100644 index 00000000000..b9c29a8fc27 --- /dev/null +++ b/DNN Platform/Modules/HTML/Views/web.config @@ -0,0 +1,33 @@ + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + From 4aae3e8fec6095c184b987c6222ffd83186edf57 Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Tue, 8 Apr 2025 10:20:54 +0200 Subject: [PATCH 025/146] use dnn10 default.css --- .../Skins/SkinHelpers.DnnCssInclude.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs index 904ea4f9e6b..a8d77358ef9 100644 --- a/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs +++ b/DNN Platform/DotNetNuke.Web.MvcPipeline/Skins/SkinHelpers.DnnCssInclude.cs @@ -44,7 +44,7 @@ public static IHtmlString DnnCssInclude(this HtmlHelper helper, strin public static IHtmlString DnnCssIncludeDefaultStylesheet(this HtmlHelper helper, string pathNameAlias = "", int priority = 100, bool addTag = false, string name = "", string version = "", bool forceVersion = false, string forceProvider = "", bool forceBundle = false, string cssMedia = "") { - var filePath = string.Concat(Common.Globals.ApplicationPath, "/Resources/Shared/stylesheets/dnndefault/7.0.0/default.css"); + var filePath = string.Concat(Common.Globals.ApplicationPath, "/Resources/Shared/stylesheets/dnndefault/10.0.0/default.css"); MvcClientResourceManager.RegisterDefaultStylesheet(helper.ViewContext, filePath); if (addTag || helper.ViewContext.HttpContext.IsDebuggingEnabled) From 0fd3b47b4bc1210bfea06060a01bb5d03c4e934a Mon Sep 17 00:00:00 2001 From: Sacha Trauwaen Date: Wed, 9 Apr 2025 10:52:45 +0200 Subject: [PATCH 026/146] make edit in mvc work again --- .../Modules/HTML/CkEditorContents.css | 44 + .../HTML/Controllers/DNN_HTMLController.cs | 127 +- .../Controllers/HTMLEditHTMLViewController.cs | 75 +- .../HTML/DotNetNuke.Modules.Html.csproj | 30 +- .../Modules/HTML/Models/EditHtmlViewModel.cs | 8 +- .../HTML/Models/HtmlModuleSettingsModel.cs | 8 - .../Modules/HTML/Models/WorkflowType.cs | 27 + .../Modules/HTML/Mvc/HtmlModuleControl.cs | 4 +- .../Modules/HTML/Views/EditHTML.cshtml | 22 +- DNN Platform/Modules/HTML/Views/web.config | 2 +- DNN Platform/Modules/HTML/edit.css | 14 +- DNN Platform/Modules/HTML/js/edit.js | 230 +++ DNN Platform/Modules/HTML/web.config | 11 + .../Website/DotNetNuke.Website.csproj | 10 +- .../Shared/scripts/jquery/jquery.form.js | 1540 +++++++++++++++++ .../Shared/scripts/jquery/jquery.form.min.js | 23 + 16 files changed, 2014 insertions(+), 161 deletions(-) create mode 100644 DNN Platform/Modules/HTML/CkEditorContents.css create mode 100644 DNN Platform/Modules/HTML/Models/WorkflowType.cs create mode 100644 DNN Platform/Modules/HTML/js/edit.js create mode 100644 DNN Platform/Website/Resources/Shared/scripts/jquery/jquery.form.js create mode 100644 DNN Platform/Website/Resources/Shared/scripts/jquery/jquery.form.min.js diff --git a/DNN Platform/Modules/HTML/CkEditorContents.css b/DNN Platform/Modules/HTML/CkEditorContents.css new file mode 100644 index 00000000000..1796d7d2b6f --- /dev/null +++ b/DNN Platform/Modules/HTML/CkEditorContents.css @@ -0,0 +1,44 @@ +/* +DNN Custom Override Styles to fix issues with other modules, this is loaded inside the iframe for the editor. +*/ + +body { + /* Remove the background color to make it transparent */ + background-image: none; + background: none; + background-color: #fff; + margin: 20px; +} + +.cke_editable { + font-size: 13px; + line-height: 1.6; +} + +.cke_contents_ltr blockquote { + padding-left: 20px; + padding-right: 8px; + border-left-width: 5px; +} + +.cke_contents_rtl blockquote { + padding-left: 8px; + padding-right: 20px; + border-right-width: 5px; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-center { + text-align: center; +} + +.text-justify { + text-align: justify; +} diff --git a/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs b/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs index 73640e21a9f..9243a161531 100644 --- a/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs +++ b/DNN Platform/Modules/HTML/Controllers/DNN_HTMLController.cs @@ -14,21 +14,21 @@ namespace DotNetNuke.Modules.Html.Controllers using DotNetNuke.Abstractions; using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Content.Workflow; using DotNetNuke.Entities.Content.Workflow.Entities; using DotNetNuke.Entities.Modules; using DotNetNuke.Entities.Modules.Settings; + using DotNetNuke.Entities.Portals; using DotNetNuke.Framework.JavaScriptLibraries; using DotNetNuke.Modules.Html; using DotNetNuke.Modules.Html.Components; using DotNetNuke.Modules.Html.Models; - using DotNetNuke.Mvc; + using DotNetNuke.Security; using DotNetNuke.Services.Exceptions; using DotNetNuke.Services.Localization; using DotNetNuke.Web.Client.ClientResourceManagement; - - // using DotNetNuke.Web.Mvc; - using DotNetNuke.Web.Mvc.Page; using DotNetNuke.Website.Controllers; using Microsoft.Extensions.DependencyInjection; @@ -37,7 +37,7 @@ public class DNN_HTMLController : ModuleSettingsController // private readonly INavigationManager navigationManager; private readonly HtmlTextController htmlTextController; private readonly HtmlTextLogController htmlTextLogController = new HtmlTextLogController(); - private readonly WorkflowStateController workflowStateController = new WorkflowStateController(); + private readonly IWorkflowManager workflowManager = WorkflowManager.Instance; private readonly HtmlModuleSettingsRepository settingsRepository; public DNN_HTMLController(IContentSecurityPolicy csp, INavigationManager navigationManager) @@ -48,60 +48,46 @@ public DNN_HTMLController(IContentSecurityPolicy csp, INavigationManager navigat this.settingsRepository = new HtmlModuleSettingsRepository(); } - public enum WorkflowType - { -#pragma warning disable SA1602 // Enumeration items should be documented - DirectPublish = 1, -#pragma warning restore SA1602 // Enumeration items should be documented -#pragma warning disable SA1602 // Enumeration items should be documented - ContentStaging = 2, -#pragma warning restore SA1602 // Enumeration items should be documented - } - [HttpPost] [ValidateAntiForgeryToken] public ActionResult Save(EditHtmlViewModel model) { try { + // get content int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); - htmlContent.Content = model.EditorContent; + var aliases = from PortalAliasInfo pa in PortalAliasController.Instance.GetPortalAliasesByPortalId(this.PortalSettings.PortalId) + select pa.HTTPAlias; + + var content = model.EditorContent; - var draftStateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); - var publishedStateID = this.workflowStateController.GetLastWorkflowStateID(workflowID); + if (this.Request.QueryString["nuru"] == null) + { + content = HtmlUtils.AbsoluteToRelativeUrls(content, aliases); + } + + htmlContent.Content = content; + var workflow = this.workflowManager.GetWorkflow(workflowID); + var draftStateID = workflow.FirstState.StateID; + var publishedStateID = workflow.LastState.StateID; - switch (model.WorkflowType) + switch (model.CurrentWorkflowType) { case WorkflowType.DirectPublish: - this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(model.PortalId)); + break; - case WorkflowType.ContentStaging: - if (model.Publish) - { - // if it's already published set it to draft - if (htmlContent.StateID == publishedStateID) - { - htmlContent.StateID = draftStateID; - } - else - { - htmlContent.StateID = publishedStateID; - - // here it's in published mode - } - } - else + case WorkflowType.SaveDraft: + case WorkflowType.ContentApproval: + // if it's already published set it back to draft + if (htmlContent.StateID == publishedStateID) { - // if it's already published set it back to draft - if (htmlContent.StateID != draftStateID) - { - htmlContent.StateID = draftStateID; - } + htmlContent.StateID = draftStateID; } - this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); + this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(model.PortalId)); break; } @@ -158,13 +144,7 @@ public ActionResult ShowPreview(EditHtmlViewModel model) // model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(this.ActiveModule.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(this.ActiveModule.ModuleControl.ControlSrc)); try { - int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; - var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); - - var moduleSettings = this.settingsRepository.GetSettings(this.ActiveModule); - model.PreviewContent = HtmlTextController.FormatHtmlText(model.ModuleId, htmlContent.Content, moduleSettings, this.PortalSettings, null); - - // return this.PartialView(this.ActiveModule, "_History", model); + model.PreviewContent = model.EditorContent; // HttpUtility.HtmlDecode(model.HiddenEditorContent); return this.PartialView(this.ActiveModule, "EditHtml", model); } catch (Exception exc) @@ -182,36 +162,6 @@ public ActionResult ShowEdit(EditHtmlViewModel model) model.RedirectUrl = this.NavigationManager.NavigateURL(model.TabId); try { - int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; - var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); - - model.ShowPublishOption = model.WorkflowType != WorkflowType.DirectPublish; - model.ShowCurrentVersion = model.WorkflowType != WorkflowType.DirectPublish; - - var workflowStates = this.workflowStateController.GetWorkflowStates(workflowID); - var maxVersions = this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId); - - var htmlContentItemID = -1; - - if (htmlContent != null) - { - htmlContentItemID = htmlContent.ItemID; - var html = System.Web.HttpUtility.HtmlDecode(htmlContent.Content); - model.EditorContent = html; - } - - model.MaxVersions = maxVersions; - - model.WorkflowType = workflowStates.Count == 1 ? WorkflowType.DirectPublish : WorkflowType.ContentStaging; - if (htmlContentItemID != -1) - { - this.PopulateModelWithContent(model, htmlContent); - } - else - { - this.PopulateModelWithInitialContent(model, workflowStates[0] as WorkflowStateInfo); - } - // return this.PartialView(this.ActiveModule, "_Edit", model); return this.PartialView(this.ActiveModule, "EditHtml", model); } @@ -225,10 +175,7 @@ public ActionResult ShowEdit(EditHtmlViewModel model) public ActionResult HistoryRemove(EditHtmlViewModel model) { - // int workflowID = this.htmlTextController.GetWorkflow(model.ModuleId, model.TabId, this.PortalSettings.PortalId).Value; - // var htmlContent = this.GetLatestHTMLContent(workflowID, model.ModuleId); this.htmlTextController.DeleteHtmlText(model.ModuleId, model.ItemID); - return this.ShowEdit(model); } @@ -239,7 +186,7 @@ public ActionResult HistoryRollback(EditHtmlViewModel model) htmlContent.ItemID = -1; htmlContent.ModuleID = model.ModuleId; htmlContent.WorkflowID = workflowID; - htmlContent.StateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); + htmlContent.StateID = this.workflowManager.GetWorkflow(workflowID).FirstState.StateID; this.htmlTextController.UpdateHtmlText(htmlContent, this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId)); return this.ShowEdit(model); } @@ -306,14 +253,6 @@ public ActionResult UpdateSettings(HtmlModuleSettingsModel model) } } - private List GetWorkflows() - { - // Récupérer les workflows disponibles - var workflowStateController = new WorkflowStateController(); - var workflows = workflowStateController.GetWorkflows(this.ActiveModule.PortalID); - return workflows.Cast().Where(w => !w.IsDeleted).ToList(); // Filtrer les workflows non supprimés - } - private void UpdateWorkflow(string selectedWorkflow, string applyTo, bool replace) { var htmlTextController = new HtmlTextController(this.NavigationManager); @@ -360,7 +299,7 @@ private HtmlTextInfo GetLatestHTMLContent(int workflowID, int moduleId) { htmlContent = new HtmlTextInfo(); htmlContent.ItemID = -1; - htmlContent.StateID = this.workflowStateController.GetFirstWorkflowStateID(workflowID); + htmlContent.StateID = this.workflowManager.GetWorkflow(workflowID).FirstState.StateID; htmlContent.WorkflowID = workflowID; htmlContent.ModuleID = moduleId; } @@ -377,10 +316,10 @@ private void PopulateModelWithContent(EditHtmlViewModel model, HtmlTextInfo html // model.Content = this.FormatContent(htmlContent.Content); } - private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowStateInfo firstState) + private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowState firstState) { // model.EditorContent = this.LocalizeString("AddContent"); - model.CurrentWorkflowInUse = firstState.WorkflowName; + model.CurrentWorkflowInUse = firstState.StateName; model.ShowCurrentWorkflowState = false; model.ShowCurrentVersion = false; } diff --git a/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs b/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs index ffd469cdca0..13ce0022889 100644 --- a/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs +++ b/DNN Platform/Modules/HTML/Controllers/HTMLEditHTMLViewController.cs @@ -14,7 +14,9 @@ namespace DotNetNuke.Modules.Html.Controllers using DotNetNuke.Abstractions; using DotNetNuke.Common; + using DotNetNuke.Common.Utilities; using DotNetNuke.ContentSecurityPolicy; + using DotNetNuke.Entities.Content.Workflow; using DotNetNuke.Entities.Content.Workflow.Entities; using DotNetNuke.Entities.Modules; using DotNetNuke.Entities.Modules.Settings; @@ -22,51 +24,43 @@ namespace DotNetNuke.Modules.Html.Controllers using DotNetNuke.Modules.Html; using DotNetNuke.Modules.Html.Components; using DotNetNuke.Modules.Html.Models; - using DotNetNuke.Mvc; + using DotNetNuke.Security; using DotNetNuke.Services.Exceptions; using DotNetNuke.Services.Localization; using DotNetNuke.Web.Client.ClientResourceManagement; - using DotNetNuke.Web.Mvc; - - // using DotNetNuke.Web.Mvc; using DotNetNuke.Web.MvcPipeline.Controllers; - using DotNetNuke.Website.Controllers; using Microsoft.Extensions.DependencyInjection; - using static DotNetNuke.Modules.Html.Controllers.DNN_HTMLController; - - public class HTMLEditHTMLViewController : ModuleViewControllerBase + public partial class HTMLEditHTMLViewController : ModuleViewControllerBase { private readonly INavigationManager navigationManager; private readonly HtmlTextController htmlTextController; - private readonly WorkflowStateController workflowStateController = new WorkflowStateController(); + private readonly IWorkflowManager workflowManager = WorkflowManager.Instance; private readonly IContentSecurityPolicy contentSecurityPolicy; public HTMLEditHTMLViewController(IContentSecurityPolicy csp) { this.navigationManager = Globals.DependencyProvider.GetRequiredService(); this.htmlTextController = new HtmlTextController(this.navigationManager); - - // this.contentSecurityPolicy = Globals.DependencyProvider.GetRequiredService(); + this.navigationManager = Globals.DependencyProvider.GetRequiredService(); + this.contentSecurityPolicy = Globals.DependencyProvider.GetRequiredService(); this.contentSecurityPolicy = csp; } protected override object ViewModel(ModuleInfo module) { var model = new EditHtmlViewModel(); - - // model.LocalResourceFile = "/DesktopModules/Html/" + Localization.LocalResourceDirectory + "/EditHTML"; - model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(module.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(module.ModuleControl.ControlSrc)); - model.ShowEditView = true; - model.ModuleId = module.ModuleID; - model.TabId = module.TabID; - model.PortalId = this.PortalSettings.PortalId; - model.RedirectUrl = this.navigationManager.NavigateURL(); - int workflowID = this.htmlTextController.GetWorkflow(module.ModuleID, module.TabID, module.PortalID).Value; - try { - var htmlContentItemID = -1; + model.LocalResourceFile = Path.Combine(Path.GetDirectoryName(module.ModuleControl.ControlSrc), Localization.LocalResourceDirectory + "/" + Path.GetFileNameWithoutExtension(module.ModuleControl.ControlSrc)); + model.ShowEditView = true; + model.ModuleId = module.ModuleID; + model.TabId = module.TabID; + model.PortalId = module.PortalID; + model.RedirectUrl = this.navigationManager.NavigateURL(); + int workflowID = this.htmlTextController.GetWorkflow(module.ModuleID, module.TabID, module.PortalID).Value; + + var htmlContentItemID = Null.NullInteger; var htmlContent = this.htmlTextController.GetTopHtmlText(module.ModuleID, false, workflowID); if (htmlContent != null) @@ -76,32 +70,43 @@ protected override object ViewModel(ModuleInfo module) model.EditorContent = html; } - var workflowStates = this.workflowStateController.GetWorkflowStates(workflowID); - var maxVersions = this.htmlTextController.GetMaximumVersionHistory(this.PortalSettings.PortalId); + var workflow = this.workflowManager.GetWorkflow(workflowID); + var workflowStates = workflow.States.ToList(); + model.MaxVersions = this.htmlTextController.GetMaximumVersionHistory(module.PortalID); + var userCanEdit = this.UserInfo.IsSuperUser || PortalSecurity.IsInRole(this.PortalSettings.AdministratorRoleName); - model.MaxVersions = maxVersions; + model.PageSize = Math.Min(Math.Max(model.MaxVersions, 5), 10); // min 5, max 10 + + switch (workflow.WorkflowKey) + { + case SystemWorkflowManager.DirectPublishWorkflowKey: + model.CurrentWorkflowType = WorkflowType.DirectPublish; + break; + case SystemWorkflowManager.SaveDraftWorkflowKey: + model.CurrentWorkflowType = WorkflowType.SaveDraft; + break; + case SystemWorkflowManager.ContentAprovalWorkflowKey: + model.CurrentWorkflowType = WorkflowType.ContentApproval; + break; + } - model.WorkflowType = workflowStates.Count == 1 ? WorkflowType.DirectPublish : WorkflowType.ContentStaging; if (htmlContentItemID != -1) { this.PopulateModelWithContent(model, htmlContent); } else { - this.PopulateModelWithInitialContent(model, workflowStates[0] as WorkflowStateInfo); + this.PopulateModelWithInitialContent(model, workflowStates[0]); } - model.ShowPublishOption = model.WorkflowType != WorkflowType.DirectPublish; - model.ShowCurrentVersion = model.WorkflowType != WorkflowType.DirectPublish; - model.ShowPreviewVersion = model.WorkflowType != WorkflowType.DirectPublish; + model.ShowPublishOption = model.CurrentWorkflowType != WorkflowType.DirectPublish; + model.ShowCurrentVersion = model.CurrentWorkflowType != WorkflowType.DirectPublish; + model.ShowPreviewVersion = model.CurrentWorkflowType != WorkflowType.DirectPublish; model.ShowHistoryView = false; model.ShowMasterContentButton = false; - - // model.RenderOptions = this.GetRenderOptions(); } catch (Exception exc) { - // Gérer l'exception // Exceptions.ProcessModuleLoadException(this, exc); throw new Exception("EditHTML", exc); } @@ -126,10 +131,10 @@ private void PopulateModelWithContent(EditHtmlViewModel model, HtmlTextInfo html // model.Content = this.FormatContent(htmlContent.Content); } - private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowStateInfo firstState) + private void PopulateModelWithInitialContent(EditHtmlViewModel model, WorkflowState firstState) { // model.EditorContent = this.LocalizeString("AddContent"); - model.CurrentWorkflowInUse = firstState.WorkflowName; + model.CurrentWorkflowInUse = firstState.StateName; model.ShowCurrentWorkflowState = false; model.ShowCurrentVersion = false; } diff --git a/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj b/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj index 15d9ba1c631..00751793835 100644 --- a/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj +++ b/DNN Platform/Modules/HTML/DotNetNuke.Modules.Html.csproj @@ -81,7 +81,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - + False ..\..\DotNetNuke.Instrumentation\bin\DotNetNuke.Instrumentation.dll @@ -116,14 +116,28 @@ ..\..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + - - ..\..\..\Packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + + ..\..\packages\Microsoft.AspNet.Mvc.5.3.0\lib\net45\System.Web.Mvc.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.Helpers.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\..\packages\Microsoft.AspNet.WebPages.3.3.0\lib\net45\System.Web.WebPages.Razor.dll @@ -145,7 +159,11 @@ + + + + EditHtml.ascx ASPXCodeBehind @@ -160,6 +178,7 @@ HtmlModule.ascx + @@ -197,6 +216,7 @@ + @@ -244,6 +264,10 @@ {6928A9B1-F88A-4581-A132-D3EB38669BB0} DotNetNuke.Abstractions + + {33C9571D-E3AF-46FC-DC75-9CA082BBDC3D} + DotNetNuke.ContentSecurityPolicy + {AA3EE19B-81A0-3766-E8F4-424C2425A8D3} DotNetNuke.Web.MvcPipeline diff --git a/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs b/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs index cd351ece8e8..798807a7c7a 100644 --- a/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs +++ b/DNN Platform/Modules/HTML/Models/EditHtmlViewModel.cs @@ -10,8 +10,6 @@ namespace DotNetNuke.Modules.Html.Models using DotNetNuke.Web.Mvc.Page; using DotNetNuke.Web.MvcPipeline.Models; - using static DotNetNuke.Modules.Html.Controllers.DNN_HTMLController; - public class EditHtmlViewModel : ModuleModelBase { [AllowHtml] @@ -42,7 +40,7 @@ public class EditHtmlViewModel : ModuleModelBase public bool Publish { get; set; } - public WorkflowType WorkflowType { get; set; } + public WorkflowType CurrentWorkflowType { get; set; } public int MaxVersions { get; set; } @@ -69,5 +67,9 @@ public class EditHtmlViewModel : ModuleModelBase public int PortalId { get; set; } public string PreviewContent { get; set; } + + public int PageSize { get; set; } + + public bool ShowHistoryButton { get; set; } } } diff --git a/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs b/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs index f87feebe452..cac43487140 100644 --- a/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs +++ b/DNN Platform/Modules/HTML/Models/HtmlModuleSettingsModel.cs @@ -13,24 +13,16 @@ namespace DotNetNuke.Modules.Html.Models public class HtmlModuleSettingsModel : ModuleSettingsModel { - [Display(Name = "plReplaceTokens")] // ID de Settings.ascx public bool ReplaceTokens { get; set; } - [Display(Name = "plDecorate")] // ID de Settings.ascx public bool UseDecorate { get; set; } - [Display(Name = "plSearchDescLength")] // ID de Settings.ascx public int SearchDescLength { get; set; } - public List Workflows { get; set; } - - [Display(Name = "plWorkflow")] // ID de Settings.ascx public string SelectedWorkflow { get; set; } - [Display(Name = "plApplyTo")] // ID de Settings.ascx public string ApplyTo { get; set; } - [Display(Name = "chkReplace")] // ID de Settings.ascx public bool Replace { get; set; } } } diff --git a/DNN Platform/Modules/HTML/Models/WorkflowType.cs b/DNN Platform/Modules/HTML/Models/WorkflowType.cs new file mode 100644 index 00000000000..2742fabc6c6 --- /dev/null +++ b/DNN Platform/Modules/HTML/Models/WorkflowType.cs @@ -0,0 +1,27 @@ +// 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.Modules.Html.Models +{ + /// + /// Définit les différents types de workflows disponibles pour la gestion du contenu HTML. + /// + public enum WorkflowType + { + /// + /// Le contenu est publié directement sans validation intermédiaire. + /// + DirectPublish = 1, + + /// + /// Le contenu est sauvegardé comme brouillon avant publication. + /// + SaveDraft = 2, + + /// + /// Le contenu doit passer par un processus d'approbation avant publication. + /// + ContentApproval = 3, + } +} diff --git a/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs b/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs index d0c9fd96e0f..d20d7c04b60 100644 --- a/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs +++ b/DNN Platform/Modules/HTML/Mvc/HtmlModuleControl.cs @@ -21,8 +21,6 @@ public class HtmlModuleControl : ModuleControlBase, IActionable { private readonly INavigationManager navigationManager; - // private bool editorEnabled; - // private int workflowID; public HtmlModuleControl() { this.navigationManager = this.DependencyProvider.GetRequiredService(); @@ -43,7 +41,7 @@ public ModuleActionCollection ModuleActions ModuleActionType.AddContent, string.Empty, string.Empty, - this.EditUrl(/*"mvcpage", "yes"*/), + this.EditUrl("mvcpage", "yes"), false, SecurityAccessLevel.Edit, true, diff --git a/DNN Platform/Modules/HTML/Views/EditHTML.cshtml b/DNN Platform/Modules/HTML/Views/EditHTML.cshtml index 42829c76868..ebf13ee9d83 100644 --- a/DNN Platform/Modules/HTML/Views/EditHTML.cshtml +++ b/DNN Platform/Modules/HTML/Views/EditHTML.cshtml @@ -8,16 +8,23 @@ { @Html.AntiForgeryToken() @Html.HiddenFor(x => x.TabId) + @Html.HiddenFor(x => x.PortalId) @Html.HiddenFor(x => x.ModuleId) - @Html.HiddenFor(x => x.WorkflowType) + @Html.HiddenFor(x => x.CurrentWorkflowType) +
    @if (Model.ShowEditView) { @Html.Partial("~/DeskTopModules/Html/Views/_Edit.cshtml", Model) } + else + { + @Html.HiddenFor(x => x.EditorContent) + } @if (Model.ShowPreviewView) { +
    @if (Model.ShowPreviewVersion) { @@ -38,14 +45,7 @@ @Html.Raw(Model.PreviewContent)
    - @* -

    @Localization.GetString("dshHistory", Model.LocalResourceFile)

    -
    - Html.Partial("_HistoryGrid", Model.HistoryItems) -
    - *@ } - @if (Model.ShowHistoryView) {
    @@ -69,13 +69,13 @@ @Localization.GetString("cmdCancel", Model.LocalResourceFile)
  • - +
  • - +
  • - +
  • diff --git a/DNN Platform/Modules/HTML/Views/web.config b/DNN Platform/Modules/HTML/Views/web.config index b9c29a8fc27..a7751b2f5f9 100644 --- a/DNN Platform/Modules/HTML/Views/web.config +++ b/DNN Platform/Modules/HTML/Views/web.config @@ -9,7 +9,7 @@ - + diff --git a/DNN Platform/Modules/HTML/edit.css b/DNN Platform/Modules/HTML/edit.css index 997238d5869..59f949bae7c 100644 --- a/DNN Platform/Modules/HTML/edit.css +++ b/DNN Platform/Modules/HTML/edit.css @@ -8,7 +8,7 @@ .divCurrentVersion{min-height:61px;} .ehActions {height:60px;} .ehccContent fieldset:last-child {margin-bottom:0;} -.ehCurrentContent {overflow: scroll;} +.ehCurrentContent {overflow: auto;} .dnnTextEditor {margin-bottom:0;} .dnnTextPanelView-basic {padding-right: 0 !important;border: 0 !important;} .ehActions .dnnFormItem select {width:auto;margin:0;} @@ -25,4 +25,16 @@ border-radius: 3px; font-weight: normal; text-align: center; +} + +.dnnFormItem button, +.dnnFormItem input[type=button], +.dnnFormItem input[type=reset], +.dnnFormItem input[type=submit], +.dnnLogin .LoginTabGroup span, +.dnnPrimaryAction, +.dnnSecondaryAction, +.dnnTertiaryAction { + font-size: var(--dnn-base-font-size, 16px); + line-height: 24px; } \ No newline at end of file diff --git a/DNN Platform/Modules/HTML/js/edit.js b/DNN Platform/Modules/HTML/js/edit.js new file mode 100644 index 00000000000..bd7878c7a26 --- /dev/null +++ b/DNN Platform/Modules/HTML/js/edit.js @@ -0,0 +1,230 @@ +$(function () { + + var portalId = $('#dnnEditHtml').attr('data-portalid'); + var tabId = $('#dnnEditHtml').attr('data-tabid'); + var moduleId = $('#dnnEditHtml').attr('data-moduleid'); + var urlpars = 'tabid=' + tabId + '&PortalID=' + portalId + '&mid=' + moduleId; + + // window.CKEDITOR_BASEPATH = '/Providers/HtmlEditorProviders/DNNConnect.CKE/js/ckeditor/4.18.0/'; + + var editorConfigeditortxtContent = { + //allowedContent: false, + //allowedContent: true, + justifyClasses: ['text-left', 'text-center', 'text-right', 'text-justify'], + allowedContent: 'h1 h2 strong u sub sup em ol label hr blockquote table theader th tbody tr td; a[!href](*); img[!src,alt,width,height](*); div(*);ul(*); li(*);span(*);p(*)', + autoGrow_bottomSpace: 0, + autoGrow_maxHeight: 0, + autoGrow_minHeight: 200, autoGrow_onStartup: false, autoParagraph: true, + autosave_delay: 25, autoUpdateElement: true, baseFloatZIndex: 10000, + basicEntities: true, + blockedKeystrokes: [CKEDITOR.CTRL + 66, CKEDITOR.CTRL + 73, CKEDITOR.CTRL + 85], + browserContextMenuOnCtrl: true, clipboard_defaultContentType: 'html', + codemirror: { + theme: 'default', lineNumbers: true, lineWrapping: true, + matchBrackets: true, autoCloseTags: false, enableSearchTools: true, + enableCodeFolding: true, enableCodeFormatting: true, autoFormatOnStart: false, + autoFormatOnUncomment: true, highlightActiveLine: true, + highlightMatches: true, showTabs: false, showFormatButton: true, + showCommentButton: true, showUncommentButton: true + }, + colorButton_colors: '00923E,F8C100,28166F', + colorButton_enableMore: true, + contentsLangDirection: '', + dataIndentationChars: ' ', + defaultLanguage: 'en', defaultLinkType: 'url', + dialog_backgroundCoverColor: 'white', dialog_backgroundCoverOpacity: '0.5', + dialog_buttonsOrder: 'OS', dialog_magnetDistance: 20, dialog_noConfirmCancel: 0, + dialog_startupFocusTab: false, disableNativeSpellChecker: true, + disableNativeTableHandles: true, disableObjectResizing: false, + disableReadonlyStyling: false, div_wrapTable: false, + docType: '', enableTabKeyTools: true, + enterMode: 1, entities: true, entities_additional: '#39', + entities_greek: false, entities_latin: false, + entities_processNumerical: false, + extraPlugins: 'dnnpages,wordcount,notification', + filebrowserWindowFeatures: 'location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes', + fillEmptyBlocks: true, flashAddEmbedTag: false, + flashConvertOnEdit: false, flashEmbedTagOnly: false, + floatSpaceDockedOffsetX: 0, floatSpaceDockedOffsetY: 0, floatSpacePinnedOffsetX: 0, + floatSpacePinnedOffsetY: 0, fontSize_sizes: '12px;2.3em;130%;larger;x-small', + font_names: 'Arial;Times New Roman;Verdana', forceEnterMode: false, + forcePasteAsPlainText: false, forceSimpleAmpersand: false, + format_tags: 'p;h1;h2;h3;h4;h5;h6;pre;address;div', fullPage: false, + htmlEncodeOutput: false, ignoreEmptyParagraph: true, + image_previewText: 'Lorem ipsum dolor...', image_removeLinkByEmptyURL: true, + indentOffset: 40, indentUnit: 'px', linkJavaScriptLinksAllowed: false, + linkShowAdvancedTab: true, linkShowTargetTab: true, magicline_color: '#FF0000', + magicline_holdDistance: '0.5', magicline_putEverywhere: false, + magicline_triggerOffset: 30, + menu_groups: 'clipboard,tablecell,tablecellproperties,tablerow,tablecolumn,table,anchor,link,image,flash,checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div', menu_subMenuDelay: 400, + oembed_maxWidth: 0, oembed_maxHeight: 0, pasteFromWordNumberedHeadingToList: false, + pasteFromWordPromptCleanup: false, pasteFromWordRemoveFontStyles: true, + pasteFromWordRemoveStyles: true, + protectedSource: [(/[\s\S]*?<\/i>/gi), (/[\s\S]*?<\/span>/gi), (/[\s\S]*?<\/em>/gi), (/