From fc23af89d30e65f29d87ed7cc13006a667f9bca3 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 10 Apr 2025 11:54:58 -0400 Subject: [PATCH 01/78] Update README.md --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a0fc61e..a69b4390 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline # Latest Release -[6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) was released on March 12, 2025 and is a maintenance release including 46 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 6400. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) was released on April 10, 2025 and is a maintenance release including 41 pull requests by 3 different contributors, pushing the total number of project commits all-time to over 6500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -22,11 +22,11 @@ Microsoft's Public Cloud (requires an Azure account) A free ASP.NET hosting account. No hidden fees. No credit card required. [![Deploy to MonsterASP.NET](https://www.oqtane.org/files/Public/MonsterASPNET.png)](https://www.monsterasp.net/) -# Getting Started (Version 6.1.1) +# Getting Started (Version 6.1.2) **Installing using source code from the Dev/Master branch:** -- Install **[.NET 9.0.3 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. +- Install **[.NET 9.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. - Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. @@ -92,6 +92,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan # Roadmap This project is open source, and therefore is a work in progress... +[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) (Apr 10, 2025) +- [x] Stabilization improvements + [6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) (Mar 12, 2025) - [x] Stabilization improvements - [x] Cookie Consent Banner & Privacy/Terms @@ -165,7 +168,7 @@ The following diagram visualizes the client and server components in the Oqtane # Databases -As of version 2.1 (June 2021) Oqtane supports multiple relational database providers - SQL Server, SQLite, MySQL, PostgreSQL +Oqtane supports multiple relational database providers - SQL Server, SQLite, MySQL, PostgreSQL ![Databases](https://github.com/oqtane/framework/blob/dev/screenshots/databases.png?raw=true "Oqtane Databases") From cc906d49bad7d56381fe3eff553ff71342ad3f90 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 10 Apr 2025 12:04:26 -0400 Subject: [PATCH 02/78] update Deploy To Azure to the 6.1.2 release --- azuredeploy.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azuredeploy.json b/azuredeploy.json index 461130fc..e57cc1f8 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -220,7 +220,7 @@ "apiVersion": "2024-04-01", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]", "properties": { - "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.1/Oqtane.Framework.6.1.1.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]" From 7840230c621cbb64d658cad5284869f6bbd941dc Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 10 Apr 2025 12:27:21 -0400 Subject: [PATCH 03/78] backup parameter needs to be backward compatible --- Oqtane.Updater/Program.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Oqtane.Updater/Program.cs b/Oqtane.Updater/Program.cs index 5cac25f6..e99ac13f 100644 --- a/Oqtane.Updater/Program.cs +++ b/Oqtane.Updater/Program.cs @@ -15,19 +15,25 @@ namespace Oqtane.Updater /// static void Main(string[] args) { - // requires 3 arguments - the ContentRootPath, the WebRootPath of the site, and a backup flag + // note additional arguments must be added in a backward compatible manner as older versions will not pass them + // requires 2 arguments - the ContentRootPath and the WebRootPath of the site // for testing purposes you can uncomment and modify the logic below //Array.Resize(ref args, 3); //args[0] = @"C:\yourpath\oqtane.framework\Oqtane.Server"; //args[1] = @"C:\yourpath\oqtane.framework\Oqtane.Server\wwwroot"; - //args[2] = @"true"; + //args[2] = @"true"; // parameter added in 6.1.2 - if (args.Length == 3) + if (args.Length >= 2) { string contentrootfolder = args[0]; string webrootfolder = args[1]; - bool backup = bool.Parse(args[2]); + + bool backup = true; + if (args.Length >= 3) + { + backup = bool.Parse(args[2]); + } string deployfolder = Path.Combine(contentrootfolder, "Packages"); string backupfolder = Path.Combine(contentrootfolder, "Backup"); From 46431f018736db8c07a5b835720e3a1c0c7e68a8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 10 Apr 2025 14:36:42 -0400 Subject: [PATCH 04/78] fix issue with new UserProfile parameters --- Oqtane.Client/Themes/Controls/Theme/UserProfile.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor index 9c3802b4..d408b2ae 100644 --- a/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor +++ b/Oqtane.Client/Themes/Controls/Theme/UserProfile.razor @@ -60,11 +60,11 @@ if (!string.IsNullOrEmpty(ProfileUrl)) { - _registerurl = ProfileUrl + "?returnurl=" + (ProfileUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); + _profileurl = ProfileUrl + "?returnurl=" + (ProfileUrl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl); } else { - _registerurl = NavigateUrl("profile", "returnurl=" + _returnurl); + _profileurl = NavigateUrl("profile", "returnurl=" + _returnurl); } } } From 1a1e9ac6becd3296261a2cba6979c5efa0d5a6bb Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Fri, 11 Apr 2025 09:24:18 +0200 Subject: [PATCH 05/78] Swagger Updated to latest Swashbuckle.AspNetCore update from 8.1.0 > 8.1.1 --- Oqtane.Server/Oqtane.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 1ae9c302..a57cac0e 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -48,7 +48,7 @@ - + From 26a686c41257266e8369f9a466bdd91e1760d0fe Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 11 Apr 2025 15:54:02 -0400 Subject: [PATCH 06/78] .NET MAUI client was changed from 0.0.0.0 to 0.0.0.1 in .NET 9 --- Oqtane.Server/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 8c99ef7f..51b60555 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -136,7 +136,7 @@ namespace Oqtane policy => { // allow .NET MAUI client cross origin calls - policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0") + policy.WithOrigins("https://0.0.0.1", "http://0.0.0.1", "app://0.0.0.1") .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); From 95cb5dd66c465d499cb218fd56d8d2089e085647 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 15 Apr 2025 09:20:18 -0400 Subject: [PATCH 07/78] UX improvements for System Update --- .../Modules/Admin/Upgrade/Index.razor | 22 ++++++++++++------- .../Modules/Admin/Upgrade/Index.resx | 7 ++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index 31fee382..aeda1366 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -15,7 +15,7 @@ {
- +
@@ -52,15 +57,15 @@
+
-

