From 57deeb6acf77ce9889b683181b8f472792baa2e6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 6 Feb 2026 11:53:10 -0500 Subject: [PATCH] improvements for site groups --- Oqtane.Client/Modules/Admin/Site/Index.razor | 55 +++++++++---------- .../Resources/Modules/Admin/Site/Index.resx | 3 - .../Controls/ControlPanelInteractive.resx | 5 +- .../Services/SiteGroupDefinitionService.cs | 13 ++++- .../Themes/Controls/Theme/ControlPanel.razor | 7 ++- .../Theme/ControlPanelInteractive.razor | 17 ++++++ .../Controls/Theme/LanguageSelector.razor | 37 ------------- .../Controls/Theme/LanguageSwitcher.razor | 24 +++++--- .../Themes/OqtaneTheme/Themes/Default.razor | 1 - .../SiteGroupDefinitionController.cs | 49 ++++++++++++----- .../Infrastructure/Jobs/SynchronizationJob.cs | 7 ++- Oqtane.Server/Services/SiteService.cs | 11 ++++ 12 files changed, 131 insertions(+), 98 deletions(-) delete mode 100644 Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 473ce664..fce700f7 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -549,15 +549,18 @@ - @if (!string.IsNullOrEmpty(_synchronized)) - { -
- -
+
+ +
+
-
+ @if (!string.IsNullOrEmpty(_synchronized)) + { + + }
- } +
+
} }
@@ -605,10 +608,6 @@
- @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _siteGroupDefinitions.Any(item => item.PrimarySiteId == PageState.Site.SiteId && item.Synchronization)) - { - - }

