diff --git a/Directory.Build.props b/Directory.Build.props index e1f15325..47e4da1a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net10.0 Debug;Release - 10.1.0 + 10.1.1 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/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 https://github.com/oqtane/oqtane.framework Git diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj index 8874d9db..1d618b2d 100644 --- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj +++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj @@ -23,7 +23,7 @@ - + diff --git a/Oqtane.Application/Oqtane.Application.Template.nuspec b/Oqtane.Application/Oqtane.Application.Template.nuspec index 9ba1ed74..a5acb9a9 100644 --- a/Oqtane.Application/Oqtane.Application.Template.nuspec +++ b/Oqtane.Application/Oqtane.Application.Template.nuspec @@ -2,7 +2,7 @@ Oqtane.Application.Template - 10.1.0 + 10.1.1 Oqtane Application Template For Blazor Shaun Walker false diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index 3526407a..c8f34122 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj index 7689e461..636ff9bf 100644 --- a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj +++ b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj @@ -11,7 +11,7 @@ - + diff --git a/Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor b/Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor index dff7e341..a62d686b 100644 --- a/Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor +++ b/Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor @@ -28,7 +28,7 @@ - Site Properties? + Site Info? @SharedLocalizer["Yes"] @@ -37,7 +37,7 @@ - Page Properties? + Page Info? @SharedLocalizer["Yes"] @@ -46,7 +46,7 @@ - Module Properties? + Module Info? @SharedLocalizer["Yes"] diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index bbb2698e..0fd25300 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -130,7 +130,7 @@ else private async Task LoadModuleDefinitions() { - _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); + _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList(); _packages = await PackageService.GetPackageUpdatesAsync("module"); } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 9b94e0c0..34f41758 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -1413,10 +1413,13 @@ { if (_siteGroupId != -1) { - var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(PageState.Site.SiteId, _siteGroupId); - if (siteGroupMember != null) + if (_siteId != -1) { - await SiteGroupMemberService.DeleteSiteGroupMemberAsync(siteGroupMember.SiteGroupId); + var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId); + if (siteGroupMember != null) + { + await SiteGroupMemberService.DeleteSiteGroupMemberAsync(siteGroupMember.SiteGroupMemberId); + } } var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId); diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index c1db2ae1..82b5dcd2 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -12,7 +12,7 @@ - + diff --git a/Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx b/Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx index e5339e4d..dc3b692c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx @@ -130,16 +130,16 @@ Specify if module content should be updated - Page Properties? + Page Info? - Specify if page properties should be updated (ie. name, title, headcontent, bodycontent) + Specify if page information should be updated (ie. name, title, head content, body content settings) - Site Properties? + Site Info? - Specify if site properties should be updated (ie. name, headcontent, bodycontent) + Specify if site information should be updated (ie. name, head content, body content, settings) Replace With: @@ -148,10 +148,10 @@ Specify the replacement content - Module Properties? + Module Info? - Specify if module properties should be updated (ie. title, header, footer) + Specify if module information should be updated (ie. title, header, footer settings) Your Global Replace Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient. diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index ffd54afa..72d14be6 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -303,7 +303,7 @@ _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); _containerType = PageState.Site.DefaultContainerType; _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId); - _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); + _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList(); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList(); _siteGroups = await SiteGroupService.GetSiteGroupsAsync(PageState.Site.SiteId); } @@ -312,7 +312,7 @@ private void CategoryChanged(ChangeEventArgs e) { _category = (string)e.Value; - _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); + _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList(); _moduleDefinitionName = "-"; _message = ""; } @@ -521,7 +521,7 @@ case "Edit": // get page management moduleid moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]); - NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, "Edit", $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}")); + NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}")); break; case "Copy": // get page management moduleid diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 18f449d8..4c346167 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -18,7 +18,7 @@ com.oqtane.maui - 10.1.0 + 10.1.1 1 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 42696e16..ae062ace 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 10.1.0 + 10.1.1 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/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 readme.md icon.png oqtane @@ -23,7 +23,7 @@ - + diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 25f116ad..f2e8a58e 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 10.1.0 + 10.1.1 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v10.1.0/Oqtane.Framework.10.1.0.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/download/v10.1.1/Oqtane.Framework.10.1.1.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 4caf53a3..095b3c88 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 10.1.0 + 10.1.1 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/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 readme.md icon.png oqtane @@ -26,8 +26,8 @@ - - + + diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index b1374aea..7e0c9cd2 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 10.1.0 + 10.1.1 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/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 readme.md icon.png oqtane @@ -20,7 +20,7 @@ - + diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 840f3de0..d1d8e2ef 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 10.1.0 + 10.1.1 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/v10.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1 readme.md icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 1849738e..b2611634 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.0.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.1.Install.zip" -Force diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index dba48f9b..4a84e86e 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.0.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.1.Upgrade.zip" -Force diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index a55b28a1..20d80d11 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -266,6 +266,28 @@ private void HandlePageNotFound(Site site, Page page, Route route) { + // filter + string useragent = (Context.Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.UserAgent] : "(none)"; + var settings = Context.GetSiteSettings(); + var filter = settings.GetValue("VisitorFilter", Constants.DefaultVisitorFilter); + foreach (string term in filter.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray()) + { + if (_remoteIPAddress.ToLower().Contains(term) || useragent.ToLower().Contains(term)) + { + // handle not found request in static mode + if (_renderMode == RenderModes.Static) + { + NavigationManager.NotFound(); + } + else + { + // redirect to 404 page + NavigationManager.NavigateTo(route.SiteUrl + "/404", true); + } + return; + } + } + // referrer will only be set if the link originated externally string referrer = (Context.Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.Referer] : ""; @@ -564,23 +586,6 @@ } } - private void SetLocalizationCookie(string cookieValue) - { - var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions - { - Expires = DateTimeOffset.UtcNow.AddYears(1), - SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute - Secure = true, // Ensure the cookie is only sent over HTTPS - HttpOnly = false // cookie is updated using JS Interop in Interactive render mode - }; - - Context.Response.Cookies.Append( - Shared.CookieRequestCultureProvider.DefaultCookieName, - cookieValue, - cookieOptions - ); - } - private async Task> GetPageResources(Alias alias, Site site, Page page, List modules, int moduleid, string action) { var resources = new List(); @@ -852,4 +857,21 @@ SetLocalizationCookie(cultureCookie); } } + + private void SetLocalizationCookie(string cookieValue) + { + var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions + { + Expires = DateTimeOffset.UtcNow.AddYears(1), + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute + Secure = true, // Ensure the cookie is only sent over HTTPS + HttpOnly = false // cookie is updated using JS Interop in Interactive render mode + }; + + Context.Response.Cookies.Append( + Shared.CookieRequestCultureProvider.DefaultCookieName, + cookieValue, + cookieOptions + ); + } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 61f69f33..fc3b4d64 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -264,7 +264,7 @@ namespace Oqtane.Controllers _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename)) { // get content - var content = _modules.ExportModule(moduleid); + var content = _modules.ExportModule(module, "Export Module"); // get folder var folder = _folders.GetFolder(folderid, false); @@ -317,7 +317,7 @@ namespace Oqtane.Controllers var module = _modules.GetModule(moduleid); if (ModelState.IsValid && module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit)) { - success = _modules.ImportModule(moduleid, content); + success = _modules.ImportModule(module, content, "Import Module"); if (success) { _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Content Imported {ModuleId}", moduleid); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index a23ba365..33a3ed0b 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Oqtane.Enums; @@ -239,10 +238,11 @@ namespace Oqtane.Controllers }; module = _modules.AddModule(module); - string content = _modules.ExportModule(pm.ModuleId); + // deep copy module content (includes settings) + string content = _modules.ExportModule(pm.Module, "Copy Page"); if (content != "") { - _modules.ImportModule(module.ModuleId, content); + _modules.ImportModule(module, content, "Copy Page"); } PageModule pagemodule = new PageModule(); @@ -509,6 +509,20 @@ namespace Oqtane.Controllers var toPage = _pages.GetPage(toPageId); if (toPage != null && toPage.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, toPage.PermissionList)) { + // copy page settings + var settings = _settings.GetSettings(EntityNames.Page, fromPage.PageId).ToList(); + foreach (var setting in settings) + { + _settings.AddSetting(new Setting + { + EntityName = setting.EntityName, + EntityId = toPage.PageId, + SettingName = setting.SettingName, + SettingValue = setting.SettingValue, + IsPrivate = setting.IsPrivate + }); + } + // copy modules List pageModules = _pageModules.GetPageModules(fromPage.SiteId).ToList(); foreach (PageModule pm in pageModules.Where(item => item.PageId == fromPage.PageId && !item.Module.AllPages && !item.IsDeleted)) @@ -544,10 +558,12 @@ namespace Oqtane.Controllers }).ToList(); module = _modules.AddModule(module); - string content = _modules.ExportModule(pm.ModuleId); + + // deep copy module content (includes settings) + string content = _modules.ExportModule(pm.Module, "Copy Page"); if (content != "") { - _modules.ImportModule(module.ModuleId, content); + _modules.ImportModule(module, content, "Copy Page"); } } else diff --git a/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs b/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs index 24e3ed19..b3d4ac08 100644 --- a/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs @@ -617,7 +617,11 @@ namespace Oqtane.Infrastructure private string SynchronizeModules(IServiceProvider provider, ISettingRepository settingRepository, IPageModuleRepository pageModuleRepository, IModuleRepository moduleRepository, SiteGroupMember siteGroupMember, List primaryPageModules, List secondaryPageModules, Page primaryPage, Page secondaryPage, int secondarySiteId) { var log = ""; - var removePageModules = secondaryPageModules.Where(item => item.PageId == secondaryPage.PageId).ToList(); + var removePageModules = new List(); + if (secondaryPage != null) + { + removePageModules = secondaryPageModules.Where(item => item.PageId == secondaryPage.PageId).ToList(); + } // iterate through primary modules on primary page foreach (var primaryPageModule in primaryPageModules.Where(item => item.PageId == primaryPage.PageId)) diff --git a/Oqtane.Server/Infrastructure/SiteTasks/GlobalReplaceTask.cs b/Oqtane.Server/Infrastructure/SiteTasks/GlobalReplaceTask.cs index c9b1efa6..c99ce10a 100644 --- a/Oqtane.Server/Infrastructure/SiteTasks/GlobalReplaceTask.cs +++ b/Oqtane.Server/Infrastructure/SiteTasks/GlobalReplaceTask.cs @@ -4,7 +4,6 @@ using System.Net; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Oqtane.Models; -using Oqtane.Modules; using Oqtane.Repository; using Oqtane.Shared; @@ -20,7 +19,9 @@ namespace Oqtane.Infrastructure var siteRepository = provider.GetRequiredService(); var pageRepository = provider.GetRequiredService(); var pageModuleRepository = provider.GetRequiredService(); - var TenantManager = provider.GetRequiredService(); + var moduleRepository = provider.GetRequiredService(); + var settingRepository = provider.GetRequiredService(); + var tenantManager = provider.GetRequiredService(); var syncManager = provider.GetRequiredService(); if (!string.IsNullOrEmpty(parameters)) @@ -59,6 +60,14 @@ namespace Oqtane.Infrastructure log += $"Site Updated"; refresh = true; } + if (globalReplace.Site) + { + if (UpdateSettings(settingRepository, EntityNames.Site, site.SiteId, find, replace, comparisonType)) + { + log += $"Site Settings Updated"; + refresh = true; + } + } var pages = pageRepository.GetPages(site.SiteId).ToList(); var pageModules = pageModuleRepository.GetPageModules(site.SiteId).ToList(); @@ -94,6 +103,14 @@ namespace Oqtane.Infrastructure log += $"Page Updated: /{page.Path}"; refresh = true; } + if (globalReplace.Pages) + { + if (UpdateSettings(settingRepository, EntityNames.Page, page.PageId, find, replace, comparisonType)) + { + log += $"Page Settings Updated"; + refresh = true; + } + } foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) { @@ -120,28 +137,24 @@ namespace Oqtane.Infrastructure log += $"Module Updated: {pageModule.Title} Page: /{page.Path}"; refresh = true; } + if (globalReplace.Modules) + { + if (UpdateSettings(settingRepository, EntityNames.Module, pageModule.ModuleId, find, replace, comparisonType)) + { + log += $"Module Settings Updated"; + refresh = true; + } + } // module content - if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "") + if (globalReplace.Content) { - Type moduleType = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType); - if (moduleType != null && moduleType.GetInterface(nameof(IPortable)) != null) + var content = moduleRepository.ExportModule(pageModule.Module, "Global Replace"); + if (!string.IsNullOrEmpty(content) && content.Contains(WebUtility.HtmlEncode(find), comparisonType)) { - try - { - var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType); - var moduleContent = ((IPortable)moduleObject).ExportModule(pageModule.Module); - if (!string.IsNullOrEmpty(moduleContent) && moduleContent.Contains(WebUtility.HtmlEncode(find), comparisonType) && globalReplace.Content) - { - moduleContent = moduleContent.Replace(WebUtility.HtmlEncode(find), WebUtility.HtmlEncode(replace), comparisonType); - ((IPortable)moduleObject).ImportModule(pageModule.Module, moduleContent, pageModule.Module.ModuleDefinition.Version); - log += $"Module Content Updated: {pageModule.Title} Page: /{page.Path}"; - } - } - catch (Exception ex) - { - log += $"Error Processing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}"; - } + content = content.Replace(WebUtility.HtmlEncode(find), WebUtility.HtmlEncode(replace), comparisonType); + moduleRepository.ImportModule(pageModule.Module, content, "Global Replace"); + log += $"Module Content Updated: {pageModule.Title} Page: /{page.Path}"; } } } @@ -150,7 +163,7 @@ namespace Oqtane.Infrastructure if (refresh) { // clear cache - syncManager.AddSyncEvent(TenantManager.GetAlias(), EntityNames.Site, site.SiteId, SyncEventActions.Refresh); + syncManager.AddSyncEvent(tenantManager.GetAlias(), EntityNames.Site, site.SiteId, SyncEventActions.Refresh); } } else @@ -160,5 +173,21 @@ namespace Oqtane.Infrastructure return log; } + + private bool UpdateSettings(ISettingRepository settingRepository, string entityName, int entityId, string find, string replace, StringComparison comparisonType) + { + var changed = false; + var settings = settingRepository.GetSettings(entityName, entityId).ToList(); + foreach (var setting in settings) + { + if (setting.SettingValue != null && setting.SettingValue.Contains(find, comparisonType)) + { + setting.SettingValue = setting.SettingValue.Replace(find, replace, comparisonType); + settingRepository.UpdateSetting(setting); + changed = true; + } + } + return changed; + } } } diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index b5ba8453..8694713e 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,8 +34,8 @@ - - + + diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index f01cd0b7..2c7d5ee6 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -48,7 +48,7 @@ namespace Oqtane.Pages public IActionResult OnGet(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(path) || _alias == null) { HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; return BrokenFile(); diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index fd1e11e7..ae68b41c 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -448,11 +448,20 @@ namespace Oqtane.Repository var route = routeAttributes.First().Template; if (!string.IsNullOrEmpty(route)) { - // @page "/route" (note that nested routes are not permitted) + // @page "/path" or @page "alias/path" (note that nested paths are not permitted) var pageTemplate = new PageTemplate(); - pageTemplate.AliasName = "*"; + if (route.StartsWith("/")) + { + pageTemplate.AliasName = "*"; // all sites + pageTemplate.Path = route.Substring(1); + } + else // route contains an alias name + { + var lastSlash = route.LastIndexOf('/'); + pageTemplate.AliasName = route.Substring(0, lastSlash); + pageTemplate.Path = route.Substring(lastSlash + 1); + } pageTemplate.Version = "*"; - pageTemplate.Path = route.Substring(1); pageTemplate.Update = false; pageTemplate.PageTemplateModules = new List(); diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs index 94291d5b..9a1549f5 100644 --- a/Oqtane.Server/Repository/ModuleRepository.cs +++ b/Oqtane.Server/Repository/ModuleRepository.cs @@ -20,7 +20,9 @@ namespace Oqtane.Repository Module GetModule(int moduleId, bool tracking); void DeleteModule(int moduleId); string ExportModule(int moduleId); + string ExportModule(Module module, string IPortableContext); bool ImportModule(int moduleId, string content); + bool ImportModule(Module module, string content, string IPortableContext); } public class ModuleRepository : IModuleRepository @@ -99,18 +101,23 @@ namespace Oqtane.Repository } public string ExportModule(int moduleId) + { + Module module = GetModule(moduleId); + return ExportModule(module, "Export Module"); + } + + public string ExportModule(Module module, string IPortableContext) { string content = ""; try { - Module module = GetModule(moduleId); if (module != null) { List moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList(); ModuleDefinition moduledefinition = moduledefinitions.FirstOrDefault(item => item.ModuleDefinitionName == module.ModuleDefinitionName); if (moduledefinition != null) { - var settings = _settings.GetSettings(EntityNames.Module, moduleId); + var settings = _settings.GetSettings(EntityNames.Module, module.ModuleId); ModuleContent modulecontent = new ModuleContent(); modulecontent.ModuleDefinitionName = moduledefinition.ModuleDefinitionName; @@ -125,6 +132,7 @@ namespace Oqtane.Repository { try { + module.IPortableContext = IPortableContext; module.Settings = settings.ToDictionary(x => x.SettingName, x => x.SettingValue); var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); modulecontent.Content = ((IPortable)moduleobject).ExportModule(module); @@ -149,30 +157,46 @@ namespace Oqtane.Repository } public bool ImportModule(int moduleId, string content) + { + Module module = GetModule(moduleId); + return ImportModule(module, content, "Import Module"); + } + + public bool ImportModule(Module module, string content, string IPortableContext) { bool success = false; try { - Module module = GetModule(moduleId); if (module != null) { List moduledefinitions = _moduleDefinitions.GetModuleDefinitions(module.SiteId).ToList(); ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionName == module.ModuleDefinitionName).FirstOrDefault(); if (moduledefinition != null) { - ModuleContent modulecontent = JsonSerializer.Deserialize(content.Replace("\n", "")); + var modulecontent = new ModuleContent(); + if (content.StartsWith("{") && content.EndsWith("}")) + { + // content was exported as a serialized ModuleContent object + modulecontent = JsonSerializer.Deserialize(content.Replace("\n", "")); + } + else + { + // raw content + modulecontent.ModuleDefinitionName = moduledefinition.ModuleDefinitionName; + modulecontent.Version = moduledefinition.Version; + modulecontent.Content = content; + } if (modulecontent.ModuleDefinitionName == moduledefinition.ModuleDefinitionName) { - var settings = _settings.GetSettings(EntityNames.Module, moduleId); - if (modulecontent.Settings != null) { + var settings = _settings.GetSettings(EntityNames.Module, module.ModuleId); foreach (var kvp in modulecontent.Settings) { var setting = settings.FirstOrDefault(item => item.SettingName == kvp.Key); if (setting == null) { - setting = new Setting { EntityName = EntityNames.Module, EntityId = moduleId, SettingName = kvp.Key, SettingValue = kvp.Value, IsPrivate = false }; + setting = new Setting { EntityName = EntityNames.Module, EntityId = module.ModuleId, SettingName = kvp.Key, SettingValue = kvp.Value, IsPrivate = false }; _settings.AddSetting(setting); } else @@ -193,7 +217,8 @@ namespace Oqtane.Repository { try { - module.Settings = _settings.GetSettings(EntityNames.Module, moduleId).ToDictionary(x => x.SettingName, x => x.SettingValue); + module.IPortableContext = IPortableContext; + module.Settings = _settings.GetSettings(EntityNames.Module, module.ModuleId).ToDictionary(x => x.SettingName, x => x.SettingValue); var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); ((IPortable)moduleobject).ImportModule(module, modulecontent.Content, modulecontent.Version); success = true; diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index f8bf4403..b2124298 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -1,14 +1,9 @@ -using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Models; -using Oqtane.Modules.Admin.Users; using Oqtane.Shared; namespace Oqtane.Repository diff --git a/Oqtane.Server/Repository/SiteGroupMemberRepository.cs b/Oqtane.Server/Repository/SiteGroupMemberRepository.cs index 82efb37a..e0d1ac78 100644 --- a/Oqtane.Server/Repository/SiteGroupMemberRepository.cs +++ b/Oqtane.Server/Repository/SiteGroupMemberRepository.cs @@ -51,6 +51,7 @@ namespace Oqtane.Repository { using var db = _dbContextFactory.CreateDbContext(); db.Entry(siteGroupMember).State = EntityState.Modified; + db.Entry(siteGroupMember.SiteGroup).State = EntityState.Unchanged; // prevent update of linked entity db.SaveChanges(); return siteGroupMember; } diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 4f917a3c..8828724c 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -532,27 +532,14 @@ namespace Oqtane.Repository _logger.Log(LogLevel.Error, "Site Template", LogFunction.Other, ex, "Error Processing Page Module {PageModule}", pageModule); } } - - if (pageTemplateModule.Content != "" && moduleDefinition.ServerManagerType != "") + if (!string.IsNullOrEmpty(pageTemplateModule.Content)) { - Type moduletype = Type.GetType(moduleDefinition.ServerManagerType); - if (moduletype != null && moduletype.GetInterface(nameof(IPortable)) != null) + var module = _moduleRepository.GetModule(pageModule.ModuleId); + if (!_moduleRepository.ImportModule(module, pageTemplateModule.Content, "Site Template")) { - try + if (alias != null) { - var module = _moduleRepository.GetModule(pageModule.ModuleId); - if (module != null) - { - var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); - ((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version); - } - } - catch (Exception ex) - { - if (alias != null) - { - _logger.Log(LogLevel.Error, "Site Template", LogFunction.Other, ex, "Error Importing Content For {ModuleDefinitionName}", pageTemplateModule.ModuleDefinitionName); - } + _logger.Log(LogLevel.Error, "Site Template", LogFunction.Other, "Error Importing Content For {ModuleDefinitionName}", pageTemplateModule.ModuleDefinitionName); } } } diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index 1c2a23cb..c38d1b66 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -39,6 +39,12 @@ namespace Oqtane.Models [NotMapped] public bool IsShared { get; set; } + /// + /// Specifies the scenario where the IPortable interface is being invoked + /// + [NotMapped] + public string IPortableContext { get; set; } + /// /// Reference to the used for this module. /// diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 7b8432a7..a9711582 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -7,7 +7,7 @@ - + diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 2df66252..e2c93076 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 = "10.1.0"; - 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,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2,10.0.3,10.0.4,10.1.0"; + public static readonly string Version = "10.1.1"; + 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,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2,10.0.3,10.0.4,10.1.0,10.1.1"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/README.md b/README.md index 3071a708..ee9fb398 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 -[10.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0) was released on February 25, 2026 and is a maintenance release including 72 pull requests by 6 different contributors, pushing the total number of project commits all-time over 7600. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[10.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1) was released on March 6, 2026 and is a maintenance release including 21 pull requests by 2 different contributors, pushing the total number of project commits all-time over 7700. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -111,6 +111,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... +[10.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.1) (Mar 6, 2026) +- [x] Stabilization improvements + [10.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0) (Feb 25, 2026) - [x] Site Groups, Content Synchronization, Content Localization - [x] Global Replace diff --git a/azuredeploy.json b/azuredeploy.json index 2f2ee83c..2e5f4b43 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/v10.1.0/Oqtane.Framework.10.1.0.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.1.1/Oqtane.Framework.10.1.1.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"