- } @code { private bool _initialized = false; + private bool _downloaded = false; private Package _package; private bool _upgradeavailable = false; private string _backup = "True"; @@ -125,6 +130,7 @@ ShowProgressIndicator(); await PackageService.DownloadPackageAsync(packageid, version); await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version); + _downloaded = true; HideProgressIndicator(); AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success); } diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index e609f42a..a38e830c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -124,7 +124,7 @@ Error Downloading Framework Package - Upload a framework package and select Install to complete the installation + Upload A Framework Package (Oqtane.Framework.#.#.#.nupkg) And Then Select Upgrade Framework: @@ -144,9 +144,6 @@ Framework Is Already Up To Date - - Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade - You Cannot Perform A System Update In A Development Environment @@ -157,6 +154,6 @@ Backup Files? - Specify if you want to backup files during the upgrade process. Disabling this option will result in a better experience in some environments. + Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade. \ No newline at end of file From 4d572d8173881206890400f37a9d825d3a6dd198 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 10:48:48 +0200 Subject: [PATCH 08/78] Allow earlier return in files server --- Oqtane.Server/Pages/Files.cshtml.cs | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index b241388e..97fc2c57 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -112,7 +112,7 @@ namespace Oqtane.Pages url += Request.QueryString.Value.Substring(1); } - + return RedirectPermanent(url); } @@ -137,6 +137,34 @@ namespace Oqtane.Pages string downloadName = file.Name; string filepath = _files.GetFilePath(file); + if (Request.QueryString.HasValue) + { + etag = Utilities.GenerateSimpleHash(Request.QueryString.Value); + } + else + { + etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); + } + + var header = ""; + if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) + { + header = ifNoneMatch.ToString(); + } + + if (header.Equals(etag)) + { + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; + return Content(String.Empty); + } + + if (!System.IO.File.Exists(filepath)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return BrokenFile(); + } + // evaluate any querystring parameters bool isRequestingImageManipulation = false; @@ -165,34 +193,6 @@ namespace Oqtane.Pages isRequestingImageManipulation = true; } - if (isRequestingImageManipulation) - { - etag = Utilities.GenerateSimpleHash(Request.QueryString.Value); - } - else - { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); - } - - var header = ""; - if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) - { - header = ifNoneMatch.ToString(); - } - - if (header.Equals(etag)) - { - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; - return Content(String.Empty); - } - - if (!System.IO.File.Exists(filepath)) - { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; - return BrokenFile(); - } - if (isRequestingImageManipulation) { var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue; From 1b00fa74bcbabdf4e854a660fd92788496b08143 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 11:14:24 +0200 Subject: [PATCH 09/78] Compute file server etag with MD5 and always include ModifiedOn --- Oqtane.Server/Pages/Files.cshtml.cs | 10 +++++----- Oqtane.Shared/Shared/Utilities.cs | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index 97fc2c57..7be27e89 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -137,15 +137,15 @@ namespace Oqtane.Pages string downloadName = file.Name; string filepath = _files.GetFilePath(file); + var etagInput = $"{file.ModifiedOn.Ticks}:{file.Size}"; + if (Request.QueryString.HasValue) { - etag = Utilities.GenerateSimpleHash(Request.QueryString.Value); - } - else - { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); + etagInput += $":{Request.QueryString.Value}"; } + etag = Utilities.GenerateHashMD5(etagInput); + var header = ""; if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) { diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 826fac70..d38fe490 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using File = Oqtane.Models.File; @@ -619,6 +620,13 @@ namespace Oqtane.Shared } } + public static string GenerateHashMD5(string input) + { + var bytes = Encoding.UTF8.GetBytes(input); + var hashBytes = MD5.HashData(bytes); + return Convert.ToHexString(hashBytes); + } + [Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)] public static string ContentUrl(Alias alias, int fileId) { From e7acd14faa9a3021ca3099f6979835503e098a0f Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 15:51:25 +0200 Subject: [PATCH 10/78] Replace MD5 hash with a longer simple hash --- Oqtane.Server/Pages/Files.cshtml.cs | 2 +- Oqtane.Shared/Shared/Utilities.cs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index 7be27e89..551be05f 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -144,7 +144,7 @@ namespace Oqtane.Pages etagInput += $":{Request.QueryString.Value}"; } - etag = Utilities.GenerateHashMD5(etagInput); + etag = Utilities.GenerateSimpleHash16(etagInput); var header = ""; if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index d38fe490..30e25bb2 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -620,11 +620,17 @@ namespace Oqtane.Shared } } - public static string GenerateHashMD5(string input) + public static string GenerateSimpleHash16(string text) { - var bytes = Encoding.UTF8.GetBytes(input); - var hashBytes = MD5.HashData(bytes); - return Convert.ToHexString(hashBytes); + unchecked // prevent overflow exception + { + long hash = 23; + foreach (char c in text) + { + hash = hash * 31 + c; + } + return hash.ToString("X16"); + } } [Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)] From 430f83e8e9d034b7edee27da3749342bec31b462 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 16:46:55 +0200 Subject: [PATCH 11/78] Only compute hash when file has query string --- Oqtane.Server/Pages/Files.cshtml.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index 551be05f..4105262e 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -137,14 +137,14 @@ namespace Oqtane.Pages string downloadName = file.Name; string filepath = _files.GetFilePath(file); - var etagInput = $"{file.ModifiedOn.Ticks}:{file.Size}"; - if (Request.QueryString.HasValue) { - etagInput += $":{Request.QueryString.Value}"; + etag = Utilities.GenerateSimpleHash16($"{file.ModifiedOn.Ticks}:{file.Size}:{Request.QueryString.Value}"); + } + else + { + etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); } - - etag = Utilities.GenerateSimpleHash16(etagInput); var header = ""; if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) From d888d83a986185e1109fb27c74f20086b6308ea6 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 18:44:16 +0200 Subject: [PATCH 12/78] Simplify files etag calculation --- Oqtane.Server/Pages/Files.cshtml.cs | 11 +---------- Oqtane.Shared/Shared/Utilities.cs | 13 ------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index 4105262e..f01cd0b7 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -133,19 +133,10 @@ namespace Oqtane.Pages } } - string etag; + string etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); string downloadName = file.Name; string filepath = _files.GetFilePath(file); - if (Request.QueryString.HasValue) - { - etag = Utilities.GenerateSimpleHash16($"{file.ModifiedOn.Ticks}:{file.Size}:{Request.QueryString.Value}"); - } - else - { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); - } - var header = ""; if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) { diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 30e25bb2..ee4586b0 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -620,19 +620,6 @@ namespace Oqtane.Shared } } - public static string GenerateSimpleHash16(string text) - { - unchecked // prevent overflow exception - { - long hash = 23; - foreach (char c in text) - { - hash = hash * 31 + c; - } - return hash.ToString("X16"); - } - } - [Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)] public static string ContentUrl(Alias alias, int fileId) { From da7b0460921354ca5835615eef9a1c87d943cb46 Mon Sep 17 00:00:00 2001 From: David Montesinos Date: Mon, 21 Apr 2025 18:47:34 +0200 Subject: [PATCH 13/78] Remove extra using --- Oqtane.Shared/Shared/Utilities.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index ee4586b0..826fac70 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using File = Oqtane.Models.File; From e0044658f9767889b52389c148d198fbd30b662b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 21 Apr 2025 14:13:49 -0400 Subject: [PATCH 14/78] improve validation in Url Mapping --- .../Modules/Admin/UrlMappings/Add.razor | 44 ++++++++++--------- .../Modules/Admin/UrlMappings/Edit.razor | 5 ++- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor index e57ad13e..a0a7ac9f 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor @@ -26,35 +26,37 @@ @code { - private ElementReference form; - private bool validated = false; + private ElementReference form; + private bool validated = false; - private string _url = string.Empty; - private string _mappedurl = string.Empty; + private string _url = string.Empty; + private string _mappedurl = string.Empty; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; - private async Task SaveUrlMapping() - { - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(form)) - { - if (_url != _mappedurl) - { - var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; - url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + private async Task SaveUrlMapping() + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + if (_url != _mappedurl) + { + var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; + url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); - _url = (_url.StartsWith("/")) ? _url.Substring(1) : _url; - _url = (!_url.StartsWith("http")) ? url + _url : _url; + _url = (_url.StartsWith("/")) ? _url.Substring(1) : _url; + _url = (!_url.StartsWith("http")) ? url + _url : _url; - if (_url.StartsWith(url)) + _mappedurl = _mappedurl.Replace(url, ""); + _mappedurl = (_mappedurl.StartsWith("/")) ? _mappedurl.Substring(1) : _mappedurl; + + if (_url.StartsWith(url)) { var urlmapping = new UrlMapping(); urlmapping.SiteId = PageState.Site.SiteId; - var route = new Route(_url, PageState.Alias.Path); - urlmapping.Url = route.PagePath; - urlmapping.MappedUrl = _mappedurl.Replace(url, ""); + urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath; + urlmapping.MappedUrl = _mappedurl; urlmapping.Requests = 0; urlmapping.CreatedOn = DateTime.UtcNow; urlmapping.RequestedOn = DateTime.UtcNow; diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor index 10c9a57f..073f851d 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor @@ -67,8 +67,11 @@ var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + _mappedurl = _mappedurl.Replace(url, ""); + _mappedurl = (_mappedurl.StartsWith("/")) ? _mappedurl.Substring(1) : _mappedurl; + var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid); - urlmapping.MappedUrl = _mappedurl.Replace(url, ""); + urlmapping.MappedUrl = _mappedurl; urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping); await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); NavigationManager.NavigateTo(NavigateUrl()); From 4f16cd2d012c12a8ce802921c96db0f11e3fc0bd Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 21 Apr 2025 15:14:39 -0400 Subject: [PATCH 15/78] url mapping improvements --- .../Modules/Admin/UrlMappings/Add.razor | 77 +++++++++++-------- .../Modules/Admin/UrlMappings/Edit.razor | 2 +- .../Modules/Admin/UrlMappings/Add.resx | 9 ++- .../Modules/Admin/UrlMappings/Edit.resx | 2 +- Oqtane.Client/UI/SiteRouter.razor | 2 +- Oqtane.Server/Components/App.razor | 2 +- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor index a0a7ac9f..8f43985d 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor @@ -8,9 +8,12 @@
- +
- +
+ + +
@@ -49,43 +52,57 @@ _url = (!_url.StartsWith("http")) ? url + _url : _url; _mappedurl = _mappedurl.Replace(url, ""); - _mappedurl = (_mappedurl.StartsWith("/")) ? _mappedurl.Substring(1) : _mappedurl; + _mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl; if (_url.StartsWith(url)) - { - var urlmapping = new UrlMapping(); - urlmapping.SiteId = PageState.Site.SiteId; + { + var urlmapping = new UrlMapping(); + urlmapping.SiteId = PageState.Site.SiteId; urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath; - urlmapping.MappedUrl = _mappedurl; - urlmapping.Requests = 0; - urlmapping.CreatedOn = DateTime.UtcNow; - urlmapping.RequestedOn = DateTime.UtcNow; + urlmapping.MappedUrl = _mappedurl; + urlmapping.Requests = 0; + urlmapping.CreatedOn = DateTime.UtcNow; + urlmapping.RequestedOn = DateTime.UtcNow; - try - { - urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping); - await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); - NavigationManager.NavigateTo(NavigateUrl()); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); - AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); - } - } - else - { - AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning); - } - } - else - { + try + { + urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping); + await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); + NavigationManager.NavigateTo(NavigateUrl()); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); + AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); + } + } + else + { + AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning); + } + } + else + { AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning); - } + } } else { AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } + + private void GenerateUrl() + { + var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; + url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + + var chars = "abcdefghijklmnopqrstuvwxyz"; + Random rnd = new Random(); + for (int i = 0; i < 5; i++) + { + url += chars.Substring(rnd.Next(0, chars.Length - 1), 1); + } + _url = url; + } } diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor index 073f851d..78a062f9 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor @@ -68,7 +68,7 @@ url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); _mappedurl = _mappedurl.Replace(url, ""); - _mappedurl = (_mappedurl.StartsWith("/")) ? _mappedurl.Substring(1) : _mappedurl; + _mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl; var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid); urlmapping.MappedUrl = _mappedurl; diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx index e1185498..a8de1c59 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx @@ -1,4 +1,4 @@ - + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs new file mode 100644 index 00000000..7c4c9cbe --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Resources; +using Microsoft.Extensions.Localization; + +[assembly: RootNamespace("[Owner].Module.[Module].Server")] From 8aa967fa1b77140d68bd021a1a8830af91e5ff7b Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 6 May 2025 13:29:26 -0700 Subject: [PATCH 27/78] Remove Unnecessary Namespaces Removes Unnecessary Namespaces 'System' and 'System.ComponentModel.DataAnnotations.Schema;'. --- Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs index d4f49d37..024c266a 100644 --- a/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs +++ b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs @@ -1,7 +1,5 @@ -using System; using Oqtane.Models; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Oqtane.Documentation; namespace Oqtane.Modules.HtmlText.Models From 0d708124c204a9e869bf34ff2d4439a23012419d Mon Sep 17 00:00:00 2001 From: Ikuo Ohba Date: Wed, 7 May 2025 08:31:34 +0900 Subject: [PATCH 28/78] Undo Oqtane.Server.csproj --- Oqtane.Server/Oqtane.Server.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 5a9c6c03..b6c55409 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -74,7 +74,4 @@ - - - From 3f5f3ef10bbde2de3fa4adb58df8589308bc0f08 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:10:58 -0700 Subject: [PATCH 29/78] Update Version to 6.1.3 --- Oqtane.Client/Oqtane.Client.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index c974f27a..9da5f4ce 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net9.0 Exe Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -12,7 +12,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane From a728cd2d917f93105d31725b04ec32d0cdec3eec Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:14:12 -0700 Subject: [PATCH 30/78] Update Package Dependencies & Version to 6.1.3 --- Oqtane.Maui/Oqtane.Maui.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index eed1f14c..08bf796f 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,7 +6,7 @@ Exe - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -30,7 +30,7 @@ com.oqtane.maui - 6.1.2 + 6.1.3 1 @@ -72,9 +72,9 @@ - - - + + + From 25667499e65a927016e3cffa208ecc6b820f1e29 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:16:38 -0700 Subject: [PATCH 31/78] Update Package Dependencies & Version to 6.1.3 --- Oqtane.Server/Oqtane.Server.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index b6c55409..ddf80b3d 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -36,7 +36,7 @@ - + all @@ -46,7 +46,7 @@ - + From db9a40db2bbacfd888d9abf9f0236e0453f50859 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:17:17 -0700 Subject: [PATCH 32/78] Update Version to 6.1.3 --- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index c991ee17..65ae667e 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane From 8efdcb9c49b014ded4e2dfa94d974673f5f225f2 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:17:54 -0700 Subject: [PATCH 33/78] Update Version to 6.1.3 --- Oqtane.Updater/Oqtane.Updater.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 0efc8228..7474506c 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net9.0 Exe - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane From 994429f098ba9f519d7bc6d1fe08f213a4f677d4 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:18:40 -0700 Subject: [PATCH 34/78] Update Version to 6.1.3 --- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index c10d12b1..13ecf1ac 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git true From 5507006c531f17e653b9bd76866ca5e4fcb18f1c Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:19:05 -0700 Subject: [PATCH 35/78] Update Version to 6.1.3 --- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index cc1be87e..ae1e04a8 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git true From a348913888f8ca21aa22877db22892af309a8baf Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:20:15 -0700 Subject: [PATCH 36/78] Update Version to 6.1.3 --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index c30e49a5..e6703b08 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git true From e76e0fc3515435feb417cc4fd779fcb8468a70d3 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:20:52 -0700 Subject: [PATCH 37/78] Update Version to 6.1.3 --- Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 3e6f1890..ace07515 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -2,7 +2,7 @@ net9.0 - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git true From f2aa39aa8562d4933b5cafdcc2659b7f257cad9b Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:22:04 -0700 Subject: [PATCH 38/78] Update Version to 6.1.3 --- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 0d0217f8..b50b816c 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane From ad868ba841231f9f2c4944ae9ff6816c0540d926 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:22:47 -0700 Subject: [PATCH 39/78] Update Version to 6.1.3 --- Oqtane.Package/Oqtane.Framework.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 0ebabcbb..a9e739dc 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/download/v6.1.3/Oqtane.Framework.6.1.3.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane framework From 0ae38f8a40fc01624800575a420a975bc80fec75 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:23:11 -0700 Subject: [PATCH 40/78] Update Version to 6.1.3 --- Oqtane.Package/Oqtane.Server.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 06dcf42c..b5e58742 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane From ca19496b5c2bc4e2e306582becba0efc72f096d8 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:23:32 -0700 Subject: [PATCH 41/78] Update Version to 6.1.3 --- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index a240d6a2..9c2041be 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane From fc403f920b6368f499033bdbca4bdefc0724b664 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:24:13 -0700 Subject: [PATCH 42/78] Update Version to 6.1.3 --- Oqtane.Package/Oqtane.Updater.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 8ef6a84a..09453309 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane From 48c6796128abaff66d8825e48520aa201e4e5b59 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:24:36 -0700 Subject: [PATCH 43/78] Update Version to 6.1.3 --- Oqtane.Package/install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 3f8d7188..fb0b6d7f 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Install.zip" -Force From 8ccb1a24f8dcaa0354cec3070a01794976f5da24 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 7 May 2025 10:25:27 -0700 Subject: [PATCH 44/78] Update Version to 6.1.3 --- Oqtane.Package/upgrade.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 9b53797a..e2f26321 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Upgrade.zip" -Force From 3d9c81d8500cf77fe96ed76a66a6748cf4ee3b85 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 7 May 2025 17:12:14 -0400 Subject: [PATCH 45/78] change Synchronize button to Check For Updates to improve clarity --- .../Resources/Modules/Admin/ModuleDefinitions/Index.resx | 6 +++--- Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx index 90ffbe18..8d8e5cac 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx @@ -160,12 +160,12 @@ Enabled? - Synchronize + Check For Updates - Modules Have Been Successfully Synchronized With The Marketplace + Module Information Has Been Retrieved From The Marketplace - Error Synchronizing Modules With The Marketplace + Error Retrieving Module Information From The Marketplace \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx index 4edc516c..0d3b97ed 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx @@ -160,12 +160,12 @@ Assign - Synchronize + Check For Updates - Themes Have Been Successfully Synchronized With The Marketplace + Theme Information Has Been Retrieved From The Marketplace - Error Synchronizing Themes With The Marketplace + Error Retrieving Theme Information From The Marketplace \ No newline at end of file From 60da903360db84b6accc8dd3710e41c42f588a3c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 8 May 2025 16:09:00 -0400 Subject: [PATCH 46/78] update version to 6.1.3 --- Oqtane.Shared/Shared/Constants.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 6e70f5bc..a11a80cc 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,8 +4,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "6.1.2"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2"; + public static readonly string Version = "6.1.3"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; From 90d72489d9e020a97048a3490c02c1b17a5f6523 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 12 May 2025 08:41:57 -0400 Subject: [PATCH 47/78] fix #5287 - allow deletion of folder which contains files --- Oqtane.Client/Modules/Admin/Files/Edit.razor | 14 +++----------- .../Resources/Modules/Admin/Files/Edit.resx | 3 --- Oqtane.Server/Controllers/FolderController.cs | 12 +++++++++++- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index ec8881b5..b3099c5e 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -265,17 +265,9 @@ } if (!isparent) { - var files = await FileService.GetFilesAsync(_folderId); - if (files.Count == 0) - { - await FolderService.DeleteFolderAsync(_folderId); - await logger.LogInformation("Folder Deleted {Folder}", _folderId); - NavigationManager.NavigateTo(NavigateUrl()); - } - else - { - AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning); - } + await FolderService.DeleteFolderAsync(_folderId); + await logger.LogInformation("Folder Deleted {Folder}", _folderId); + NavigationManager.NavigateTo(NavigateUrl()); } else { diff --git a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx index c35e179e..c0c4cb8c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx @@ -135,9 +135,6 @@ Error Saving Folder - - Folder Has Files And Cannot Be Deleted - Folder Has Subfolders And Cannot Be Deleted diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 62545760..866f649b 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -20,14 +20,16 @@ namespace Oqtane.Controllers { private readonly IFolderRepository _folders; private readonly IUserPermissions _userPermissions; + private readonly IFileRepository _files; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) + public FolderController(IFolderRepository folders, IUserPermissions userPermissions, IFileRepository files, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _folders = folders; _userPermissions = userPermissions; + _files = files; _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); @@ -283,12 +285,20 @@ namespace Oqtane.Controllers var folderPath = _folders.GetFolderPath(folder); if (Directory.Exists(folderPath)) { + // remove all files from disk (including thumbnails, etc...) foreach (var filePath in Directory.GetFiles(folderPath)) { System.IO.File.Delete(filePath); } Directory.Delete(folderPath); } + + // remove files from database + foreach (var file in _files.GetFiles(id)) + { + _files.DeleteFile(file.FileId); + } + _folders.DeleteFolder(id); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id); From b53f54295df82b33ba886b12ee1e5f3469d82ca7 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 12 May 2025 11:32:27 -0400 Subject: [PATCH 48/78] fix #5292 - fix External Login Provider Info link --- Oqtane.Shared/Shared/ExternalLoginProviders.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Shared/Shared/ExternalLoginProviders.cs b/Oqtane.Shared/Shared/ExternalLoginProviders.cs index 8b62bad9..57cf9322 100644 --- a/Oqtane.Shared/Shared/ExternalLoginProviders.cs +++ b/Oqtane.Shared/Shared/ExternalLoginProviders.cs @@ -68,7 +68,7 @@ namespace Oqtane.Shared Name = "Facebook", Settings = new Dictionary() { - { "ExternalLogin:ProviderUrl", "https://developers.facebook.com/apps/" }, + { "ExternalLogin:ProviderUrl", "https://developers.facebook.com" }, { "ExternalLogin:ProviderType", "oauth2" }, { "ExternalLogin:ProviderName", "Facebook" }, { "ExternalLogin:AuthorizationUrl", "https://www.facebook.com/v18.0/dialog/oauth" }, From 9f18c460d8e221770ed0b59a1a2255b99827b656 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 09:24:17 -0400 Subject: [PATCH 49/78] add time zone support for sites and users --- .../OqtaneServiceCollectionExtensions.cs | 1 + .../Modules/Admin/Register/Index.razor | 18 ++ Oqtane.Client/Modules/Admin/Site/Index.razor | 26 ++- .../Modules/Admin/UserProfile/Index.razor | 187 ++++++++++-------- Oqtane.Client/Modules/Admin/Users/Add.razor | 50 +++-- Oqtane.Client/Modules/Admin/Users/Edit.razor | 154 ++++++++------- .../Modules/Admin/Register/Index.resx | 6 + .../Resources/Modules/Admin/Site/Index.resx | 6 + .../Modules/Admin/UserProfile/Index.resx | 6 + .../Resources/Modules/Admin/Users/Add.resx | 6 + .../Resources/Modules/Admin/Users/Edit.resx | 6 + .../Services/Interfaces/ITimeZoneService.cs | 18 ++ Oqtane.Client/Services/TimeZoneService.cs | 22 +++ .../Controllers/TimeZoneController.cs | 29 +++ Oqtane.Server/Controllers/UserController.cs | 1 + .../OqtaneServiceCollectionExtensions.cs | 1 + .../Migrations/Tenant/06010301_AddTimeZone.cs | 31 +++ Oqtane.Shared/Models/Site.cs | 6 + Oqtane.Shared/Models/TimeZone.cs | 10 + Oqtane.Shared/Models/User.cs | 5 + 20 files changed, 417 insertions(+), 172 deletions(-) create mode 100644 Oqtane.Client/Services/Interfaces/ITimeZoneService.cs create mode 100644 Oqtane.Client/Services/TimeZoneService.cs create mode 100644 Oqtane.Server/Controllers/TimeZoneController.cs create mode 100644 Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs create mode 100644 Oqtane.Shared/Models/TimeZone.cs diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index c5590d51..9a89bce8 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -54,6 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor index 88ccdd56..13920441 100644 --- a/Oqtane.Client/Modules/Admin/Register/Index.razor +++ b/Oqtane.Client/Modules/Admin/Register/Index.razor @@ -3,6 +3,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @inject ISettingService SettingService @@ -56,6 +57,18 @@
+
+ +
+ +
+