@@ -1411,10 +1410,23 @@ { siteGroup.Synchronize = bool.Parse(_synchronize); siteGroup.Notify = bool.Parse(_notify); + siteGroup.SynchronizedOn = string.IsNullOrEmpty(_synchronized) ? null : siteGroup.SynchronizedOn; await SiteGroupService.UpdateSiteGroupAsync(siteGroup); } } + if (siteGroupDefinition.Synchronization) + { + // enable synchronization job if it is not enabled already + var jobs = await JobService.GetJobsAsync(); + var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server"); + if (job != null && !job.IsEnabled) + { + job.IsEnabled = true; + await JobService.UpdateJobAsync(job); + } + } + await LoadSiteGroups(); } else @@ -1450,25 +1462,8 @@ } } - private async Task SynchronizeSite() + private async Task ResetSiteGroup() { - // enable synchronization job if it is not enabled already - var jobs = await JobService.GetJobsAsync(); - var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server"); - if (job != null && !job.IsEnabled) - { - job.IsEnabled = true; - await JobService.UpdateJobAsync(job); - } - - // mark secondary sites for synchronization - foreach (var group in _siteGroupDefinitions.Where(item => item.PrimarySiteId == PageState.Site.SiteId && item.Synchronization)) - { - group.Synchronize = true; - await SiteGroupDefinitionService.UpdateSiteGroupDefinitionAsync(group); - } - - AddModuleMessage(Localizer["Message.Site.Synchronize"], MessageType.Success); - await ScrollToPageTop(); + _synchronized = ""; } } diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index b653285a..ce84a1f7 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -552,9 +552,6 @@ Site Submitted For Synchronization - - Synchronize - Notify? diff --git a/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx b/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx index 15c567fc..a4981464 100644 --- a/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx +++ b/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx @@ -200,5 +200,8 @@ Copy Existing Module - + + + Synchronize + \ No newline at end of file diff --git a/Oqtane.Client/Services/SiteGroupDefinitionService.cs b/Oqtane.Client/Services/SiteGroupDefinitionService.cs index 4f9728e6..51148846 100644 --- a/Oqtane.Client/Services/SiteGroupDefinitionService.cs +++ b/Oqtane.Client/Services/SiteGroupDefinitionService.cs @@ -19,6 +19,12 @@ namespace Oqtane.Services /// Task> GetSiteGroupDefinitionsAsync(); + /// + /// Get all s + /// + /// + Task> GetSiteGroupDefinitionsAsync(int primarySiteId); + /// /// Get one specific /// @@ -57,7 +63,12 @@ namespace Oqtane.Services public async Task> GetSiteGroupDefinitionsAsync() { - return await GetJsonAsync>($"{Apiurl}", Enumerable.Empty().ToList()); + return await GetSiteGroupDefinitionsAsync(-1); + } + + public async Task> GetSiteGroupDefinitionsAsync(int primarySiteId) + { + return await GetJsonAsync>($"{Apiurl}?siteid={primarySiteId}", Enumerable.Empty().ToList()); } public async Task GetSiteGroupDefinitionAsync(int siteGroupDefinitionId) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index 78fb7a40..762e457a 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -4,6 +4,11 @@ @inject IPageService PageService @inject ISettingService SettingService +@if (ShowLanguageSwitcher) +{ + +} + @if (ShowEditMode && (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered)))) {
@@ -48,11 +53,9 @@ [Parameter] public string BodyClass { get; set; } = "offcanvas-body overflow-auto"; - // deprecated in 10.1.0 - UI culture is set in user's profile [Parameter] public bool ShowLanguageSwitcher { get; set; } = true; - // deprecated in 10.1.0 - UI culture is set in user's profile [Parameter] public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index b702ad8d..3b3b2eda 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -11,6 +11,7 @@ @inject ILogService logger @inject ISettingService SettingService @inject IJSRuntime jsRuntime +@inject ISiteGroupDefinitionService SiteGroupDefinitionService @inject IServiceProvider ServiceProvider @inject ILogService LoggingService @inject IStringLocalizer Localizer @@ -34,6 +35,10 @@
+ @if (_siteGroupDefinitions.Any(item => item.Synchronization)) + { + + }
} @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) @@ -257,6 +262,7 @@ private List _pages = new List(); private List _modules = new List(); private List _containers = new List(); + private List _siteGroupDefinitions = new List(); private string _category = "Common"; private string _pane = ""; @@ -287,6 +293,7 @@ _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList(); + _siteGroupDefinitions = await SiteGroupDefinitionService.GetSiteGroupDefinitionsAsync(PageState.Site.SiteId); } } @@ -631,4 +638,14 @@ { _message = ""; } + + private async Task SynchronizeSite() + { + foreach (var group in _siteGroupDefinitions.Where(item => item.Synchronization)) + { + group.Synchronize = true; + await SiteGroupDefinitionService.UpdateSiteGroupDefinitionAsync(group); + } + NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true); + } } diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor deleted file mode 100644 index 4a8e1277..00000000 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor +++ /dev/null @@ -1,37 +0,0 @@ -@using System.Globalization -@using Oqtane.Models -@using System.Linq -@namespace Oqtane.Themes.Controls -@inherits ThemeControlBase -@inject ILocalizationService LocalizationService -@inject NavigationManager NavigationManager - -@if (PageState.Site.Languages.Count() > 1) -{ -
- - -
-} - -@code{ - private string MenuAlignment = string.Empty; - - [Parameter] - public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right - - [Parameter] - public string ButtonClass { get; set; } = "btn-outline-secondary"; - - protected override async Task OnParametersSetAsync() - { - MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty; - } -} diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index 03cdcbaa..2211b7be 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -6,22 +6,29 @@ @inject ILocalizationCookieService LocalizationCookieService @inject NavigationManager NavigationManager -@if (_supportedCultures?.Count() > 1) +@if (PageState.Site.Languages.Count() > 1) {
@@ -29,7 +36,7 @@ } @code{ - private IEnumerable _supportedCultures; + private bool _contentLocalization; private string MenuAlignment = string.Empty; [Parameter] @@ -41,12 +48,13 @@ { MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty; - _supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name }); + // if AliasName is populated it means the site is using content localization + _contentLocalization = PageState.Languages.Any(item => !string.IsNullOrEmpty(item.AliasName)); if (PageState.QueryString.ContainsKey("culture")) { var culture = PageState.QueryString["culture"]; - if (_supportedCultures.Any(item => item.Name == culture)) + if (PageState.Site.Languages.Any(item => item.Code == culture)) { await LocalizationCookieService.SetLocalizationCookieAsync(culture); } diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index b54570ed..f4fe08d6 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -11,7 +11,6 @@ -
diff --git a/Oqtane.Server/Controllers/SiteGroupDefinitionController.cs b/Oqtane.Server/Controllers/SiteGroupDefinitionController.cs index ed419755..da2e4ceb 100644 --- a/Oqtane.Server/Controllers/SiteGroupDefinitionController.cs +++ b/Oqtane.Server/Controllers/SiteGroupDefinitionController.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Authorization; -using Oqtane.Enums; -using Oqtane.Models; -using Oqtane.Shared; -using Oqtane.Infrastructure; -using Oqtane.Repository; -using System.Net; using System.Linq; +using System.Net; +using System.Security.Policy; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; namespace Oqtane.Controllers { @@ -27,12 +28,26 @@ namespace Oqtane.Controllers _alias = tenantManager.GetAlias(); } - // GET: api/ + // GET: api/?siteid=x [HttpGet] - [Authorize(Roles = RoleNames.Host)] - public IEnumerable Get() + [Authorize(Roles = RoleNames.Admin)] + public IEnumerable Get(string siteid) { - return _siteGroupDefinitionRepository.GetSiteGroupDefinitions().ToList(); + if (User.IsInRole(RoleNames.Host) || (int.TryParse(siteid, out int SiteId) && SiteId == _alias.SiteId)) + { + var siteGroupDefinitions = _siteGroupDefinitionRepository.GetSiteGroupDefinitions(); + if (!User.IsInRole(RoleNames.Host)) + { + siteGroupDefinitions = siteGroupDefinitions.Where(item => item.PrimarySiteId == _alias.SiteId); + } + return siteGroupDefinitions.ToList(); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Get Attempt {SiteId}", siteid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + return null; + } } // GET api//5 @@ -74,11 +89,17 @@ namespace Oqtane.Controllers // PUT api//5 [HttpPut("{id}")] - [Authorize(Roles = RoleNames.Host)] + [Authorize(Roles = RoleNames.Admin)] public SiteGroupDefinition Put(int id, [FromBody] SiteGroupDefinition siteGroupDefinition) { - if (ModelState.IsValid && siteGroupDefinition.SiteGroupDefinitionId == id && _siteGroupDefinitionRepository.GetSiteGroupDefinition(siteGroupDefinition.SiteGroupDefinitionId, false) != null) + if (ModelState.IsValid && siteGroupDefinition.SiteGroupDefinitionId == id) { + if (!User.IsInRole(RoleNames.Host) && siteGroupDefinition.Synchronize) + { + // admins can only update the synchronize field + siteGroupDefinition = _siteGroupDefinitionRepository.GetSiteGroupDefinition(siteGroupDefinition.SiteGroupDefinitionId, false); + siteGroupDefinition.Synchronize = true; + } siteGroupDefinition = _siteGroupDefinitionRepository.UpdateSiteGroupDefinition(siteGroupDefinition); _syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Definition Updated {Group}", siteGroupDefinition); diff --git a/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs b/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs index 5ef48827..78fb5026 100644 --- a/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs @@ -89,6 +89,10 @@ namespace Oqtane.Infrastructure { siteGroup.SynchronizedOn = DateTime.MinValue; } + if (siteGroup.SiteGroupDefinition.Localization) + { + siteGroup.Synchronize = false; // when using localization, do not overwrite content + } // replicate site var siteLog = ReplicateSite(provider, tenantManager, settingRepository, siteGroup, primarySite, secondarySite); @@ -136,6 +140,7 @@ namespace Oqtane.Infrastructure if (primarySite.ModifiedOn > siteGroup.SynchronizedOn) { secondarySite.TimeZoneId = primarySite.TimeZoneId; + secondarySite.CultureCode = primarySite.CultureCode; if (secondarySite.LogoFileId != primarySite.LogoFileId) { secondarySite.LogoFileId = ResolveFileId(provider, primarySite.LogoFileId, secondarySite.SiteId); @@ -181,7 +186,7 @@ namespace Oqtane.Infrastructure { siteRepository.UpdateSite(secondarySite); } - log += Log(siteGroup, $"Secondary Site Updated: {secondarySite.Name}"); + log += Log(siteGroup, $"Site Updated: {secondarySite.Name}"); } // site settings diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index ea232358..f8a50351 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -318,6 +318,7 @@ namespace Oqtane.Services var siteGroups = _siteGroups.GetSiteGroups(); if (siteGroups.Any(item => item.SiteId == siteId && item.SiteGroupDefinition.Localization)) { + // site is part of a localized site group - get all languages from the site group var sites = _sites.GetSites().ToList(); var aliases = _aliases.GetAliases().ToList(); @@ -340,6 +341,16 @@ namespace Oqtane.Services } } } + else + { + // use site languages + languages = _languages.GetLanguages(siteId).ToList(); + var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture); + if (!languages.Exists(item => item.Code == defaultCulture.Name)) + { + languages.Add(new Language { Code = defaultCulture.Name, Name = "", Version = Constants.Version, IsDefault = !languages.Any(l => l.IsDefault) }); + } + } return languages; }