@@ -77,6 +90,7 @@ else } @code { + private List _timezones; private string _passwordrequirements; private string _username = string.Empty; private ElementReference form; @@ -87,6 +101,7 @@ else private string _confirm = string.Empty; private string _email = string.Empty; private string _displayname = string.Empty; + private string _timezoneid = string.Empty; private bool _userCreated = false; private bool _allowsitelogin = true; @@ -96,6 +111,8 @@ else { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); + _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezoneid = PageState.Site.TimeZoneId; } protected override void OnParametersSet() @@ -124,6 +141,7 @@ else Password = _password, Email = _email, DisplayName = (_displayname == string.Empty ? _username : _displayname), + TimeZoneId = _timezoneid, PhotoFileId = null }; user = await UserService.AddUserAsync(user); diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 5333cd3b..172c4225 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -10,6 +10,7 @@ @inject IAliasService AliasService @inject IThemeService ThemeService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject INotificationService NotificationService @@ -41,6 +42,18 @@ +
+ +
+ +
+
@@ -133,7 +146,7 @@
- +
@@ -416,9 +429,11 @@ private List _themes = new List(); private List _containers = new List(); private List _pages; + private List _timezones; private string _name = string.Empty; private string _homepageid = "-"; + private string _timezoneid = string.Empty; private string _isdeleted; private string _sitemap = ""; private string _siteguid = ""; @@ -435,7 +450,7 @@ private Dictionary _textEditors = new Dictionary(); private string _textEditor = ""; - private string _imageFiles = string.Empty; + private string _imagefiles = string.Empty; private string _headcontent = string.Empty; private string _bodycontent = string.Empty; @@ -493,11 +508,13 @@ Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { + _timezones = await TimeZoneService.GetTimeZonesAsync(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); _name = site.Name; + _timezoneid = site.TimeZoneId; if (site.HomePageId != null) { _homepageid = site.HomePageId.Value.ToString(); @@ -531,8 +548,8 @@ _textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName)); } _textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor); - _imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); - _imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles; + _imagefiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; // page content _headcontent = site.HeadContent; @@ -650,6 +667,7 @@ if (site != null) { site.Name = _name; + site.TimeZoneId = _timezoneid; site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 63486fc7..2517c538 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -9,6 +9,7 @@ @inject INotificationService NotificationService @inject IFileService FileService @inject IFolderService FolderService +@inject ITimeZoneService TimeZoneService @inject IJSRuntime jsRuntime @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @@ -16,9 +17,9 @@ @if (_initialized) { - @if (PageState.User != null && photo != null) + @if (PageState.User != null && _photo != null) { - @displayname + @_displayname } else { @@ -31,7 +32,7 @@
- +
@@ -47,17 +48,17 @@
- +
- @if (allowtwofactor) + @if (_allowtwofactor) {
- @@ -67,19 +68,31 @@
- +
- +
- +
- + +
+
+
+ +
+
@@ -91,17 +104,17 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -150,12 +163,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } else @@ -163,12 +176,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } } @@ -179,12 +192,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } else @@ -192,12 +205,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } } @@ -220,11 +233,11 @@
- @if (filter == "to") + @if (_filter == "to") { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
    @@ -260,15 +273,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -285,9 +298,9 @@ } else { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
@@ -324,15 +337,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -354,29 +367,32 @@ } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private string username = string.Empty; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private bool allowtwofactor = false; - private string twofactor = "False"; - private string email = string.Empty; - private string displayname = string.Empty; - private FileManager filemanager; - private int folderid = -1; - private int photofileid = -1; - private File photo = null; - private string _ImageFiles = string.Empty; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private string _confirm = string.Empty; + private bool _allowtwofactor = false; + private string _twofactor = "False"; + private string _email = string.Empty; + private string _displayname = string.Empty; + private FileManager _filemanager; + private int _folderid = -1; + private string _timezoneid = string.Empty; + private int _photofileid = -1; + private File _photo = null; + private string _imagefiles = string.Empty; - private string filter = "to"; - private List notifications; - private string notificationSummary = string.Empty; + private List _profiles; + private Dictionary _userSettings; + private string _category = string.Empty; + + private string _filter = "to"; + private List _notifications; + private string _notificationSummary = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; @@ -386,17 +402,19 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); - profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); + _profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.User != null) { - username = PageState.User.Username; - twofactor = PageState.User.TwoFactorRequired.ToString(); - email = PageState.User.Email; - displayname = PageState.User.DisplayName; + _username = PageState.User.Username; + _twofactor = PageState.User.TwoFactorRequired.ToString(); + _email = PageState.User.Email; + _displayname = PageState.User.DisplayName; + _timezoneid = PageState.User.TimeZoneId; - if (string.IsNullOrEmpty(email)) + if (string.IsNullOrEmpty(_email)) { AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning); } @@ -405,24 +423,24 @@ var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); if (folder != null) { - folderid = folder.FolderId; + _folderid = folder.FolderId; } if (PageState.User.PhotoFileId != null) { - photofileid = PageState.User.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = PageState.User.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } - userSettings = PageState.User.Settings; + _userSettings = PageState.User.Settings; var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles); - _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; + _imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; await LoadNotificationsAsync(); @@ -442,13 +460,13 @@ private async Task LoadNotificationsAsync() { - notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); - notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); + _notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId); + _notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -460,38 +478,39 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { var user = PageState.User; - user.Username = username; + user.Username = _username; user.Password = _password; - user.TwoFactorRequired = bool.Parse(twofactor); - user.Email = email; - user.DisplayName = (displayname == string.Empty ? username : displayname); - user.PhotoFileId = filemanager.GetFileId(); + user.TwoFactorRequired = bool.Parse(_twofactor); + user.Email = _email; + user.DisplayName = (_displayname == string.Empty ? _username : _displayname); + user.TimeZoneId = _timezoneid; + user.PhotoFileId = _filemanager.GetFileId(); if (user.PhotoFileId == -1) { user.PhotoFileId = null; } if (user.PhotoFileId != null) { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = user.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId); + await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId); await logger.LogInformation("User Profile Saved"); if (!string.IsNullOrEmpty(PageState.ReturnUrl)) @@ -557,12 +576,12 @@ private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -594,7 +613,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _userSettings = SettingService.SetSetting(_userSettings, SettingName, value); } private async Task Delete(Notification Notification) @@ -624,7 +643,7 @@ private async void FilterChanged(ChangeEventArgs e) { - filter = (string)e.Value; + _filter = (string)e.Value; await LoadNotificationsAsync(); StateHasChanged(); } @@ -634,7 +653,7 @@ try { ShowProgressIndicator(); - foreach(var Notification in notifications) + foreach(var Notification in _notifications) { if (!Notification.IsDeleted) { diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 10cf19e8..510c87e7 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -5,6 +5,7 @@ @inject IUserService UserService @inject IProfileService ProfileService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -12,7 +13,7 @@ { - @if (profiles != null) + @if (_profiles != null) {
@@ -33,6 +34,18 @@
+
+ +
+ +
+
@@ -48,20 +61,20 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { +
@@ -35,7 +36,7 @@
- +
@@ -43,13 +44,25 @@
- +
- + +
+
+
+ +
+
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) @@ -57,7 +70,7 @@
- @@ -67,13 +80,13 @@
- +
- +
@@ -81,15 +94,15 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -131,44 +144,46 @@ @SharedLocalizer["Cancel"] - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost) + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost) { } - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && isdeleted == "True") + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True") { }

- + } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private int userid; - private string username = string.Empty; + private int _userid; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private string email = string.Empty; - private string displayname = string.Empty; - private string isdeleted; - private string lastlogin; - private string lastipaddress; - private bool ishost = false; + private string _confirm = string.Empty; + private string _email = string.Empty; + private string _displayname = string.Empty; + private string _timezoneid = string.Empty; + private string _isdeleted; + private string _lastlogin; + private string _lastipaddress; + private bool _ishost = false; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private List _profiles; + private Dictionary _settings; + private string _category = string.Empty; - private string createdby; - private DateTime createdon; - private string modifiedby; - private DateTime modifiedon; - private string deletedby; - private DateTime? deletedon; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + private string _deletedby; + private DateTime? _deletedon; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; @@ -178,29 +193,31 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId)) { - userid = UserId; - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + _userid = UserId; + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); if (user != null) { - username = user.Username; - email = user.Email; - displayname = user.DisplayName; - isdeleted = user.IsDeleted.ToString(); - lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); - lastipaddress = user.LastIPAddress; - ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); + _username = user.Username; + _email = user.Email; + _displayname = user.DisplayName; + _timezoneid = PageState.User.TimeZoneId; + _isdeleted = user.IsDeleted.ToString(); + _lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); + _lastipaddress = user.LastIPAddress; + _ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); - userSettings = user.Settings; - createdby = user.CreatedBy; - createdon = user.CreatedOn; - modifiedby = user.ModifiedBy; - modifiedon = user.ModifiedOn; - deletedby = user.DeletedBy; - deletedon = user.DeletedOn; + _settings = user.Settings; + _createdby = user.CreatedBy; + _createdon = user.CreatedOn; + _modifiedby = user.ModifiedBy; + _modifiedon = user.ModifiedOn; + _deletedby = user.DeletedBy; + _deletedon = user.DeletedOn; } } @@ -208,14 +225,14 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); } } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_settings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -227,27 +244,28 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); user.SiteId = PageState.Site.SiteId; - user.Username = username; + user.Username = _username; user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; + user.Email = _email; + user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; + user.TimeZoneId = _timezoneid; if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); + user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); + await SettingService.UpdateUserSettingsAsync(_settings, user.UserId); await logger.LogInformation("User Saved {User}", user); NavigationManager.NavigateTo(NavigateUrl()); } @@ -269,7 +287,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message); + await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error); } } @@ -278,17 +296,17 @@ { try { - await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", username, PageState.User.Username); + await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username); // post back to the server so that the cookies are set correctly var interop = new Interop(JSRuntime); - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = username, returnurl = PageState.Alias.Path }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/"); await interop.SubmitForm(url, fields); } catch (Exception ex) { - await logger.LogError(ex, "Error Impersonating User {Username} {Error}", username, ex.Message); + await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message); AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error); } } @@ -297,9 +315,9 @@ { try { - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && userid != PageState.User.UserId) + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); await logger.LogInformation("User Permanently Deleted {User}", user); NavigationManager.NavigateTo(NavigateUrl()); @@ -307,19 +325,19 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error); } } private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -346,7 +364,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _settings = SettingService.SetSetting(_settings, SettingName, value); } private void TogglePassword() diff --git a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx index 5a0af9ed..47e95add 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx @@ -180,4 +180,10 @@ Already have account? Login now. + + Time Zone: + + + Your time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 0127c7cc..acfd022e 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -447,4 +447,10 @@ Site Map Cache Cleared + + Time Zone: + + + The default time zone for the site + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx index a6f0a739..d6136ee8 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx @@ -246,4 +246,10 @@ Logout Everywhere + + Time Zone: + + + Your time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx index b25b2477..536de9b4 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Add.resx @@ -156,4 +156,10 @@ Notify? + + Time Zone: + + + The user's time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx index 3dbd1d1e..df4ccc95 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx @@ -210,4 +210,10 @@ Unable To Impersonate User + + Time Zone: + + + The user's time zone + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs b/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs new file mode 100644 index 00000000..c31f90b6 --- /dev/null +++ b/Oqtane.Client/Services/Interfaces/ITimeZoneService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Services +{ + /// + /// Service to store and retrieve entries + /// + public interface ITimeZoneService + { + /// + /// Get the list of time zones + /// + /// + Task> GetTimeZonesAsync(); + } +} diff --git a/Oqtane.Client/Services/TimeZoneService.cs b/Oqtane.Client/Services/TimeZoneService.cs new file mode 100644 index 00000000..f7983b14 --- /dev/null +++ b/Oqtane.Client/Services/TimeZoneService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Documentation; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class TimeZoneService : ServiceBase, ITimeZoneService + { + public TimeZoneService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("TimeZone"); + + public async Task> GetTimeZonesAsync() + { + return await GetJsonAsync>($"{Apiurl}"); + } + } +} diff --git a/Oqtane.Server/Controllers/TimeZoneController.cs b/Oqtane.Server/Controllers/TimeZoneController.cs new file mode 100644 index 00000000..158d9f72 --- /dev/null +++ b/Oqtane.Server/Controllers/TimeZoneController.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class TimeZoneController : Controller + { + public TimeZoneController() {} + + // GET: api/ + [HttpGet] + public IEnumerable Get() + { + return TimeZoneInfo.GetSystemTimeZones() + .Select(item => new Models.TimeZone + { + Id = item.Id, + DisplayName = item.DisplayName + }) + .OrderBy(item => item.DisplayName); + } + } +} diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 92aef5c5..7a0f3bfd 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -135,6 +135,7 @@ namespace Oqtane.Controllers if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId) { filtered.Email = user.Email; + filtered.TimeZoneId = user.TimeZoneId; filtered.PhotoFileId = user.PhotoFileId; filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 9b4b09e9..98a392cc 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -104,6 +104,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs new file mode 100644 index 00000000..0e7d40c0 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.01")] + public class AddTimeZone : MultiDatabaseMigration + { + public AddTimeZone(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + + var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase); + userEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index aeb6e37b..e674be2e 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -26,6 +26,11 @@ namespace Oqtane.Models /// public string Name { get; set; } + /// + /// The default time zone for the site + /// + public string TimeZoneId { get; set; } + /// /// Reference to a which has the Logo for this site. /// Should be an image. @@ -200,6 +205,7 @@ namespace Oqtane.Models SiteId = SiteId, TenantId = TenantId, Name = Name, + TimeZoneId = TimeZoneId, LogoFileId = LogoFileId, FaviconFileId = FaviconFileId, DefaultThemeType = DefaultThemeType, diff --git a/Oqtane.Shared/Models/TimeZone.cs b/Oqtane.Shared/Models/TimeZone.cs new file mode 100644 index 00000000..a2ff00f0 --- /dev/null +++ b/Oqtane.Shared/Models/TimeZone.cs @@ -0,0 +1,10 @@ +namespace Oqtane.Models +{ + public class TimeZone + { + public string Id { get; set; } + + public string DisplayName { get; set; } + + } +} diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index d5009fb5..7b6b398b 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -29,6 +29,11 @@ namespace Oqtane.Models /// public string Email { get; set; } + /// + /// User time zone + /// + public string TimeZoneId { get; set; } + /// /// Reference to a containing the users photo. /// From 139793f3c0892dc5f1811ef7f325fd24d3721f2c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 11:29:26 -0400 Subject: [PATCH 50/78] display local datetimes in the Job Scheduler (using time zones) --- Oqtane.Client/Modules/Admin/Jobs/Edit.razor | 18 ++++++------ Oqtane.Client/Modules/Admin/Jobs/Index.razor | 2 +- Oqtane.Client/Modules/ModuleBase.cs | 29 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor index 1d359f66..bcb500ff 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor @@ -132,13 +132,13 @@ _isEnabled = job.IsEnabled.ToString(); _interval = job.Interval.ToString(); _frequency = job.Frequency; - _startDate = Utilities.UtcAsLocalDate(job.StartDate); - _startTime = Utilities.UtcAsLocalDateTime(job.StartDate); - _endDate = Utilities.UtcAsLocalDate(job.EndDate); - _endTime = Utilities.UtcAsLocalDateTime(job.EndDate); + _startDate = UtcToLocal(job.StartDate); + _startTime = UtcToLocal(job.StartDate); + _endDate = UtcToLocal(job.EndDate); + _endTime = UtcToLocal(job.EndDate); _retentionHistory = job.RetentionHistory.ToString(); - _nextDate = Utilities.UtcAsLocalDate(job.NextExecution); - _nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution); + _nextDate = UtcToLocal(job.NextExecution); + _nextTime = UtcToLocal(job.NextExecution); createdby = job.CreatedBy; createdon = job.CreatedOn; modifiedby = job.ModifiedBy; @@ -176,10 +176,10 @@ { job.Interval = int.Parse(_interval); } - job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime); - job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime); + job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay)); + job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay)); job.RetentionHistory = int.Parse(_retentionHistory); - job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime); + job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay)); try { diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor index 4a2f5de6..033c3787 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor @@ -29,7 +29,7 @@ else @context.Name @DisplayStatus(context.IsEnabled, context.IsExecuting) @DisplayFrequency(context.Interval, context.Frequency) - @context.NextExecution?.ToLocalTime() + @UtcToLocal(context.NextExecution) @if (context.IsStarted) { diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 5bbfc855..49d6b00b 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -487,6 +487,35 @@ namespace Oqtane.Modules return content; } + // date methods + public DateTime? UtcToLocal(DateTime? datetime) + { + TimeZoneInfo timezone = null; + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); + } + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); + } + return Utilities.UtcAsLocalDateTime(datetime, timezone); + } + + public DateTime? LocalToUtc(DateTime? datetime) + { + TimeZoneInfo timezone = null; + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); + } + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); + } + return Utilities.LocalDateAndTimeAsUtc(datetime, timezone); + } + // logging methods public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) { From deb460708115e9a7f3a130fe29221961a9e3fafe Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 13:55:01 -0400 Subject: [PATCH 51/78] adding time zone support to admin modules --- Oqtane.Client/Modules/Admin/Files/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Jobs/Log.razor | 4 ++-- Oqtane.Client/Modules/Admin/Logs/Detail.razor | 2 +- Oqtane.Client/Modules/Admin/Logs/Index.razor | 2 +- Oqtane.Client/Modules/Admin/UrlMappings/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Users/Edit.razor | 2 +- Oqtane.Client/Modules/Admin/Users/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Visitors/Detail.razor | 4 ++-- Oqtane.Client/Modules/Admin/Visitors/Index.razor | 4 ++-- Oqtane.Client/Modules/Controls/AuditInfo.razor | 6 +++--- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Files/Index.razor b/Oqtane.Client/Modules/Admin/Files/Index.razor index 06f7e61c..b730603b 100644 --- a/Oqtane.Client/Modules/Admin/Files/Index.razor +++ b/Oqtane.Client/Modules/Admin/Files/Index.razor @@ -53,7 +53,7 @@ else @context.Name - @context.ModifiedOn + @UtcToLocal(context.ModifiedOn) @context.Extension.ToUpper() @SharedLocalizer["File"] @string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB diff --git a/Oqtane.Client/Modules/Admin/Jobs/Log.razor b/Oqtane.Client/Modules/Admin/Jobs/Log.razor index f5453315..55efc788 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Log.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Log.razor @@ -23,8 +23,8 @@ else @context.Job.Name @DisplayStatus(context.Job.IsExecuting, context.Succeeded) - @context.StartDate - @context.FinishDate + @UtcToLocal(context.StartDate) + @UtcToLocal(context.FinishDate) @((MarkupString)context.Notes) diff --git a/Oqtane.Client/Modules/Admin/Logs/Detail.razor b/Oqtane.Client/Modules/Admin/Logs/Detail.razor index fd980bce..379a4f3c 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Detail.razor @@ -141,7 +141,7 @@ var log = await LogService.GetLogAsync(_logId); if (log != null) { - _logDate = log.LogDate.ToString(CultureInfo.CurrentCulture); + _logDate = UtcToLocal(log.LogDate).Value.ToString(CultureInfo.CurrentCulture); _level = log.Level; _feature = log.Feature; _function = log.Function; diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index dfb0cf8d..594393ca 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -64,7 +64,7 @@ else
- @context.LogDate + @UtcToLocal(context.LogDate) @context.Level @context.Feature @context.Function diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor index 75a4c845..554213fa 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor @@ -48,7 +48,7 @@ else } @context.Requests - @context.RequestedOn + @UtcToLocal(context.RequestedOn)
diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index 37d8f165..ae66baef 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -207,7 +207,7 @@ _displayname = user.DisplayName; _timezoneid = PageState.User.TimeZoneId; _isdeleted = user.IsDeleted.ToString(); - _lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); + _lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn)); _lastipaddress = user.LastIPAddress; _ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index d3eb9bc7..7d34670f 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -43,7 +43,7 @@ else @context.User.Username @context.User.DisplayName @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) - @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "") + @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", UtcToLocal(context.User.LastLoginOn)) : "")
diff --git a/Oqtane.Client/Modules/Admin/Visitors/Detail.razor b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor index e0570431..13dd456f 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor @@ -101,8 +101,8 @@ _url = visitor.Url; _referrer = visitor.Referrer; _visits = visitor.Visits.ToString(); - _visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture); - _created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture); + _visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture); + _created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture); if (visitor.UserId != null) { diff --git a/Oqtane.Client/Modules/Admin/Visitors/Index.razor b/Oqtane.Client/Modules/Admin/Visitors/Index.razor index 1cb46d3e..2dccb94b 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Index.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Index.razor @@ -53,8 +53,8 @@ else @context.Language @context.Visits - @context.VisitedOn - @context.CreatedOn + @UtcToLocal(context.VisitedOn) + @UtcToLocal(context.CreatedOn) diff --git a/Oqtane.Client/Modules/Controls/AuditInfo.razor b/Oqtane.Client/Modules/Controls/AuditInfo.razor index acbb3dc6..e2a025ec 100644 --- a/Oqtane.Client/Modules/Controls/AuditInfo.razor +++ b/Oqtane.Client/Modules/Controls/AuditInfo.razor @@ -52,7 +52,7 @@ if (CreatedOn != null) { - _text += $" {Localizer["On"]} {CreatedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; @@ -69,7 +69,7 @@ if (ModifiedOn != null) { - _text += $" {Localizer["On"]} {ModifiedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; @@ -86,7 +86,7 @@ if (DeletedOn != null) { - _text += $" {Localizer["On"]} {DeletedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; From a0f41341acd9335e000cb0654a6acb12118dd64e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 15:49:16 -0400 Subject: [PATCH 52/78] fix #5398 - editing page permissions --- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 57 +++++++++++++------ Oqtane.Client/Modules/Admin/Users/Edit.razor | 2 +- .../Resources/Modules/Admin/Pages/Edit.resx | 6 ++ Oqtane.Server/Controllers/PageController.cs | 53 +++++++++-------- Oqtane.Shared/Models/Page.cs | 6 ++ 5 files changed, 82 insertions(+), 42 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 6d27b990..df9db9bb 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -30,16 +30,16 @@
- + + @foreach (Page page in _pages) + { + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId) { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId) - { - - } + } - + } +
@@ -217,6 +217,9 @@

+ + +

@@ -225,15 +228,28 @@
+

+
+ +
+ +
+
+
+ +
-   -   - @Localizer["ModuleTitle"] - @Localizer["ModuleDefinition"] +   +   + @Localizer["ModuleTitle"] + @Localizer["ModuleDefinition"]
@@ -247,8 +263,10 @@ { @_themeSettingsComponent +
+ +
-
} } @@ -299,19 +317,21 @@
+
+ + @if (_themeSettingsType != null) { @_themeSettingsComponent +
+ +
-
} } -
- - } @@ -348,6 +368,7 @@ private string _bodycontent; private List _permissions = null; private PermissionGrid _permissionGrid; + private string _updatemodulepermissions; private List _pageModules; private string _createdby; private DateTime _createdon; @@ -436,6 +457,7 @@ // permissions _permissions = _page.PermissionList; + _updatemodulepermissions = "True"; // page modules var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); @@ -651,6 +673,7 @@ if (_page.UserId == null) { _page.PermissionList = _permissionGrid.GetPermissionList(); + _page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions); } _page = await PageService.UpdatePageAsync(_page); diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index ae66baef..549059e4 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -141,7 +141,7 @@
- +
@SharedLocalizer["Cancel"] @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost) diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx index cf720e19..ded50502 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx @@ -303,4 +303,10 @@ Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash. + + Update Module Permissions? + + + Specify if changes made to page permissions should be propagated to the modules on this page + \ No newline at end of file diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index f8ad1925..ee2ff20c 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -295,38 +295,43 @@ namespace Oqtane.Controllers var removed = GetPermissionsDifferences(currentPermissions, page.PermissionList); // synchronize module permissions - if (added.Count > 0 || removed.Count > 0) + if (page.UpdateModulePermissions && (added.Count > 0 || removed.Count > 0)) { - foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList()) + var pageModules = _pageModules.GetPageModules(page.SiteId); + foreach (PageModule pageModule in pageModules.Where(item => item.PageId == page.PageId).ToList()) { - var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); - // permissions added - foreach (Permission permission in added) + // ignore "shared" modules + if (!pageModules.Any(item => item.ModuleId == pageModule.ModuleId && item.PageId != pageModule.PageId)) { - if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName - && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized)) + var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); + // permissions added + foreach (Permission permission in added) { - _permissionRepository.AddPermission(new Permission + if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName + && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized)) { - SiteId = page.SiteId, - EntityName = EntityNames.Module, - EntityId = pageModule.ModuleId, - PermissionName = permission.PermissionName, - RoleId = permission.RoleId, - UserId = permission.UserId, - IsAuthorized = permission.IsAuthorized - }); + _permissionRepository.AddPermission(new Permission + { + SiteId = page.SiteId, + EntityName = EntityNames.Module, + EntityId = pageModule.ModuleId, + PermissionName = permission.PermissionName, + RoleId = permission.RoleId, + UserId = permission.UserId, + IsAuthorized = permission.IsAuthorized + }); + } } - } - // permissions removed - foreach (Permission permission in removed) - { - var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName - && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized); - if (modulePermission != null) + // permissions removed + foreach (Permission permission in removed) { - _permissionRepository.DeletePermission(modulePermission.PermissionId); + var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName + && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized); + if (modulePermission != null) + { + _permissionRepository.DeletePermission(modulePermission.PermissionId); + } } } } diff --git a/Oqtane.Shared/Models/Page.cs b/Oqtane.Shared/Models/Page.cs index bec0347e..15d640fe 100644 --- a/Oqtane.Shared/Models/Page.cs +++ b/Oqtane.Shared/Models/Page.cs @@ -122,6 +122,12 @@ namespace Oqtane.Models [NotMapped] public bool HasChildren { get; set; } + /// + /// Indicates if module permissions should be updated to be consistent with page permissions + /// + [NotMapped] + public bool UpdateModulePermissions { get; set; } + /// /// List of permissions for this page /// From 128bcecfe3e847ac7ad888674aefd44f4484aed0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 16:38:48 -0400 Subject: [PATCH 53/78] rollback change which moved ConfigureOqtaneAssemblies(env); --- Oqtane.Server/Startup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index b51609ae..51b60555 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -195,6 +195,9 @@ namespace Oqtane app.UseHsts(); } + // execute any IServerStartup logic + app.ConfigureOqtaneAssemblies(env); + // allow oqtane localization middleware app.UseOqtaneLocalization(); @@ -245,9 +248,6 @@ namespace Oqtane .AddAdditionalAssemblies(typeof(SiteRouter).Assembly); }); - // execute any IServerStartup logic - app.ConfigureOqtaneAssemblies(env); - // simulate the fallback routing approach of traditional Blazor - allowing the custom SiteRouter to handle all routing concerns app.UseEndpoints(endpoints => { From e8f9888a41baaf48755dffbc62164cb321e6dcc6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 16:49:46 -0400 Subject: [PATCH 54/78] upgrade to .NET SDK 9.0.5 --- Oqtane.Client/Oqtane.Client.csproj | 8 ++++---- .../Oqtane.Database.PostgreSQL.csproj | 2 +- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Maui/Oqtane.Maui.csproj | 16 ++++++++-------- Oqtane.Server/Oqtane.Server.csproj | 14 +++++++------- Oqtane.Shared/Oqtane.Shared.csproj | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 9da5f4ce..bb7671d6 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -22,10 +22,10 @@ - - - - + + + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index ace07515..1238c9cb 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -34,7 +34,7 @@ - + diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index ae1e04a8..6a594463 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 13ecf1ac..c13db7c9 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 08bf796f..6f0b4c92 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -67,14 +67,14 @@ - - - - - - - - + + + + + + + + diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index ddf80b3d..76bb68c8 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,17 +34,17 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 65ae667e..fbffe080 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,11 +19,11 @@ - - - + + + - + From ffef1f4820e5fed088d30ce450e097a239ded536 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 13 May 2025 16:52:39 -0400 Subject: [PATCH 55/78] update default templates to .NET SDK 9.0.5 --- .../Client/[Owner].Module.[Module].Client.csproj | 10 +++++----- .../Server/[Owner].Module.[Module].Server.csproj | 8 ++++---- .../Client/[Owner].Theme.[Theme].Client.csproj | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj index f4e222ee..c40f7c41 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index 3844f51a..75da4858 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index a454ee59..77ae8f52 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -13,9 +13,9 @@ - - - + + + From 9000f05961d6621e4b51d1d66c439d7517867763 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 14 May 2025 11:12:58 -0400 Subject: [PATCH 56/78] move ConfigureOqtaneAssemblies to occur before UseEndpoints --- Oqtane.Server/Startup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 51b60555..2101a861 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -195,9 +195,6 @@ namespace Oqtane app.UseHsts(); } - // execute any IServerStartup logic - app.ConfigureOqtaneAssemblies(env); - // allow oqtane localization middleware app.UseOqtaneLocalization(); @@ -228,6 +225,9 @@ namespace Oqtane app.UseAuthorization(); app.UseAntiforgery(); + // execute any IServerStartup logic + app.ConfigureOqtaneAssemblies(env); + if (_useSwagger) { app.UseSwagger(); From 57d443be8dc20236a2c55dbd6c8163247330ab6c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 14 May 2025 12:18:37 -0400 Subject: [PATCH 57/78] support for module header and footer content --- .../Modules/Admin/Modules/Settings.razor | 75 +++++++++++++------ .../Modules/Admin/Modules/Settings.resx | 15 ++++ Oqtane.Client/UI/ModuleInstance.razor | 9 +++ Oqtane.Server/Controllers/ModuleController.cs | 2 + Oqtane.Server/Controllers/PageController.cs | 4 + .../Tenant/06010302_AddModuleHeaderFooter.cs | 29 +++++++ Oqtane.Server/Repository/SiteRepository.cs | 2 + Oqtane.Server/Services/SiteService.cs | 2 + Oqtane.Shared/Models/Module.cs | 14 ++++ Oqtane.Shared/Models/PageModule.cs | 13 ++++ Oqtane.Shared/Models/SiteTemplate.cs | 4 + 11 files changed, 147 insertions(+), 22 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index a949271f..b7cf4f4c 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -97,6 +97,23 @@
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
} @@ -144,6 +161,8 @@ private string _pane; private string _containerType; private string _allPages = "false"; + private string _header = ""; + private string _footer = ""; private string _permissionNames = ""; private List _permissions = null; private string _pageId; @@ -167,37 +186,47 @@ protected override async Task OnInitializedAsync() { SetModuleTitle(Localizer["ModuleSettings.Title"]); - - _title = ModuleState.Title; _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; - _pane = ModuleState.Pane; + _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); - _containerType = ModuleState.ContainerType; - _allPages = ModuleState.AllPages.ToString(); - _permissions = ModuleState.PermissionList; - _pageId = ModuleState.PageId.ToString(); - createdby = ModuleState.CreatedBy; - createdon = ModuleState.CreatedOn; - modifiedby = ModuleState.ModifiedBy; - modifiedon = ModuleState.ModifiedOn; - _effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate); - _expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); - if (ModuleState.ModuleDefinition != null) - { - _module = ModuleState.ModuleDefinition.Name; - _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; + var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); - if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) + _pageId = pagemodule.PageId.ToString(); + _title = pagemodule.Title; + _pane = pagemodule.Pane; + _containerType = pagemodule.ContainerType; + if (string.IsNullOrEmpty(_containerType)) + { + _containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType; + } + _header = pagemodule.Header; + _footer = pagemodule.Footer; + _effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate); + _expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate); + + _allPages = pagemodule.Module.AllPages.ToString(); + createdby = pagemodule.Module.CreatedBy; + createdon = pagemodule.Module.CreatedOn; + modifiedby = pagemodule.Module.ModifiedBy; + modifiedon = pagemodule.Module.ModifiedOn; + _permissions = pagemodule.Module.PermissionList; + + if (pagemodule.Module.ModuleDefinition != null) + { + _module = pagemodule.Module.ModuleDefinition.Name; + _permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames; + + if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType)) { // module settings type explicitly declared in IModule interface - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType); } else { // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); } if (_moduleSettingsType != null) { @@ -218,7 +247,7 @@ } else { - AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); + AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error); } var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); @@ -270,10 +299,12 @@ { pagemodule.ContainerType = string.Empty; } + pagemodule.Header = _header; + pagemodule.Footer = _footer; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - var module = ModuleState; + var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId); module.AllPages = bool.Parse(_allPages); module.PageModuleId = ModuleState.PageModuleId; module.PermissionList = _permissionGrid.GetPermissionList(); diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index a91aa36e..4f8f895c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -189,4 +189,19 @@ Module Settings + + Header: + + + Optionally provide content to be injected above the module instance + + + Footer: + + + Optionally provide content to be injected below the module instance + + + Content + \ No newline at end of file diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index 1bd5296f..e8353a82 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -1,6 +1,7 @@ @namespace Oqtane.UI @inject SiteState SiteState +@((MarkupString)ModuleState.Header) @if (_comment != null) { @((MarkupString)_comment) @@ -13,6 +14,7 @@ } } +@((MarkupString)ModuleState.Footer) @code { [CascadingParameter] @@ -23,6 +25,8 @@ private bool _prerender; private string _comment; + private string _header; + private string _footer; protected override void OnParametersSet() { @@ -39,11 +43,16 @@ } _comment += " -->"; + _header = ModuleState.Header; + _footer = ModuleState.Footer; + if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive) { // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries // please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components PageState.Site.Pages = new List(); + ModuleState.Header = string.Empty; + ModuleState.Footer = string.Empty; } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index a7c09fcb..02f6f8c6 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -76,6 +76,8 @@ namespace Oqtane.Controllers module.ContainerType = pagemodule.ContainerType; module.EffectiveDate = pagemodule.EffectiveDate; module.ExpiryDate = pagemodule.ExpiryDate; + module.Header = pagemodule.Header; + module.Footer = pagemodule.Footer; module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName)); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index ee2ff20c..6086bd0f 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -246,6 +246,10 @@ namespace Oqtane.Controllers pagemodule.Pane = pm.Pane; pagemodule.Order = pm.Order; pagemodule.ContainerType = pm.ContainerType; + pagemodule.EffectiveDate = pm.EffectiveDate; + pagemodule.ExpiryDate = pm.ExpiryDate; + pagemodule.Header = pm.Header; + pagemodule.Footer = pm.Footer; _pageModules.AddPageModule(pagemodule); } diff --git a/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs new file mode 100644 index 00000000..39e01287 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.02")] + public class AddModuleHeaderFooter : MultiDatabaseMigration + { + public AddModuleHeaderFooter(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase); + pageModuleEntityBuilder.AddMaxStringColumn("Header", true); + pageModuleEntityBuilder.AddMaxStringColumn("Footer", true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 10de3576..48620ead 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -442,6 +442,8 @@ namespace Oqtane.Repository pageModule.Pane = (string.IsNullOrEmpty(pageTemplateModule.Pane)) ? PaneNames.Default : pageTemplateModule.Pane; pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.ContainerType = pageTemplateModule.ContainerType; + pageModule.Header = pageTemplateModule.Header; + pageModule.Footer = pageTemplateModule.Footer; pageModule.IsDeleted = pageTemplateModule.IsDeleted; pageModule.Module.PermissionList = new List(); foreach (var permission in pageTemplateModule.PermissionList) diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index cc0991d8..f316b766 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -285,6 +285,8 @@ namespace Oqtane.Services ContainerType = pagemodule.ContainerType, EffectiveDate = pagemodule.EffectiveDate, ExpiryDate = pagemodule.ExpiryDate, + Header = pagemodule.Header, + Footer = pagemodule.Footer, ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)), diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index 55f31357..7775fcf5 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -113,6 +113,18 @@ namespace Oqtane.Models [NotMapped] public DateTime? ExpiryDate { get; set; } + /// + /// Header content to include at the top of a module instance in the UI + /// + [NotMapped] + public string Header { get; set; } + + /// + /// Footer content to include below a module instance in the UI + /// + [NotMapped] + public string Footer { get; set; } + #endregion #region SiteRouter properties @@ -218,6 +230,8 @@ namespace Oqtane.Models ContainerType = ContainerType, EffectiveDate = EffectiveDate, ExpiryDate = ExpiryDate, + Header = Header, + Footer = Footer, CreatedBy = CreatedBy, CreatedOn = CreatedOn, ModifiedBy = ModifiedBy, diff --git a/Oqtane.Shared/Models/PageModule.cs b/Oqtane.Shared/Models/PageModule.cs index 0e7c812f..20985e93 100644 --- a/Oqtane.Shared/Models/PageModule.cs +++ b/Oqtane.Shared/Models/PageModule.cs @@ -41,14 +41,27 @@ namespace Oqtane.Models /// Reference to a Razor Container which wraps this module instance. /// public string ContainerType { get; set; } + /// /// Start of when this assignment is valid. See also /// public DateTime? EffectiveDate { get; set; } + /// /// End of when this assignment is valid. See also /// public DateTime? ExpiryDate { get; set; } + + /// + /// Header content to include above the module instance in the UI + /// + public string Header { get; set; } + + /// + /// Footer content to include below the module instance in the UI + /// + public string Footer { get; set; } + #region IDeletable Properties public string DeletedBy { get; set; } diff --git a/Oqtane.Shared/Models/SiteTemplate.cs b/Oqtane.Shared/Models/SiteTemplate.cs index 348e1834..42cbffee 100644 --- a/Oqtane.Shared/Models/SiteTemplate.cs +++ b/Oqtane.Shared/Models/SiteTemplate.cs @@ -95,6 +95,8 @@ namespace Oqtane.Models Pane = PaneNames.Default; Order = 1; ContainerType = ""; + Header = ""; + Footer = ""; IsDeleted = false; PermissionList = new List() { @@ -110,6 +112,8 @@ namespace Oqtane.Models public string Pane { get; set; } public int Order { get; set; } public string ContainerType { get; set; } + public string Header { get; set; } + public string Footer { get; set; } public bool IsDeleted { get; set; } public List PermissionList { get; set; } public List Settings { get; set; } From f1791a709c8b9c8b88441058df9a934449875368 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 14 May 2025 14:20:44 -0400 Subject: [PATCH 58/78] fix issue with module header/footer --- Oqtane.Client/UI/ModuleInstance.razor | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index e8353a82..e9a634d3 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -1,7 +1,10 @@ @namespace Oqtane.UI @inject SiteState SiteState -@((MarkupString)ModuleState.Header) +@if (PageState.ModuleId == -1) +{ + @((MarkupString)ModuleState.Header) +} @if (_comment != null) { @((MarkupString)_comment) @@ -14,7 +17,11 @@ } } -@((MarkupString)ModuleState.Footer) +@if (PageState.ModuleId == -1) +{ + @((MarkupString)ModuleState.Footer) +} + @code { [CascadingParameter] @@ -25,8 +32,6 @@ private bool _prerender; private string _comment; - private string _header; - private string _footer; protected override void OnParametersSet() { @@ -43,16 +48,11 @@ } _comment += " -->"; - _header = ModuleState.Header; - _footer = ModuleState.Footer; - if (PageState.RenderMode == RenderModes.Static && ModuleState.RenderMode == RenderModes.Interactive) { // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries // please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components PageState.Site.Pages = new List(); - ModuleState.Header = string.Empty; - ModuleState.Footer = string.Empty; } } From f3fcef52ddcaa4a07f0381aa4b399536c7601c97 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 14 May 2025 15:51:51 -0400 Subject: [PATCH 59/78] fix #5200 - sort folders alphabetically, display folders hierarchically --- Oqtane.Client/Modules/Admin/Files/Edit.razor | 25 ++++++++--- Oqtane.Client/Services/FolderService.cs | 8 ---- .../Services/Interfaces/IFolderService.cs | 9 ---- Oqtane.Server/Controllers/FolderController.cs | 43 +++---------------- 4 files changed, 25 insertions(+), 60 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index b3099c5e..49ac137a 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -15,22 +15,34 @@
- - } + + } + else + { + + + }
- + @if (_isSystem) + { + + } + else + { + + }
@@ -229,7 +241,6 @@ if (folder != null) { - await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); await logger.LogInformation("Folder Saved {Folder}", folder); NavigationManager.NavigateTo(NavigateUrl()); } diff --git a/Oqtane.Client/Services/FolderService.cs b/Oqtane.Client/Services/FolderService.cs index 556309ad..2ce045db 100644 --- a/Oqtane.Client/Services/FolderService.cs +++ b/Oqtane.Client/Services/FolderService.cs @@ -42,14 +42,6 @@ namespace Oqtane.Services return await PutJsonAsync($"{ApiUrl}/{folder.FolderId}", folder); } - public async Task UpdateFolderOrderAsync(int siteId, int folderId, int? parentId) - { - var parent = parentId == null - ? string.Empty - : parentId.ToString(); - await PutAsync($"{ApiUrl}/?siteid={siteId}&folderid={folderId}&parentid={parent}"); - } - public async Task DeleteFolderAsync(int folderId) { await DeleteAsync($"{ApiUrl}/{folderId}"); diff --git a/Oqtane.Client/Services/Interfaces/IFolderService.cs b/Oqtane.Client/Services/Interfaces/IFolderService.cs index a3085f3a..35e96956 100644 --- a/Oqtane.Client/Services/Interfaces/IFolderService.cs +++ b/Oqtane.Client/Services/Interfaces/IFolderService.cs @@ -39,15 +39,6 @@ namespace Oqtane.Services /// Task UpdateFolderAsync(Folder folder); - /// - /// Update the internal Folder-Order within the list of Folders. - /// - /// Reference to the - /// Reference to a for the security check - /// Reference to the Parent or null - this Folders children will be re-sorted. - /// - Task UpdateFolderOrderAsync(int siteId, int folderId, int? parentId); - /// /// Delete a /// diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 866f649b..12a9c3fa 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -43,7 +43,8 @@ namespace Oqtane.Controllers int SiteId; if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) { - foreach (Folder folder in _folders.GetFolders(SiteId)) + var hierarchy = GetFoldersHierarchy(_folders.GetFolders(SiteId).ToList()); + foreach (Folder folder in hierarchy) { // note that Browse permission is used for this method if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.PermissionList)) @@ -51,7 +52,6 @@ namespace Oqtane.Controllers folders.Add(folder); } } - folders = GetFoldersHierarchy(folders); } else { @@ -246,34 +246,6 @@ namespace Oqtane.Controllers return folder; } - // PUT api//?siteid=x&folderid=y&parentid=z - [HttpPut] - [Authorize(Roles = RoleNames.Registered)] - public void Put(int siteid, int folderid, int? parentid) - { - if (siteid == _alias.SiteId && _folders.GetFolder(folderid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Folder, folderid, PermissionNames.Edit)) - { - int order = 1; - List folders = _folders.GetFolders(siteid).ToList(); - foreach (Folder folder in folders.Where(item => item.ParentId == parentid).OrderBy(item => item.Order)) - { - if (folder.Order != order) - { - folder.Order = order; - _folders.UpdateFolder(folder); - _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); - } - order += 2; - } - _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Order Updated {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid); - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Update, "Unauthorized Folder Put Attempt {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - } - } - // DELETE api//5 [HttpDelete("{id}")] [Authorize(Roles = RoleNames.Registered)] @@ -314,7 +286,6 @@ namespace Oqtane.Controllers { List hierarchy = new List(); Action, Folder> getPath = null; - var folders1 = folders; getPath = (folderList, folder) => { IEnumerable children; @@ -322,23 +293,23 @@ namespace Oqtane.Controllers if (folder == null) { level = -1; - children = folders1.Where(item => item.ParentId == null); + children = folders.Where(item => item.ParentId == null); } else { level = folder.Level; - children = folders1.Where(item => item.ParentId == folder.FolderId); + children = folders.Where(item => item.ParentId == folder.FolderId); } foreach (Folder child in children) { child.Level = level + 1; - child.HasChildren = folders1.Any(item => item.ParentId == child.FolderId); + child.HasChildren = folders.Any(item => item.ParentId == child.FolderId); hierarchy.Add(child); - if (getPath != null) getPath(folderList, child); + getPath(folderList, child); } }; - folders = folders.OrderBy(item => item.Order).ToList(); + folders = folders.OrderBy(item => item.Name).ToList(); getPath(folders, null); // add any non-hierarchical items to the end of the list From a49b8728fd3cc6ab1071a763816216f248d6d563 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 15 May 2025 08:56:21 -0400 Subject: [PATCH 60/78] improve module export so that content can be saved to a file --- .../Modules/Admin/Modules/Export.razor | 71 ++++++++++++++++--- .../Modules/Admin/Modules/Export.resx | 20 +++++- .../Services/Interfaces/IModuleService.cs | 12 +++- Oqtane.Client/Services/ModuleService.cs | 7 +- Oqtane.Server/Controllers/ModuleController.cs | 66 ++++++++++++++++- Oqtane.Shared/Models/Result.cs | 2 + 6 files changed, 164 insertions(+), 14 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Export.razor b/Oqtane.Client/Modules/Admin/Modules/Export.razor index d2e90193..ab714221 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Export.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Export.razor @@ -5,24 +5,45 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -
-
- -
- + + +
+
+ +
+ +
+
-
-
+
+ + @SharedLocalizer["Cancel"] + + +
+
+ +
+ +
+
+
+
+ + @SharedLocalizer["Cancel"] +
+ + - -@SharedLocalizer["Cancel"] @code { private string _content = string.Empty; + private FileManager _filemanager; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Export Content"; - private async Task ExportModule() + private async Task ExportText() { try { @@ -35,4 +56,34 @@ AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); } } + + private async Task ExportFile() + { + try + { + var folderid = _filemanager.GetFolderId(); + if (folderid != -1) + { + var result = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid); + if (result.Success) + { + AddModuleMessage(string.Format(Localizer["Success.Export.File"], result.Message), MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); + } + } + else + { + AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message); + AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); + } + } + } diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx index 3ed705f8..90e614c2 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx @@ -121,7 +121,7 @@ Export - The Exported Module Content + Select the Export option and you will be able to view the module content Content: @@ -135,4 +135,22 @@ Export Content + + Text + + + File + + + Folder: + + + Select a folder where you wish to save the exported content + + + Please Select A Folder Before Choosing Export + + + Content Was Successfully Exported To Specified Folder With Filename {0} + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/IModuleService.cs b/Oqtane.Client/Services/Interfaces/IModuleService.cs index 46777fdf..34a9c1b1 100644 --- a/Oqtane.Client/Services/Interfaces/IModuleService.cs +++ b/Oqtane.Client/Services/Interfaces/IModuleService.cs @@ -56,7 +56,17 @@ namespace Oqtane.Services /// Exports a given module /// /// - /// module in JSON + /// + /// module content in JSON format Task ExportModuleAsync(int moduleId, int pageId); + + /// + /// Exports a given module + /// + /// + /// + /// + /// success/failure + Task ExportModuleAsync(int moduleId, int pageId, int folderId); } } diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs index e1914004..1e688610 100644 --- a/Oqtane.Client/Services/ModuleService.cs +++ b/Oqtane.Client/Services/ModuleService.cs @@ -47,8 +47,13 @@ namespace Oqtane.Services } public async Task ExportModuleAsync(int moduleId, int pageId) -{ + { return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}"); } + + public async Task ExportModuleAsync(int moduleId, int pageId, int folderId) + { + return await PostJsonAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}", null); + } } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 02f6f8c6..78aae764 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -9,6 +9,10 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; +using System.IO; +using System; +using static System.Net.WebRequestMethods; +using System.Net.Http; namespace Oqtane.Controllers { @@ -20,18 +24,22 @@ namespace Oqtane.Controllers private readonly IPageRepository _pages; private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly ISettingRepository _settings; + private readonly IFolderRepository _folders; + private readonly IFileRepository _files; private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) + public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IFolderRepository folders, IFileRepository files, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) { _modules = modules; _pageModules = pageModules; _pages = pages; _moduleDefinitions = moduleDefinitions; _settings = settings; + _folders = folders; + _files = files; _userPermissions = userPermissions; _syncManager = syncManager; _logger = logger; @@ -248,6 +256,62 @@ namespace Oqtane.Controllers return content; } + // POST api//export?moduleid=x&pageid=y&folderid=z + [HttpPost("export")] + [Authorize(Roles = RoleNames.Registered)] + public Result Export(int moduleid, int pageid, int folderid) + { + var result = new Result(false); + var module = _modules.GetModule(moduleid); + if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) && + _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit)) + { + // get content + var content = _modules.ExportModule(moduleid); + + // get folder + var folder = _folders.GetFolder(folderid, false); + string folderPath = _folders.GetFolderPath(folder); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // create text file + var filename = Utilities.GetTypeNameLastSegment(module.ModuleDefinitionName, 0) + moduleid.ToString() + ".json"; + string filepath = Path.Combine(folderPath, filename); + if (System.IO.File.Exists(filepath)) + { + System.IO.File.Delete(filepath); + } + System.IO.File.WriteAllText(filepath, content); + + // register file + var file = _files.GetFile(folderid, filename); + if (file == null) + { + file = new Models.File { FolderId = folderid, Name = filename, Extension = "txt", Size = (int)new FileInfo(filepath).Length, ImageWidth = 0, ImageHeight = 0 }; + _files.AddFile(file); + } + else + { + file.Size = (int)new FileInfo(filepath).Length; + _files.UpdateFile(file); + } + + result.Success = true; + result.Message = filename; + + _logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + return result; + } + // POST api//import?moduleid=x&pageid=y [HttpPost("import")] [Authorize(Roles = RoleNames.Registered)] diff --git a/Oqtane.Shared/Models/Result.cs b/Oqtane.Shared/Models/Result.cs index d550f2c4..8500d3ad 100644 --- a/Oqtane.Shared/Models/Result.cs +++ b/Oqtane.Shared/Models/Result.cs @@ -6,6 +6,8 @@ namespace Oqtane.Models public string Message { get; set; } + public Result() {} + public Result(bool success) { Success = success; From 51ba3a01f5ce4eeae207c3d90e1707988a2c678e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 15 May 2025 09:34:19 -0400 Subject: [PATCH 61/78] allow module import from a file --- .../Modules/Admin/Modules/Import.razor | 19 ++++++++++++++++--- Oqtane.Server/Controllers/ModuleController.cs | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Import.razor b/Oqtane.Client/Modules/Admin/Modules/Import.razor index 04b2557a..eacec260 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Import.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Import.razor @@ -2,20 +2,27 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IModuleService ModuleService +@inject IFileService FileService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer
- + +
+ +
+
+
+
+
-
- +
@SharedLocalizer["Cancel"]
@@ -28,6 +35,12 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Import Content"; + private async Task OnSelectFile(int fileId) + { + var bytes = await FileService.DownloadFileAsync(fileId); + _content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + private async Task ImportModule() { validated = true; diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 78aae764..e075e5e5 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -290,7 +290,7 @@ namespace Oqtane.Controllers var file = _files.GetFile(folderid, filename); if (file == null) { - file = new Models.File { FolderId = folderid, Name = filename, Extension = "txt", Size = (int)new FileInfo(filepath).Length, ImageWidth = 0, ImageHeight = 0 }; + file = new Models.File { FolderId = folderid, Name = filename, Extension = "json", Size = (int)new FileInfo(filepath).Length, ImageWidth = 0, ImageHeight = 0 }; _files.AddFile(file); } else From 5d077e843d2d8a0508509b8ac1fb6eaf30694ee6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 15 May 2025 10:58:55 -0400 Subject: [PATCH 62/78] allow filename to be provided during module export --- Oqtane.Client/Modules/Admin/Modules/Export.razor | 13 ++++++++++--- .../Resources/Modules/Admin/Modules/Export.resx | 9 ++++++--- Oqtane.Client/Services/Interfaces/IModuleService.cs | 3 ++- Oqtane.Client/Services/ModuleService.cs | 4 ++-- Oqtane.Server/Controllers/ModuleController.cs | 10 +++++----- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Export.razor b/Oqtane.Client/Modules/Admin/Modules/Export.razor index ab714221..f7fd3a57 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Export.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Export.razor @@ -27,6 +27,12 @@
+
+ +
+ +
+

@@ -39,6 +45,7 @@ @code { private string _content = string.Empty; private FileManager _filemanager; + private string _filename = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Export Content"; @@ -62,12 +69,12 @@ try { var folderid = _filemanager.GetFolderId(); - if (folderid != -1) + if (folderid != -1 && !string.IsNullOrEmpty(_filename)) { - var result = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid); + var result = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename); if (result.Success) { - AddModuleMessage(string.Format(Localizer["Success.Export.File"], result.Message), MessageType.Success); + AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success); } else { diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx index 90e614c2..0a365dfd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx @@ -148,9 +148,12 @@ Select a folder where you wish to save the exported content - Please Select A Folder Before Choosing Export + Please Select A Folder And Provide A Filename Before Choosing Export - - Content Was Successfully Exported To Specified Folder With Filename {0} + + Filename: + + + Specify a name for the file (without an extension) \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/IModuleService.cs b/Oqtane.Client/Services/Interfaces/IModuleService.cs index 34a9c1b1..a2334a20 100644 --- a/Oqtane.Client/Services/Interfaces/IModuleService.cs +++ b/Oqtane.Client/Services/Interfaces/IModuleService.cs @@ -66,7 +66,8 @@ namespace Oqtane.Services /// /// /// + /// /// success/failure - Task ExportModuleAsync(int moduleId, int pageId, int folderId); + Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename); } } diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs index 1e688610..68d1e4fd 100644 --- a/Oqtane.Client/Services/ModuleService.cs +++ b/Oqtane.Client/Services/ModuleService.cs @@ -51,9 +51,9 @@ namespace Oqtane.Services return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}"); } - public async Task ExportModuleAsync(int moduleId, int pageId, int folderId) + public async Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename) { - return await PostJsonAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}", null); + return await PostJsonAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null); } } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index e075e5e5..012f2e11 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -256,15 +256,15 @@ namespace Oqtane.Controllers return content; } - // POST api//export?moduleid=x&pageid=y&folderid=z + // POST api//export?moduleid=x&pageid=y&folderid=z&filename=a [HttpPost("export")] [Authorize(Roles = RoleNames.Registered)] - public Result Export(int moduleid, int pageid, int folderid) + public Result Export(int moduleid, int pageid, int folderid, string filename) { var result = new Result(false); var module = _modules.GetModule(moduleid); if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) && - _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit)) + _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename)) { // get content var content = _modules.ExportModule(moduleid); @@ -277,8 +277,8 @@ namespace Oqtane.Controllers Directory.CreateDirectory(folderPath); } - // create text file - var filename = Utilities.GetTypeNameLastSegment(module.ModuleDefinitionName, 0) + moduleid.ToString() + ".json"; + // create json file + filename = Path.GetFileNameWithoutExtension(filename) + ".json"; string filepath = Path.Combine(folderPath, filename); if (System.IO.File.Exists(filepath)) { From c57c6abb1b66acbaafe3ef48c2c83ab21df11f5e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 15 May 2025 11:06:04 -0400 Subject: [PATCH 63/78] update module export resource info --- Oqtane.Client/Modules/Admin/Modules/Export.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Export.razor b/Oqtane.Client/Modules/Admin/Modules/Export.razor index f7fd3a57..d72ec689 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Export.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Export.razor @@ -6,7 +6,7 @@ @inject IStringLocalizer SharedLocalizer - +
diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx index 0a365dfd..23378774 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx @@ -135,8 +135,8 @@ Export Content - - Text + + Content File From eb5a0dc1c9f121e83ab78bd80fbf03696892e8a1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 08:25:50 -0400 Subject: [PATCH 64/78] improve filename validation in module content export --- Oqtane.Client/Modules/Admin/Modules/Export.razor | 9 +++++++-- .../Services/Interfaces/IModuleService.cs | 4 ++-- Oqtane.Client/Services/ModuleService.cs | 4 ++-- Oqtane.Server/Controllers/FileController.cs | 1 - Oqtane.Server/Controllers/ModuleController.cs | 16 ++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Export.razor b/Oqtane.Client/Modules/Admin/Modules/Export.razor index d72ec689..10831a56 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Export.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Export.razor @@ -50,6 +50,11 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Export Content"; + protected override void OnInitialized() + { + _filename = Utilities.GetFriendlyUrl(ModuleState.Title); + } + private async Task ExportText() { try @@ -71,8 +76,8 @@ var folderid = _filemanager.GetFolderId(); if (folderid != -1 && !string.IsNullOrEmpty(_filename)) { - var result = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename); - if (result.Success) + var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename); + if (fileid != -1) { AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success); } diff --git a/Oqtane.Client/Services/Interfaces/IModuleService.cs b/Oqtane.Client/Services/Interfaces/IModuleService.cs index a2334a20..ea6beab3 100644 --- a/Oqtane.Client/Services/Interfaces/IModuleService.cs +++ b/Oqtane.Client/Services/Interfaces/IModuleService.cs @@ -67,7 +67,7 @@ namespace Oqtane.Services /// /// /// - /// success/failure - Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename); + /// file id + Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename); } } diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs index 68d1e4fd..ac093bed 100644 --- a/Oqtane.Client/Services/ModuleService.cs +++ b/Oqtane.Client/Services/ModuleService.cs @@ -51,9 +51,9 @@ namespace Oqtane.Services return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}"); } - public async Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename) + public async Task ExportModuleAsync(int moduleId, int pageId, int folderId, string filename) { - return await PostJsonAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null); + return await PostJsonAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null); } } } diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 7ead95d7..f0b72f22 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Cors; using System.IO.Compression; using Oqtane.Services; using Microsoft.Extensions.Primitives; -using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Net.Http.Headers; // ReSharper disable StringIndexOfIsCultureSpecific.1 diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 012f2e11..61f69f33 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -10,9 +10,6 @@ using Oqtane.Repository; using Oqtane.Security; using System.Net; using System.IO; -using System; -using static System.Net.WebRequestMethods; -using System.Net.Http; namespace Oqtane.Controllers { @@ -259,9 +256,9 @@ namespace Oqtane.Controllers // POST api//export?moduleid=x&pageid=y&folderid=z&filename=a [HttpPost("export")] [Authorize(Roles = RoleNames.Registered)] - public Result Export(int moduleid, int pageid, int folderid, string filename) + public int Export(int moduleid, int pageid, int folderid, string filename) { - var result = new Result(false); + var fileid = -1; var module = _modules.GetModule(moduleid); if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename)) @@ -278,7 +275,7 @@ namespace Oqtane.Controllers } // create json file - filename = Path.GetFileNameWithoutExtension(filename) + ".json"; + filename = Utilities.GetFriendlyUrl(Path.GetFileNameWithoutExtension(filename)) + ".json"; string filepath = Path.Combine(folderPath, filename); if (System.IO.File.Exists(filepath)) { @@ -298,9 +295,7 @@ namespace Oqtane.Controllers file.Size = (int)new FileInfo(filepath).Length; _files.UpdateFile(file); } - - result.Success = true; - result.Message = filename; + fileid = file.FileId; _logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid); } @@ -309,7 +304,8 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } - return result; + + return fileid; } // POST api//import?moduleid=x&pageid=y From bbd6f13f369d0f3871c31ace90c5bafc62db42cd Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 09:09:07 -0400 Subject: [PATCH 65/78] fix initialization issue related to time zones --- .../Modules/Admin/Register/Index.razor | 143 +++++++++--------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor index 13920441..712ba186 100644 --- a/Oqtane.Client/Modules/Admin/Register/Index.razor +++ b/Oqtane.Client/Modules/Admin/Register/Index.razor @@ -8,88 +8,92 @@ @inject IStringLocalizer SharedLocalizer @inject ISettingService SettingService -@if (PageState.Site.AllowRegistration) +@if (_initialized) { - if (!_userCreated) + @if (PageState.Site.AllowRegistration) { - if (PageState.User != null) + if (!_userCreated) { - - } - else - { - -
-
-
- -
- + if (PageState.User != null) + { + + } + else + { + + +
+
+ +
+ +
-
-
- -
-
- - +
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
-
- -
-
- - -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- - - @if (_allowsitelogin) - {
+ + + @if (_allowsitelogin) + { +
-
- @Localizer["Login"] - } - +
+ @Localizer["Login"] + } + + } } - } -} -else -{ - + } + else + { + + } } @code { + private bool _initialized = false; private List _timezones; private string _passwordrequirements; private string _username = string.Empty; @@ -113,6 +117,7 @@ else _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); _timezones = await TimeZoneService.GetTimeZonesAsync(); _timezoneid = PageState.Site.TimeZoneId; + _initialized = true; } protected override void OnParametersSet() From 1f05d12ef55c06edbfd4000191309ad9cf3575d0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 09:39:57 -0400 Subject: [PATCH 66/78] fix spelling mistake --- Oqtane.Server/Controllers/UserController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 7a0f3bfd..37648c4f 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -131,7 +131,7 @@ namespace Oqtane.Controllers filtered.TwoFactorCode = ""; filtered.SecurityStamp = ""; - // include private properties if authenticated user is accessing their own user account os is an administrator + // include private properties if authenticated user is accessing their own user account or is an administrator if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId) { filtered.Email = user.Email; From ff6a810ad589126cceee37fa255af9b4f4b22a58 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 11:13:03 -0400 Subject: [PATCH 67/78] Fix #4789 - allow user email verification to be managed by administrator --- Oqtane.Client/Modules/Admin/Login/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Users/Edit.razor | 28 +++++++++---- .../Resources/Modules/Admin/Login/Index.resx | 4 +- .../Resources/Modules/Admin/Users/Edit.resx | 6 +++ Oqtane.Server/Controllers/UserController.cs | 10 ++++- Oqtane.Server/Managers/UserManager.cs | 39 ++++++++++++------- 6 files changed, 63 insertions(+), 26 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index f10c0203..f0a057d0 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -144,7 +144,7 @@ else user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); if (user != null) { - await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); + await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username); AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); } else diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index 549059e4..c6401a47 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -18,13 +18,13 @@
- +
- +
@@ -33,7 +33,7 @@
- +
@@ -42,13 +42,22 @@
- +
- + +
+ +
+
+
+
@@ -68,7 +77,7 @@ @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) {
- +
- +
@@ -167,6 +176,7 @@ private string _togglepassword = string.Empty; private string _confirm = string.Empty; private string _email = string.Empty; + private string _confirmed = string.Empty; private string _displayname = string.Empty; private string _timezoneid = string.Empty; private string _isdeleted; @@ -204,6 +214,7 @@ { _username = user.Username; _email = user.Email; + _confirmed = user.EmailConfirmed.ToString(); _displayname = user.DisplayName; _timezoneid = PageState.User.TimeZoneId; _isdeleted = user.IsDeleted.ToString(); @@ -255,6 +266,7 @@ user.Username = _username; user.Password = _password; user.Email = _email; + user.EmailConfirmed = bool.Parse(_confirmed); user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; user.TimeZoneId = _timezoneid; if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index 0b1a8780..f1fb0c8a 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -121,10 +121,10 @@ Forgot Password - User Account Verified Successfully. You Can Now Login With Your Username And Password Below. + User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password. - User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions. + User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions. User Account Linked Successfully. You Can Now Login With Your External Login Below. diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx index df4ccc95..ff38ec85 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Edit.resx @@ -216,4 +216,10 @@ The user's time zone + + Confirmed? + + + Indicates if the user's email is verified + \ No newline at end of file diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 37648c4f..7789a2d3 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -140,6 +140,7 @@ namespace Oqtane.Controllers filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; filtered.TwoFactorRequired = user.TwoFactorRequired; + filtered.EmailConfirmed = user.EmailConfirmed; filtered.Roles = user.Roles; filtered.CreatedBy = user.CreatedBy; filtered.CreatedOn = user.CreatedOn; @@ -200,10 +201,15 @@ namespace Oqtane.Controllers [Authorize] public async Task Put(int id, [FromBody] User user) { - if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && _users.GetUser(user.UserId, false) != null + var existing = _userManager.GetUser(user.UserId, user.SiteId); + if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) { - user.EmailConfirmed = User.IsInRole(RoleNames.Admin); + // only administrators can update the email confirmation + if (!User.IsInRole(RoleNames.Admin)) + { + user.EmailConfirmed = existing.EmailConfirmed; + } user = await _userManager.UpdateUser(user); } else diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 84679d23..5e1e6e64 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -65,7 +65,12 @@ namespace Oqtane.Managers { user.SiteId = siteid; user.Roles = GetUserRoles(user.UserId, user.SiteId); - user.SecurityStamp = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult()?.SecurityStamp; + var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult(); + if (identityuser != null) + { + user.SecurityStamp = identityuser.SecurityStamp; + user.EmailConfirmed = identityuser.EmailConfirmed; + } user.Settings = _settings.GetSettings(EntityNames.User, user.UserId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); } @@ -245,22 +250,30 @@ namespace Oqtane.Managers { identityuser.Email = user.Email; await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated - - // if email address changed and it is not confirmed, verification is required for new email address - if (!user.EmailConfirmed) - { - string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, user, "User Account Verification", body); - _notifications.AddNotification(notification); - } } if (user.EmailConfirmed) { - var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); + if (!identityuser.EmailConfirmed) + { + var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); + + string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password."; + var notification = new Notification(user.SiteId, user, "User Account Verification", body); + _notifications.AddNotification(notification); + } + } + else + { + identityuser.EmailConfirmed = false; + await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated + + string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Account Verification", body); + _notifications.AddNotification(notification); } user = _users.UpdateUser(user); From 5bde40ec2bd042f27f41c26deb7a1afa4dc4c074 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 11:46:53 -0400 Subject: [PATCH 68/78] improve messaging --- Oqtane.Client/Resources/Modules/Admin/Login/Index.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index f1fb0c8a..c60c0716 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -133,7 +133,7 @@ External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions. - Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User. + Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification. Please Provide All Required Fields From fe9f1897341868e997e3b2ed9491f1f5dfe7f57f Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 11:53:04 -0400 Subject: [PATCH 69/78] improve comment --- Oqtane.Shared/Models/Folder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index da875219..47177cdb 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -43,7 +43,7 @@ namespace Oqtane.Models public string Path { get; set; } /// - /// Sorting order of the folder + /// Sorting order of the folder ** not used as folders are sorted in alphabetical order ** /// public int Order { get; set; } From a4370829526a261f8a1cefaff6608a46ea0ed87b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 16 May 2025 12:11:03 -0400 Subject: [PATCH 70/78] use consistent authorization method --- Oqtane.Server/Controllers/UserController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 7789a2d3..859bd50c 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -205,8 +205,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) { - // only administrators can update the email confirmation - if (!User.IsInRole(RoleNames.Admin)) + // only authorized users can update the email confirmation + if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin)) { user.EmailConfirmed = existing.EmailConfirmed; } From 4b05f7fdad4a204bd52e926e9183479e9252e9e6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 May 2025 15:14:49 -0700 Subject: [PATCH 71/78] imprvoe help text --- Oqtane.Client/Modules/Admin/UrlMappings/Add.razor | 4 ++-- Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor | 4 ++-- Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx | 4 ++-- Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor index 8f43985d..68956c40 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor @@ -8,7 +8,7 @@
- +
@@ -17,7 +17,7 @@
- +
diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor index 78a062f9..c6cb2e0e 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor @@ -8,13 +8,13 @@
- +
- +
diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx index a8de1c59..aeae4b29 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx @@ -121,10 +121,10 @@ Redirect To: - A relative or absolute Url where the user will be redirected. Use "/" for site root path. + A Url where the user will be redirected (absolute or relative). Use '/' for site root path. - An absolute Url for this site + A Url identifying a path to a specific page in the site (absolute or relative) Url: diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx index 0b5d799a..b51c4366 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Edit.resx @@ -121,10 +121,10 @@ Redirect To: - A relative or absolute Url where the user will be redirected. Use "/" for site root path. + A Url where the user will be redirected (absolute or relative). Use '/' for site root path. - A relative Url identifying a path to a specific page in the site + A Url identifying a path to a specific page in the site (absolute or relative) Url: From 2b6ba0f4105ef18d00155275e61a847f4980386d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 May 2025 15:32:27 -0700 Subject: [PATCH 72/78] ensure Content folder is empty when packaging --- Oqtane.Package/release.cmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index 0452d806..8bc6518a 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -9,6 +9,8 @@ nuget.exe pack Oqtane.Framework.nuspec del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release +del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" > NUL +rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" setlocal ENABLEDELAYEDEXPANSION From c098839881e2b50081820e8f1ea87da6d6626418 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 19 May 2025 21:00:35 -0700 Subject: [PATCH 73/78] fix #5205 add support for inheritance when loading Resources from ModuleBase or ThemeBase --- Oqtane.Client/Modules/ModuleBase.cs | 19 +++++++++++-------- Oqtane.Client/Themes/ThemeBase.cs | 19 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 49d6b00b..18a8a5e9 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -79,18 +79,21 @@ namespace Oqtane.Modules { List resources = null; var type = GetType(); - if (type.BaseType == typeof(ModuleBase)) + if (type.IsSubclassOf(typeof(ModuleBase))) { - if (PageState.Page.Resources != null) + if (type.IsSubclassOf(typeof(ModuleControlBase))) { - resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList(); + if (Resources != null) + { + resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + } } - } - else // modulecontrolbase - { - if (Resources != null) + else // ModuleBase { - resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + if (PageState.Page.Resources != null) + { + resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList(); + } } } if (resources != null && resources.Any()) diff --git a/Oqtane.Client/Themes/ThemeBase.cs b/Oqtane.Client/Themes/ThemeBase.cs index aa5eaf5b..4359f48e 100644 --- a/Oqtane.Client/Themes/ThemeBase.cs +++ b/Oqtane.Client/Themes/ThemeBase.cs @@ -43,18 +43,21 @@ namespace Oqtane.Themes { List resources = null; var type = GetType(); - if (type.BaseType == typeof(ThemeBase)) + if (type.IsSubclassOf(typeof(ThemeBase))) { - if (PageState.Page.Resources != null) + if (type.IsSubclassOf(typeof(ThemeControlBase)) || type.IsSubclassOf(typeof(ContainerBase))) { - resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Page && item.Namespace == type.Namespace).ToList(); + if (Resources != null) + { + resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + } } - } - else // themecontrolbase, containerbase - { - if (Resources != null) + else // ThemeBase { - resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + if (PageState.Page.Resources != null) + { + resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Page && item.Namespace == type.Namespace).ToList(); + } } } if (resources != null && resources.Any()) From 7fff5c0d18601bc1e0dd359586c3db81abb474eb Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Sun, 25 May 2025 10:55:49 +0200 Subject: [PATCH 74/78] Fix for ModuleBase ReplaceTokens #5332 Replaced the ReplaceTokens logic to replace all tokens in the string --- Oqtane.Client/Modules/ModuleBase.cs | 126 ++++++++++++---------------- 1 file changed, 54 insertions(+), 72 deletions(-) diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 18a8a5e9..2dac151e 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -1,16 +1,16 @@ -using Microsoft.AspNetCore.Components; -using Oqtane.Shared; -using Oqtane.Models; -using System.Threading.Tasks; -using Oqtane.Services; using System; -using Oqtane.Enums; -using Oqtane.UI; using System.Collections.Generic; -using Microsoft.JSInterop; -using System.Linq; using System.Dynamic; -using System.Reflection; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using Oqtane.Enums; +using Oqtane.Models; +using Oqtane.Services; +using Oqtane.Shared; +using Oqtane.UI; namespace Oqtane.Modules { @@ -424,72 +424,54 @@ namespace Oqtane.Modules public string ReplaceTokens(string content, object obj) { - var tokens = new List(); - var pos = content.IndexOf("["); - if (pos != -1) - { - if (content.IndexOf("]", pos) != -1) - { - var token = content.Substring(pos, content.IndexOf("]", pos) - pos + 1); - if (token.Contains(":")) - { - tokens.Add(token.Substring(1, token.Length - 2)); - } - } - pos = content.IndexOf("[", pos + 1); - } - if (tokens.Count != 0) - { - foreach (string token in tokens) - { - var segments = token.Split(":"); - if (segments.Length >= 2 && segments.Length <= 3) - { - var objectName = string.Join(":", segments, 0, segments.Length - 1); - var propertyName = segments[segments.Length - 1]; - var propertyValue = ""; + // Pattern: [Object:Property] or [Object:SubObject:Property] + var pattern = @"\[(\w+(?::\w+){1,2})\]"; - switch (objectName) - { - case "ModuleState": - propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString(); - break; - case "PageState": - propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null).ToString(); - break; - case "PageState:Alias": - propertyValue = PageState.Alias.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null).ToString(); - break; - case "PageState:Site": - propertyValue = PageState.Site.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null).ToString(); - break; - case "PageState:Page": - propertyValue = PageState.Page.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null).ToString(); - break; - case "PageState:User": - propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null).ToString(); - break; - case "PageState:Route": - propertyValue = PageState.Route.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null).ToString(); - break; - default: - if (obj != null && obj.GetType().Name == objectName) - { - propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null).ToString(); - } - break; - } - if (propertyValue != null) - { - content = content.Replace("[" + token + "]", propertyValue); - } + return Regex.Replace(content, pattern, match => + { + string token = match.Groups[1].Value; + var segments = token.Split(':'); + if (segments.Length < 2 || segments.Length > 3) + return match.Value; // Leave as is if not a valid token - } + string objectName = string.Join(":", segments, 0, segments.Length - 1); + string propertyName = segments[segments.Length - 1]; + string propertyValue = null; + + switch (objectName) + { + case "ModuleState": + propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null)?.ToString(); + break; + case "PageState": + propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null)?.ToString(); + break; + case "PageState:Alias": + propertyValue = PageState.Alias?.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null)?.ToString(); + break; + case "PageState:Site": + propertyValue = PageState.Site?.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null)?.ToString(); + break; + case "PageState:Page": + propertyValue = PageState.Page?.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null)?.ToString(); + break; + case "PageState:User": + propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null)?.ToString(); + break; + case "PageState:Route": + propertyValue = PageState.Route?.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null)?.ToString(); + break; + default: + if (obj != null && obj.GetType().Name == objectName) + { + propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null)?.ToString(); + } + break; } - } - return content; + + return propertyValue ?? match.Value; // If not found, leave token as is + }); } - // date methods public DateTime? UtcToLocal(DateTime? datetime) { From 543e9339c74da40d2400070822f2c250b53a5e4a Mon Sep 17 00:00:00 2001 From: Cody Date: Sun, 25 May 2025 09:34:09 -0700 Subject: [PATCH 75/78] Update Swashbuckle.AspNetCore Package Dependency to 8.1.2 --- Oqtane.Server/Oqtane.Server.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 76bb68c8..2068c0ea 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -48,7 +48,7 @@ - + From ef4fbcbb8a8fc5da4ee79fe9d9e8cdeac32476cd Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 28 May 2025 17:30:19 +0200 Subject: [PATCH 76/78] Update ModuleBase.cs This method replaces all tokens in the format [Object:Property] or [Object:SubObject:Property] within a string. Efficient string parsing and reflection ensure flexibility with performance. It supports deeply nested properties, optional default fallback values (e.g. [PageState:User:Email|default@email.com]), and uses caching to optimize repeated token resolution without regex. --- Oqtane.Client/Modules/ModuleBase.cs | 114 +++++++++++++++++----------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 2dac151e..39681067 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; -using System.Text.RegularExpressions; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -424,54 +424,82 @@ namespace Oqtane.Modules public string ReplaceTokens(string content, object obj) { - // Pattern: [Object:Property] or [Object:SubObject:Property] - var pattern = @"\[(\w+(?::\w+){1,2})\]"; + // Using StringBuilder avoids the performance penalty of repeated string allocations + // that occur with string.Replace or string concatenation inside loops. + var sb = new StringBuilder(); + var cache = new Dictionary(); // Cache to store resolved tokens + int index = 0; - return Regex.Replace(content, pattern, match => + // Loop through content to find and replace all tokens + while (index < content.Length) { - string token = match.Groups[1].Value; - var segments = token.Split(':'); - if (segments.Length < 2 || segments.Length > 3) - return match.Value; // Leave as is if not a valid token - - string objectName = string.Join(":", segments, 0, segments.Length - 1); - string propertyName = segments[segments.Length - 1]; - string propertyValue = null; - - switch (objectName) + int start = content.IndexOf('[', index); // Find start of token + if (start == -1) { - case "ModuleState": - propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null)?.ToString(); - break; - case "PageState": - propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null)?.ToString(); - break; - case "PageState:Alias": - propertyValue = PageState.Alias?.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null)?.ToString(); - break; - case "PageState:Site": - propertyValue = PageState.Site?.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null)?.ToString(); - break; - case "PageState:Page": - propertyValue = PageState.Page?.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null)?.ToString(); - break; - case "PageState:User": - propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null)?.ToString(); - break; - case "PageState:Route": - propertyValue = PageState.Route?.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null)?.ToString(); - break; - default: - if (obj != null && obj.GetType().Name == objectName) - { - propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null)?.ToString(); - } - break; + sb.Append(content, index, content.Length - index); // Append remaining content + break; } - return propertyValue ?? match.Value; // If not found, leave token as is - }); + int end = content.IndexOf(']', start); // Find end of token + if (end == -1) + { + sb.Append(content, index, content.Length - index); // Append unmatched content + break; + } + + sb.Append(content, index, start - index); // Append content before token + + string token = content.Substring(start + 1, end - start - 1); // Extract token without brackets + string[] parts = token.Split('|', 2); // Separate default fallback if present + string key = parts[0]; + string fallback = parts.Length == 2 ? parts[1] : null; + + if (!cache.TryGetValue(token, out string replacement)) // Check cache first + { + replacement = "[" + token + "]"; // Default replacement is original token + string[] segments = key.Split(':'); + + if (segments.Length >= 2) + { + object current = GetTarget(segments[0], obj); // Start from root object + for (int i = 1; i < segments.Length && current != null; i++) + { + var type = current.GetType(); + var prop = type.GetProperty(segments[i]); + current = prop?.GetValue(current); + } + + if (current != null) + { + replacement = current.ToString(); + } + else if (fallback != null) + { + replacement = fallback; // Use fallback if available + } + } + cache[token] = replacement; // Store in cache + } + + sb.Append(replacement); // Append replacement value + index = end + 1; // Move index past token + } + + return sb.ToString(); } + + // Resolve the object instance for a given object name + // Easy to extend with additional object types + private object GetTarget(string name, object obj) + { + return name switch + { + "ModuleState" => ModuleState, + "PageState" => PageState, + _ => (obj != null && obj.GetType().Name == name) ? obj : null // Fallback to obj + }; + } + // date methods public DateTime? UtcToLocal(DateTime? datetime) { From 9c333232e204ad897d4c00a84132159c489c85c1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 29 May 2025 11:53:14 -0400 Subject: [PATCH 77/78] fix #5329 - clear Options after updating User Settings --- .../Controllers/SettingController.cs | 74 ++++++++++++------- .../OqtaneSiteIdentityBuilderExtensions.cs | 1 - .../Options/SiteOptionsCache.cs | 1 - 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 2579d379..fd8708c1 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -24,26 +24,50 @@ namespace Oqtane.Controllers private readonly IPageModuleRepository _pageModules; private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; - private readonly IAliasAccessor _aliasAccessor; - private readonly IOptionsMonitorCache _cookieCache; - private readonly IOptionsMonitorCache _oidcCache; - private readonly IOptionsMonitorCache _oauthCache; - private readonly IOptionsMonitorCache _identityCache; + + private readonly IOptions _cookieOptions; + private readonly IOptionsSnapshot _cookieOptionsSnapshot; + private readonly IOptionsMonitorCache _cookieOptionsMonitorCache; + + private readonly IOptions _oidcOptions; + private readonly IOptionsSnapshot _oidcOptionsSnapshot; + private readonly IOptionsMonitorCache _oidcOptionsMonitorCache; + + private readonly IOptions _oauthOptions; + private readonly IOptionsSnapshot _oauthOptionsSnapshot; + private readonly IOptionsMonitorCache _oauthOptionsMonitorCache; + + private readonly IOptions _identityOptions; + private readonly IOptionsSnapshot _identityOptionsSnapshot; + private readonly IOptionsMonitorCache _identityOptionsMonitorCache; + private readonly ILogManager _logger; private readonly Alias _alias; private readonly string _visitorCookie; - public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, IOptionsMonitorCache cookieCache, IOptionsMonitorCache oidcCache, IOptionsMonitorCache oauthCache, IOptionsMonitorCache identityCache, ILogManager logger) + public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, + IOptions cookieOptions, IOptionsSnapshot cookieOptionsSnapshot, IOptionsMonitorCache cookieOptionsMonitorCache, + IOptions oidcOptions, IOptionsSnapshot oidcOptionsSnapshot, IOptionsMonitorCache oidcOptionsMonitorCache, + IOptions oauthOptions, IOptionsSnapshot oauthOptionsSnapshot, IOptionsMonitorCache oauthOptionsMonitorCache, + IOptions identityOptions, IOptionsSnapshot identityOptionsSnapshot, IOptionsMonitorCache identityOptionsMonitorCache, + ILogManager logger) { _settings = settings; _pageModules = pageModules; _userPermissions = userPermissions; _syncManager = syncManager; - _aliasAccessor = aliasAccessor; - _cookieCache = cookieCache; - _oidcCache = oidcCache; - _oauthCache = oauthCache; - _identityCache = identityCache; + _cookieOptions = cookieOptions; + _cookieOptionsSnapshot = cookieOptionsSnapshot; + _cookieOptionsMonitorCache = cookieOptionsMonitorCache; + _oidcOptions = oidcOptions; + _oidcOptionsSnapshot = oidcOptionsSnapshot; + _oidcOptionsMonitorCache = oidcOptionsMonitorCache; + _oauthOptions = oauthOptions; + _oauthOptionsSnapshot = oauthOptionsSnapshot; + _oauthOptionsMonitorCache = oauthOptionsMonitorCache; + _identityOptions = identityOptions; + _identityOptionsSnapshot = identityOptionsSnapshot; + _identityOptionsMonitorCache = identityOptionsMonitorCache; _logger = logger; _alias = tenantManager.GetAlias(); _visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString(); @@ -210,21 +234,21 @@ namespace Oqtane.Controllers [Authorize(Roles = RoleNames.Admin)] public void Clear() { - // clear SiteOptionsCache for each option type - var cookieCache = new SiteOptionsCache(_aliasAccessor); - cookieCache.Clear(); - var oidcCache = new SiteOptionsCache(_aliasAccessor); - oidcCache.Clear(); - var oauthCache = new SiteOptionsCache(_aliasAccessor); - oauthCache.Clear(); - var identityCache = new SiteOptionsCache(_aliasAccessor); - identityCache.Clear(); + (_cookieOptions as SiteOptionsManager).Reset(); + (_cookieOptionsSnapshot as SiteOptionsManager).Reset(); + _cookieOptionsMonitorCache.Clear(); - // clear IOptionsMonitorCache for each option type - _cookieCache.Clear(); - _oidcCache.Clear(); - _oauthCache.Clear(); - _identityCache.Clear(); + (_oidcOptions as SiteOptionsManager).Reset(); + (_oidcOptionsSnapshot as SiteOptionsManager).Reset(); + _oidcOptionsMonitorCache.Clear(); + + (_oauthOptions as SiteOptionsManager).Reset(); + (_oauthOptionsSnapshot as SiteOptionsManager).Reset(); + _oauthOptionsMonitorCache.Clear(); + + (_identityOptions as SiteOptionsManager).Reset(); + (_identityOptionsSnapshot as SiteOptionsManager).Reset(); + _identityOptionsMonitorCache.Clear(); _logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared"); } diff --git a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs index 6234d2fb..d00c01ae 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Oqtane.Models; using Microsoft.AspNetCore.Identity; using System; diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs index e4737d9a..5c802fa9 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs @@ -19,7 +19,6 @@ namespace Oqtane.Infrastructure { var cache = map.GetOrAdd(GetKey(), new OptionsCache()); cache.Clear(); - } public TOptions GetOrAdd(string name, Func createOptions) From d5f19d97e2d5c1cc1d660b943a726d1ef718c71a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 29 May 2025 15:04:12 -0400 Subject: [PATCH 78/78] change id for header/footer --- Oqtane.Client/Modules/Admin/Modules/Settings.razor | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index b7cf4f4c..297a365a 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -101,15 +101,15 @@
- +
- +
- +
- +