introducing Site Groups

This commit is contained in:
sbwalker
2026-01-27 16:51:30 -05:00
parent 6006e6f63c
commit 3be2b9c720
51 changed files with 2558 additions and 352 deletions

View File

@@ -157,15 +157,8 @@
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
if (page == null && route.PagePath == "") // naked path refers to site home page
{
if (site.HomePageId != null)
{
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
}
if (page == null)
{
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
if (page == null)
{

View File

@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Controllers
@@ -13,10 +15,16 @@ namespace Oqtane.Controllers
public class LocalizationController : Controller
{
private readonly ILocalizationManager _localizationManager;
private readonly ISiteRepository _siteRepository;
private readonly ISiteGroupRepository _siteGroupRepository;
private readonly IAliasRepository _aliasRepository;
public LocalizationController(ILocalizationManager localizationManager)
public LocalizationController(ILocalizationManager localizationManager, ISiteRepository siteRepository, ISiteGroupRepository siteGroupRepository, IAliasRepository aliasRepository)
{
_localizationManager = localizationManager;
_siteRepository = siteRepository;
_siteGroupRepository = siteGroupRepository;
_aliasRepository = aliasRepository;
}
// GET: api/localization
@@ -32,13 +40,20 @@ namespace Oqtane.Controllers
{
culturecodes = _localizationManager.GetSupportedCultures();
}
var cultures = culturecodes.Select(c => new Culture
{
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
.Equals(CultureInfo.GetCultureInfo(c).Name, StringComparison.OrdinalIgnoreCase)
});
}).ToList();
if (cultures.Count == 0)
{
cultures.Add(new Culture { Name = "en", DisplayName = "English", IsDefault = true });
}
return cultures.OrderBy(item => item.DisplayName);
}
}

View File

@@ -0,0 +1,123 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
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
{
[Route(ControllerRoutes.ApiRoute)]
public class SiteGroupController : Controller
{
private readonly ISiteGroupRepository _siteGroupRepository;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public SiteGroupController(ISiteGroupRepository siteGroupRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_siteGroupRepository = siteGroupRepository;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x&groupid=y
[HttpGet]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<SiteGroup> Get(string siteid, string groupid)
{
if (int.TryParse(siteid, out int SiteId) && int.TryParse(groupid, out int SiteGroupDefinitionId))
{
return _siteGroupRepository.GetSiteGroups(SiteId, SiteGroupDefinitionId).ToList();
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Get Attempt for SiteId {SiteId} And SiteGroupDefinitionId {SiteGroupDefinitionId}", siteid, groupid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroup Get(int id)
{
var siteGroup = _siteGroupRepository.GetSiteGroup(id);
if (siteGroup != null)
{
return siteGroup;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public SiteGroup Post([FromBody] SiteGroup siteGroup)
{
if (ModelState.IsValid)
{
siteGroup = _siteGroupRepository.AddSiteGroup(siteGroup);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupDefinitionId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Added {SiteGroup}", siteGroup);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Post Attempt {SiteGroup}", siteGroup);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroup = null;
}
return siteGroup;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroup Put(int id, [FromBody] SiteGroup siteGroup)
{
if (ModelState.IsValid && siteGroup.SiteGroupDefinitionId == id && _siteGroupRepository.GetSiteGroup(siteGroup.SiteGroupDefinitionId, false) != null)
{
siteGroup = _siteGroupRepository.UpdateSiteGroup(siteGroup);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupDefinitionId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Updated {SiteGroup}", siteGroup);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Put Attempt {SiteGroup}", siteGroup);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroup = null;
}
return siteGroup;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
var siteGroup = _siteGroupRepository.GetSiteGroup(id);
if (siteGroup != null)
{
_siteGroupRepository.DeleteSiteGroup(id);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupDefinitionId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Deleted {SiteGroupDefinitionId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Delete Attempt {SiteGroupDefinitionId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@@ -0,0 +1,114 @@
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;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class SiteGroupDefinitionController : Controller
{
private readonly ISiteGroupDefinitionRepository _siteGroupDefinitionRepository;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public SiteGroupDefinitionController(ISiteGroupDefinitionRepository siteGroupDefinitionRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_siteGroupDefinitionRepository = siteGroupDefinitionRepository;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>
[HttpGet]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<SiteGroupDefinition> Get()
{
return _siteGroupDefinitionRepository.GetSiteGroupDefinitions().ToList();
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupDefinition Get(int id)
{
var group = _siteGroupDefinitionRepository.GetSiteGroupDefinition(id);
if (group != null)
{
return group;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupDefinition Post([FromBody] SiteGroupDefinition siteGroupDefinition)
{
if (ModelState.IsValid)
{
siteGroupDefinition = _siteGroupDefinitionRepository.AddSiteGroupDefinition(siteGroupDefinition);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Definition Added {Group}", siteGroupDefinition);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Post Attempt {Group}", siteGroupDefinition);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroupDefinition = null;
}
return siteGroupDefinition;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupDefinition Put(int id, [FromBody] SiteGroupDefinition siteGroupDefinition)
{
if (ModelState.IsValid && siteGroupDefinition.SiteGroupDefinitionId == id && _siteGroupDefinitionRepository.GetSiteGroupDefinition(siteGroupDefinition.SiteGroupDefinitionId, false) != null)
{
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);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Put Attempt {Group}", siteGroupDefinition);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroupDefinition = null;
}
return siteGroupDefinition;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
var siteGroupDefinition = _siteGroupDefinitionRepository.GetSiteGroupDefinition(id);
if (siteGroupDefinition != null)
{
_siteGroupDefinitionRepository.DeleteSiteGroupDefinition(id);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Definition Deleted {siteGroupDefinitionId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Delete Attempt {siteGroupDefinitionId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@@ -233,6 +233,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ICookieConsentService, ServerCookieConsentService>();
services.AddScoped<ITimeZoneService, TimeZoneService>();
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
services.AddScoped<ISiteGroupDefinitionService, SiteGroupDefinitionService>();
services.AddScoped<ISiteGroupService, SiteGroupService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
@@ -282,6 +284,8 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
services.AddTransient<ISearchContentRepository, SearchContentRepository>();
services.AddTransient<IMigrationHistoryRepository, MigrationHistoryRepository>();
services.AddTransient<ISiteGroupDefinitionRepository, SiteGroupDefinitionRepository>();
services.AddTransient<ISiteGroupRepository, SiteGroupRepository>();
// managers
services.AddTransient<IDBContextDependencies, DBContextDependencies>();

View File

@@ -594,7 +594,8 @@ namespace Oqtane.Infrastructure
Prerender = (rendermode == RenderModes.Interactive),
Hybrid = false,
EnhancedNavigation = true,
TenantId = tenant.TenantId
CultureCode = "en",
TenantId = tenant.TenantId // required for site creation
};
site = sites.AddSite(site);

View File

@@ -121,11 +121,23 @@ namespace Oqtane.Infrastructure
{
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
{
// it is possible to use basic without any authentication (not recommended)
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
{
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
try
{
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
}
catch (Exception ex)
{
log += "SMTP Not Configured Properly In Site Settings - Basic Authentication Failed Using Username And Password - " + ex.Message + "<br />";
valid = false;
}
}
else
{
// it is possible to use basic without any authentication (not recommended)
}
}
else

View File

@@ -0,0 +1,778 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class SynchronizationJob : HostedServiceBase
{
// JobType = "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server"
// synchronization only supports sites in the same tenant (database)
// module title is used as a key to identify module instances on a page (ie. using "-" as a module title is problematic ie. content as configuration)
// relies on Module.ModifiedOn to be set if the module content changes (for efficiency)
// modules must implement ISynchronizable interface (new interface as IPortable was generally only implemented in an additive manner)
// define settings that should not be synchronized (should be extensible in the future)
List<Setting> excludedSettings = new List<Setting>() {
new Setting { EntityName = EntityNames.Site, SettingName = "Search_LastIndexedOn" }
};
public SynchronizationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Synchronization Job";
Frequency = "m"; // minute
Interval = 1;
IsEnabled = false;
}
// job is executed for each tenant in installation
public override string ExecuteJob(IServiceProvider provider)
{
string log = "";
var siteGroupDefinitionRepository = provider.GetRequiredService<ISiteGroupDefinitionRepository>();
var siteGroupRepository = provider.GetRequiredService<ISiteGroupRepository>();
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var aliasRepository = provider.GetRequiredService<IAliasRepository>();
var tenantManager = provider.GetRequiredService<ITenantManager>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
List<SiteGroup> siteGroups = null;
List<Site> sites = null;
List<Alias> aliases = null;
// get groups
var groups = siteGroupDefinitionRepository.GetSiteGroupDefinitions();
// iterate through groups which need to be synchronized
foreach (var group in groups.Where(item => item.Synchronize))
{
// get data
if (siteGroups == null)
{
siteGroups = siteGroupRepository.GetSiteGroups().ToList();
sites = siteRepository.GetSites().ToList();
aliases = aliasRepository.GetAliases().ToList();
}
var aliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == group.PrimarySiteId && item.IsDefault).Name;
log += $"Processing Primary Site: {sites.First(item => item.SiteId == group.PrimarySiteId).Name} - {aliasName}<br />";
// get primary site
var primarySite = sites.FirstOrDefault(item => item.SiteId == group.PrimarySiteId);
if (primarySite != null)
{
// update flag to prevent job from processing group again
group.Synchronize = false;
siteGroupDefinitionRepository.UpdateSiteGroupDefinition(group);
// iterate through sites in group
foreach (var siteGroup in siteGroups.Where(item => item.SiteGroupDefinitionId == group.SiteGroupDefinitionId && item.SiteId != group.PrimarySiteId))
{
// get secondary site
var secondarySite = sites.FirstOrDefault(item => item.SiteId == siteGroup.SiteId);
if (secondarySite != null)
{
// get default alias for site
siteGroup.AliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == siteGroup.SiteId && item.IsDefault).Name;
// initialize SynchronizedOn
if (siteGroup.SynchronizedOn == null)
{
siteGroup.SynchronizedOn = DateTime.MinValue;
}
// replicate site
var siteLog = ReplicateSite(provider, tenantManager, settingRepository, siteGroup, primarySite, secondarySite);
// set synchronized on date/time
siteGroup.SynchronizedOn = DateTime.UtcNow;
siteGroupRepository.UpdateSiteGroup(siteGroup);
log += $"Processed Target Site: {secondarySite.Name} - {siteGroup.AliasName}<br />" + siteLog;
}
else
{
log += $"Site Group Has A SiteId {siteGroup.SiteId} Which Does Not Exist<br />";
}
}
}
else
{
log += $"Site Group Has A PrimarySiteId {group.PrimarySiteId} Which Does Not Exist<br />";
}
}
if (string.IsNullOrEmpty(log))
{
log = "No Site Groups Require Replication<br />";
}
return log;
}
private string ReplicateSite(IServiceProvider provider, ITenantManager tenantManager, ISettingRepository settingRepository, SiteGroup siteGroup, Site primarySite, Site secondarySite)
{
var log = "";
// replicate roles/users
log += ReplicateRoles(provider, settingRepository, siteGroup, primarySite.SiteId, secondarySite.SiteId);
// replicate folders/files
log += ReplicateFolders(provider, settingRepository, siteGroup, primarySite.SiteId, secondarySite.SiteId);
// replicate pages/modules
log += ReplicatePages(provider, settingRepository, tenantManager, siteGroup, primarySite.SiteId, secondarySite.SiteId);
// replicate site
if (primarySite.ModifiedOn > siteGroup.SynchronizedOn)
{
secondarySite.TimeZoneId = primarySite.TimeZoneId;
if (secondarySite.LogoFileId != primarySite.LogoFileId)
{
secondarySite.LogoFileId = ResolveFileId(provider, primarySite.LogoFileId, secondarySite.SiteId);
}
if (secondarySite.FaviconFileId != primarySite.FaviconFileId)
{
secondarySite.FaviconFileId = ResolveFileId(provider, primarySite.FaviconFileId, secondarySite.SiteId); ;
}
secondarySite.DefaultThemeType = primarySite.DefaultThemeType;
secondarySite.DefaultContainerType = primarySite.DefaultContainerType;
secondarySite.AdminContainerType = primarySite.AdminContainerType;
secondarySite.PwaIsEnabled = primarySite.PwaIsEnabled;
if (secondarySite.PwaAppIconFileId != primarySite.PwaAppIconFileId)
{
secondarySite.PwaAppIconFileId = ResolveFileId(provider, primarySite.PwaAppIconFileId, secondarySite.SiteId); ;
}
if (secondarySite.PwaSplashIconFileId != primarySite.PwaSplashIconFileId)
{
secondarySite.PwaSplashIconFileId = ResolveFileId(provider, primarySite.PwaSplashIconFileId, secondarySite.SiteId); ;
}
secondarySite.AllowRegistration = primarySite.AllowRegistration;
secondarySite.VisitorTracking = primarySite.VisitorTracking;
secondarySite.CaptureBrokenUrls = primarySite.CaptureBrokenUrls;
secondarySite.SiteGuid = primarySite.SiteGuid;
secondarySite.RenderMode = primarySite.RenderMode;
secondarySite.Runtime = primarySite.Runtime;
secondarySite.Prerender = primarySite.Prerender;
secondarySite.Hybrid = primarySite.Hybrid;
secondarySite.EnhancedNavigation = primarySite.EnhancedNavigation;
secondarySite.Version = primarySite.Version;
secondarySite.HeadContent = primarySite.HeadContent;
secondarySite.BodyContent = primarySite.BodyContent;
secondarySite.CreatedBy = primarySite.CreatedBy;
secondarySite.CreatedOn = primarySite.CreatedOn;
secondarySite.ModifiedBy = primarySite.ModifiedBy;
secondarySite.ModifiedOn = primarySite.ModifiedOn;
secondarySite.IsDeleted = primarySite.IsDeleted;
secondarySite.DeletedBy = primarySite.DeletedBy;
secondarySite.DeletedOn = primarySite.DeletedOn;
var siteRepository = provider.GetRequiredService<ISiteRepository>();
if (siteGroup.Synchronize)
{
siteRepository.UpdateSite(secondarySite);
}
log += Log(siteGroup, $"Secondary Site Updated: {secondarySite.Name}");
}
// site settings
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Site, primarySite.SiteId, secondarySite.SiteId);
if (siteGroup.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log))
{
// clear cache for secondary site if any content was replicated
var syncManager = provider.GetRequiredService<ISyncManager>();
var alias = new Alias { TenantId = tenantManager.GetTenant().TenantId, SiteId = secondarySite.SiteId };
syncManager.AddSyncEvent(alias, EntityNames.Site, secondarySite.SiteId, SyncEventActions.Refresh);
}
if (!string.IsNullOrEmpty(log) && !string.IsNullOrEmpty(siteGroup.NotifyRoleName))
{
// send change log to users in role
SendNotifications(provider, secondarySite.SiteId, secondarySite.Name, siteGroup.NotifyRoleName, log);
}
return log;
}
private int? ResolveFileId(IServiceProvider provider, int? fileId, int siteId)
{
if (fileId != null)
{
var fileRepository = provider.GetRequiredService<IFileRepository>();
var file = fileRepository.GetFile(fileId.Value);
fileId = fileRepository.GetFile(siteId, file.Folder.Path, file.Name).FileId;
}
return fileId;
}
private string ReplicateRoles(IServiceProvider provider, ISettingRepository settingRepository, SiteGroup siteGroup, int primarySiteId, int secondarySiteId)
{
// get roles
var roleRepository = provider.GetRequiredService<IRoleRepository>();
var primaryRoles = roleRepository.GetRoles(primarySiteId);
var secondaryRoles = roleRepository.GetRoles(secondarySiteId).ToList();
var log = "";
foreach (var primaryRole in primaryRoles)
{
var role = secondaryRoles.FirstOrDefault(item => item.Name == primaryRole.Name);
var secondaryRole = role;
if (secondaryRole == null)
{
secondaryRole = new Role();
secondaryRole.SiteId = secondarySiteId;
}
if (role == null || primaryRole.ModifiedOn > siteGroup.SynchronizedOn)
{
// set all properties
secondaryRole.Name = primaryRole.Name;
secondaryRole.Description = primaryRole.Description;
secondaryRole.IsAutoAssigned = primaryRole.IsAutoAssigned;
secondaryRole.IsSystem = primaryRole.IsSystem;
if (role == null)
{
if (siteGroup.Synchronize)
{
roleRepository.AddRole(secondaryRole);
}
log += Log(siteGroup, $"Role Added: {secondaryRole.Name}");
}
else
{
if (siteGroup.Synchronize)
{
roleRepository.UpdateRole(secondaryRole);
}
log += Log(siteGroup, $"Role Updated: {secondaryRole.Name}");
secondaryRoles.Remove(role);
}
}
}
// remove roles in the secondary site which do not exist in the primary site
foreach (var secondaryRole in secondaryRoles.Where(item => !primaryRoles.Select(item => item.Name).Contains(item.Name)))
{
if (siteGroup.Synchronize)
{
roleRepository.DeleteRole(secondaryRole.RoleId);
}
log += Log(siteGroup, $"Role Deleted: {secondaryRole.Name}");
}
// settings
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Role, primarySiteId, secondarySiteId);
return log;
}
private string ReplicateFolders(IServiceProvider provider, ISettingRepository settingRepository, SiteGroup siteGroup, int primarySiteId, int secondarySiteId)
{
var folderRepository = provider.GetRequiredService<IFolderRepository>();
var fileRepository = provider.GetRequiredService<IFileRepository>();
var log = "";
// get folders (ignore personalized)
var primaryFolders = folderRepository.GetFolders(primarySiteId).Where(item => !item.Path.StartsWith("Users/"));
var secondaryFolders = folderRepository.GetFolders(secondarySiteId).Where(item => !item.Path.StartsWith("Users/")).ToList();
// iterate through folders
foreach (var primaryFolder in primaryFolders)
{
var folder = secondaryFolders.FirstOrDefault(item => item.Path == primaryFolder.Path);
var secondaryFolder = folder;
if (secondaryFolder == null)
{
secondaryFolder = new Folder();
secondaryFolder.SiteId = secondarySiteId;
}
if (folder == null || primaryFolder.ModifiedOn > siteGroup.SynchronizedOn)
{
// set all properties
secondaryFolder.ParentId = null;
if (primaryFolder.ParentId != null)
{
var parentFolder = folderRepository.GetFolder(secondarySiteId, primaryFolders.First(item => item.FolderId == primaryFolder.ParentId).Path);
if (parentFolder != null)
{
secondaryFolder.ParentId = parentFolder.FolderId;
}
}
secondaryFolder.Type = primaryFolder.Type;
secondaryFolder.Name = primaryFolder.Name;
secondaryFolder.Order = primaryFolder.Order;
secondaryFolder.ImageSizes = primaryFolder.ImageSizes;
secondaryFolder.Capacity = primaryFolder.Capacity;
secondaryFolder.ImageSizes = primaryFolder.ImageSizes;
secondaryFolder.IsSystem = primaryFolder.IsSystem;
secondaryFolder.PermissionList = ReplicatePermissions(primaryFolder.PermissionList, secondarySiteId);
if (folder == null)
{
if (siteGroup.Synchronize)
{
folderRepository.AddFolder(secondaryFolder);
}
log += Log(siteGroup, $"Folder Added: {secondaryFolder.Path}");
}
else
{
if (siteGroup.Synchronize)
{
folderRepository.UpdateFolder(secondaryFolder);
}
log += Log(siteGroup, $"Folder Updated: {secondaryFolder.Path}");
secondaryFolders.Remove(folder);
}
}
// folder settings
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Folder, primaryFolder.FolderId, secondaryFolder.FolderId);
// get files for folder
var primaryFiles = fileRepository.GetFiles(primaryFolder.FolderId);
var secondaryFiles = fileRepository.GetFiles(secondaryFolder.FolderId).ToList();
foreach (var primaryFile in primaryFiles)
{
var file = secondaryFiles.FirstOrDefault(item => item.Name == primaryFile.Name);
var secondaryFile = file;
if (secondaryFile == null)
{
secondaryFile = new Models.File();
secondaryFile.FolderId = secondaryFolder.FolderId;
secondaryFile.Name = primaryFile.Name;
}
if (file == null || primaryFile.ModifiedOn > siteGroup.SynchronizedOn)
{
// set all properties
secondaryFile.Extension = primaryFile.Extension;
secondaryFile.Size = primaryFile.Size;
secondaryFile.ImageHeight = primaryFile.ImageHeight;
secondaryFile.ImageWidth = primaryFile.ImageWidth;
secondaryFile.Description = primaryFile.Description;
if (file == null)
{
if (siteGroup.Synchronize)
{
fileRepository.AddFile(secondaryFile);
ReplicateFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
}
log += Log(siteGroup, $"File Added: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
}
else
{
if (siteGroup.Synchronize)
{
fileRepository.UpdateFile(secondaryFile);
ReplicateFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
}
log += Log(siteGroup, $"File Updated: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
secondaryFiles.Remove(file);
}
}
}
// remove files in the secondary site which do not exist in the primary site
foreach (var secondaryFile in secondaryFiles.Where(item => !primaryFiles.Select(item => item.Name).Contains(item.Name)))
{
if (siteGroup.Synchronize)
{
fileRepository.DeleteFile(secondaryFile.FileId);
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
System.IO.File.Delete(secondaryPath);
}
log += Log(siteGroup, $"File Deleted: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
}
}
// remove folders in the secondary site which do not exist in the primary site
foreach (var secondaryFolder in secondaryFolders.Where(item => !primaryFolders.Select(item => item.Path).Contains(item.Path)))
{
if (siteGroup.Synchronize)
{
folderRepository.DeleteFolder(secondaryFolder.FolderId);
}
log += Log(siteGroup, $"Folder Deleted: {secondaryFolder.Path}");
}
return log;
}
private void ReplicateFile(IFolderRepository folderRepository, Folder primaryFolder, Models.File primaryFile, Folder secondaryFolder, Models.File secondaryFile)
{
var primaryPath = Path.Combine(folderRepository.GetFolderPath(primaryFolder), primaryFile.Name);
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
System.IO.File.Copy(primaryPath, secondaryPath, true);
}
private string ReplicatePages(IServiceProvider provider, ISettingRepository settingRepository, ITenantManager tenantManager, SiteGroup siteGroup, int primarySiteId, int secondarySiteId)
{
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
var moduleRepository = provider.GetRequiredService<IModuleRepository>();
var log = "";
List<PageModule> primaryPageModules = null;
List<PageModule> secondaryPageModules = null;
int tenantId = tenantManager.GetTenant().TenantId;
// get pages (ignore personalized)
var primaryPages = pageRepository.GetPages(primarySiteId).Where(item => item.UserId == null);
var secondaryPages = pageRepository.GetPages(secondarySiteId).Where(item => item.UserId == null).ToList();
// iterate through primary pages
foreach (var primaryPage in primaryPages)
{
var page = secondaryPages.FirstOrDefault(item => item.Path == primaryPage.Path);
var secondaryPage = page;
if (secondaryPage == null)
{
secondaryPage = new Page();
secondaryPage.SiteId = secondarySiteId;
}
if (page == null || primaryPage.ModifiedOn > siteGroup.SynchronizedOn)
{
// set all properties
secondaryPage.Path = primaryPage.Path;
secondaryPage.Name = primaryPage.Name;
secondaryPage.ParentId = null;
if (primaryPage.ParentId != null)
{
var parentPage = pageRepository.GetPage(primaryPages.First(item => item.PageId == primaryPage.ParentId).Path, secondarySiteId);
if (parentPage != null)
{
secondaryPage.ParentId = parentPage.PageId;
}
}
secondaryPage.Title = primaryPage.Title;
secondaryPage.Order = primaryPage.Order;
secondaryPage.Url = primaryPage.Url;
secondaryPage.ThemeType = primaryPage.ThemeType;
secondaryPage.DefaultContainerType = primaryPage.DefaultContainerType;
secondaryPage.HeadContent = primaryPage.HeadContent;
secondaryPage.BodyContent = primaryPage.BodyContent;
secondaryPage.Icon = primaryPage.Icon;
secondaryPage.IsNavigation = primaryPage.IsNavigation;
secondaryPage.IsClickable = primaryPage.IsClickable;
secondaryPage.UserId = null;
secondaryPage.IsPersonalizable = primaryPage.IsPersonalizable;
secondaryPage.EffectiveDate = primaryPage.EffectiveDate;
secondaryPage.ExpiryDate = primaryPage.ExpiryDate;
secondaryPage.CreatedBy = primaryPage.CreatedBy;
secondaryPage.CreatedOn = primaryPage.CreatedOn;
secondaryPage.ModifiedBy = primaryPage.ModifiedBy;
secondaryPage.ModifiedOn = primaryPage.ModifiedOn;
secondaryPage.DeletedBy = primaryPage.DeletedBy;
secondaryPage.DeletedOn = primaryPage.DeletedOn;
secondaryPage.IsDeleted = primaryPage.IsDeleted;
secondaryPage.PermissionList = ReplicatePermissions(primaryPage.PermissionList, secondarySiteId);
if (page == null)
{
if (siteGroup.Synchronize)
{
secondaryPage = pageRepository.AddPage(secondaryPage);
}
log += Log(siteGroup, $"Page Added: {siteGroup.AliasName}{secondaryPage.Path}");
}
else
{
if (siteGroup.Synchronize)
{
secondaryPage = pageRepository.UpdatePage(secondaryPage);
}
log += Log(siteGroup, $"Page Updated: {siteGroup.AliasName}{secondaryPage.Path}");
secondaryPages.Remove(page);
}
}
// page settings
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Page, primaryPage.PageId, secondaryPage.PageId);
// modules
if (primaryPageModules == null)
{
tenantManager.SetAlias(tenantId, primarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
primaryPageModules = pageModuleRepository.GetPageModules(primarySiteId).ToList();
}
if (secondaryPageModules == null)
{
tenantManager.SetAlias(tenantId, secondarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
secondaryPageModules = pageModuleRepository.GetPageModules(secondarySiteId).ToList();
}
foreach (var primaryPageModule in primaryPageModules.Where(item => item.PageId == primaryPage.PageId))
{
var pageModule = secondaryPageModules.FirstOrDefault(item => item.PageId == secondaryPage.PageId && item.Module.ModuleDefinitionName == primaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == primaryPageModule.Title.ToLower());
var secondaryPageModule = pageModule;
if (secondaryPageModule == null)
{
secondaryPageModule = new PageModule();
secondaryPageModule.PageId = secondaryPage.PageId;
secondaryPageModule.Module = new Module();
secondaryPageModule.Module.SiteId = secondarySiteId;
secondaryPageModule.Module.ModuleDefinitionName = primaryPageModule.Module.ModuleDefinitionName;
}
if (pageModule == null || primaryPageModule.ModifiedOn > siteGroup.SynchronizedOn || primaryPageModule.Module.ModifiedOn > siteGroup.SynchronizedOn)
{
// set all properties
secondaryPageModule.Title = primaryPageModule.Title;
secondaryPageModule.Pane = primaryPageModule.Pane;
secondaryPageModule.Order = primaryPageModule.Order;
secondaryPageModule.ContainerType = primaryPageModule.ContainerType;
secondaryPageModule.Header = primaryPageModule.Header;
secondaryPageModule.Footer = primaryPageModule.Footer;
secondaryPageModule.IsDeleted = primaryPageModule.IsDeleted;
secondaryPageModule.Module.PermissionList = ReplicatePermissions(primaryPageModule.Module.PermissionList, secondarySiteId);
secondaryPageModule.Module.AllPages = false;
secondaryPageModule.Module.IsDeleted = false;
var updateContent = false;
if (pageModule == null)
{
// check if module exists
var module = secondaryPageModules.FirstOrDefault(item => item.Module.ModuleDefinitionName == primaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == primaryPageModule.Title.ToLower())?.Module;
if (module == null)
{
// add new module
if (siteGroup.Synchronize)
{
module = moduleRepository.AddModule(secondaryPageModule.Module);
updateContent = true;
}
log += Log(siteGroup, $"Module Added: {module.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
}
if (module != null)
{
secondaryPageModule.ModuleId = module.ModuleId;
secondaryPageModule.Module = null; // remove tracking
if (siteGroup.Synchronize)
{
secondaryPageModule = pageModuleRepository.AddPageModule(secondaryPageModule);
}
log += Log(siteGroup, $"Page Module Added: {module.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
secondaryPageModule.Module = module;
}
}
else
{
// update existing module
if (primaryPageModule.Module.ModifiedOn > siteGroup.SynchronizedOn)
{
if (siteGroup.Synchronize)
{
moduleRepository.UpdateModule(secondaryPageModule.Module);
updateContent = true;
}
log += Log(siteGroup, $"Module Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
}
if (primaryPageModule.ModifiedOn > siteGroup.SynchronizedOn)
{
if (siteGroup.Synchronize)
{
secondaryPageModule = pageModuleRepository.UpdatePageModule(secondaryPageModule);
}
log += Log(siteGroup, $"Page Module Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
secondaryPageModules.Remove(pageModule);
}
}
// module content
if (updateContent && primaryPageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(primaryPageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(ISynchronizable)) != null)
{
try
{
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var primaryModuleContent = ((ISynchronizable)moduleObject).ExtractModule(primaryPageModule.Module);
var secondaryModuleContent = ((ISynchronizable)moduleObject).ExtractModule(secondaryPageModule.Module);
if (primaryModuleContent != secondaryModuleContent)
{
if (siteGroup.Synchronize)
{
((ISynchronizable)moduleObject).LoadModule(secondaryPageModule.Module, primaryModuleContent, primaryPageModule.Module.ModuleDefinition.Version);
}
log += Log(siteGroup, $"Module Content Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
}
}
catch
{
// error exporting/importing
}
}
}
}
// module settings
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Module, primaryPageModule.ModuleId, secondaryPageModule.ModuleId);
}
}
// remove modules in the secondary site which do not exist in the primary site
foreach (var secondaryPageModule in secondaryPageModules)
{
var primaryPageId = -1;
var secondaryPage = secondaryPages.FirstOrDefault(item => item.PageId == secondaryPageModule.PageId);
if (secondaryPage != null)
{
var primaryPage = primaryPages.FirstOrDefault(item => item.Path == secondaryPage.Path);
if (primaryPage != null)
{
primaryPageId = primaryPage.PageId;
}
}
if (!primaryPageModules.Any(item => item.PageId == primaryPageId && item.Module.ModuleDefinitionName == secondaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == secondaryPageModule.Title.ToLower()))
{
if (siteGroup.Synchronize)
{
pageModuleRepository.DeletePageModule(secondaryPageModule.PageModuleId);
}
log += Log(siteGroup, $"Page Module Deleted: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPageModule.Page.Path}");
}
}
// remove pages in the secondary site which do not exist in the primary site
foreach (var secondaryPage in secondaryPages.Where(item => !primaryPages.Select(item => item.Path).Contains(item.Path)))
{
if (siteGroup.Synchronize)
{
pageRepository.DeletePage(secondaryPage.PageId);
}
log += Log(siteGroup, $"Page Deleted: {siteGroup.AliasName}{secondaryPage.Path}");
}
if (siteGroup.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log))
{
// clear cache for secondary site if any content was replicated
var syncManager = provider.GetRequiredService<ISyncManager>();
var alias = new Alias { TenantId = tenantManager.GetTenant().TenantId, SiteId = secondarySiteId };
syncManager.AddSyncEvent(alias, EntityNames.Site, secondarySiteId, SyncEventActions.Refresh);
}
return log;
}
private List<Permission> ReplicatePermissions(List<Permission> permissionList, int siteId)
{
return permissionList.Select(item => new Permission
{
SiteId = siteId,
PermissionName = item.PermissionName,
RoleName = item.RoleName,
UserId = item.UserId,
IsAuthorized = item.IsAuthorized,
CreatedBy = item.CreatedBy,
CreatedOn = item.CreatedOn,
ModifiedBy = item.ModifiedBy,
ModifiedOn = item.ModifiedOn
}).ToList();
}
private string ReplicateSettings(ISettingRepository settingRepository, SiteGroup siteGroup, string entityName, int primaryEntityId, int secondaryEntityId)
{
var log = "";
var updated = false;
var secondarySettings = settingRepository.GetSettings(entityName, secondaryEntityId).ToList();
foreach (var primarySetting in settingRepository.GetSettings(entityName, primaryEntityId))
{
var secondarySetting = secondarySettings.FirstOrDefault(item => item.SettingName == primarySetting.SettingName);
if (secondarySetting == null)
{
secondarySetting = new Setting();
secondarySetting.EntityName = primarySetting.EntityName;
secondarySetting.EntityId = secondaryEntityId;
secondarySetting.SettingName = primarySetting.SettingName;
secondarySetting.SettingValue = primarySetting.SettingValue;
secondarySetting.IsPrivate = primarySetting.IsPrivate;
if (siteGroup.Synchronize && !excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.AddSetting(secondarySetting);
updated = true;
}
}
else
{
if (secondarySetting.SettingValue != primarySetting.SettingValue || secondarySetting.IsPrivate != primarySetting.IsPrivate)
{
secondarySetting.SettingValue = primarySetting.SettingValue;
secondarySetting.IsPrivate = primarySetting.IsPrivate;
if (siteGroup.Synchronize && !excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.UpdateSetting(secondarySetting);
updated = true;
}
}
secondarySettings.Remove(secondarySetting);
}
}
// any remaining secondary settings need to be deleted
foreach (var secondarySetting in secondarySettings)
{
if (siteGroup.Synchronize && !excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.DeleteSetting(secondarySetting.EntityName, secondarySetting.SettingId);
updated = true;
}
}
if (updated)
{
log += Log(siteGroup, $"{entityName} Settings Updated");
}
return log;
}
private void SendNotifications(IServiceProvider provider, int siteId, string siteName, string roleName, string log)
{
var userRoleRepository = provider.GetRequiredService<IUserRoleRepository>();
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
foreach (var userRole in userRoleRepository.GetUserRoles(roleName, siteId))
{
var notification = new Notification(siteId, userRole.User, $"{siteName} Change Log", log);
notificationRepository.AddNotification(notification);
}
}
private string Log(SiteGroup siteGroup, string content)
{
// not necessary to log initial replication
if (siteGroup.SynchronizedOn != DateTime.MinValue)
{
return content + "<br />";
}
else
{
return "";
}
}
}
}

View File

@@ -41,7 +41,9 @@ namespace Oqtane.Infrastructure
public string[] GetSupportedCultures()
{
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
return CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(item => item.Name.Length == 2) // major languages only (this could be configurable)
.Select(item => item.Name).OrderBy(c => c).ToArray();
}
public string[] GetInstalledCultures()

View File

@@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class SiteGroupDefinitionEntityBuilder : AuditableBaseEntityBuilder<SiteGroupDefinitionEntityBuilder>
{
private const string _entityTableName = "SiteGroupDefinition";
private readonly PrimaryKey<SiteGroupDefinitionEntityBuilder> _primaryKey = new("PK_SiteGroupDefinition", x => x.SiteGroupDefinitionId);
public SiteGroupDefinitionEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
}
protected override SiteGroupDefinitionEntityBuilder BuildTable(ColumnsBuilder table)
{
SiteGroupDefinitionId = AddAutoIncrementColumn(table, "SiteGroupDefinitionId");
Name = AddStringColumn(table, "Name", 200);
PrimarySiteId = AddIntegerColumn(table, "PrimarySiteId");
Synchronization = AddBooleanColumn(table, "Synchronization");
Synchronize = AddBooleanColumn(table, "Synchronize");
Localization = AddBooleanColumn(table, "Localization");
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> SiteGroupDefinitionId { get; set; }
public OperationBuilder<AddColumnOperation> Name { get; set; }
public OperationBuilder<AddColumnOperation> PrimarySiteId { get; set; }
public OperationBuilder<AddColumnOperation> Synchronization { get; set; }
public OperationBuilder<AddColumnOperation> Synchronize { get; set; }
public OperationBuilder<AddColumnOperation> Localization { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class SiteGroupEntityBuilder : AuditableBaseEntityBuilder<SiteGroupEntityBuilder>
{
private const string _entityTableName = "SiteGroup";
private readonly PrimaryKey<SiteGroupEntityBuilder> _primaryKey = new("PK_SiteGroup", x => x.SiteGroupId);
private readonly ForeignKey<SiteGroupEntityBuilder> _groupForeignKey = new("FK_SiteGroup_SiteGroupDefinition", x => x.SiteGroupDefinitionId, "SiteGroupDefinition", "SiteGroupDefinitionId", ReferentialAction.Cascade);
private readonly ForeignKey<SiteGroupEntityBuilder> _siteForeignKey = new("FK_SiteGroup_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public SiteGroupEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_groupForeignKey);
ForeignKeys.Add(_siteForeignKey);
}
protected override SiteGroupEntityBuilder BuildTable(ColumnsBuilder table)
{
SiteGroupId = AddAutoIncrementColumn(table, "SiteGroupId");
SiteGroupDefinitionId = AddIntegerColumn(table, "SiteGroupDefinitionId");
SiteId = AddIntegerColumn(table, "SiteId");
Synchronize = AddBooleanColumn(table, "Synchronize");
NotifyRoleName = AddStringColumn(table, "NotifyRoleName", 256, true);
SynchronizedOn = AddDateTimeColumn(table, "SynchronizedOn", true);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> SiteGroupId { get; set; }
public OperationBuilder<AddColumnOperation> SiteGroupDefinitionId { get; set; }
public OperationBuilder<AddColumnOperation> SiteId { get; set; }
public OperationBuilder<AddColumnOperation> Synchronize { get; set; }
public OperationBuilder<AddColumnOperation> NotifyRoleName { get; set; }
public OperationBuilder<AddColumnOperation> SynchronizedOn { get; set; }
}
}

View File

@@ -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.10.01.00.01")]
public class AddSiteGroups : MultiDatabaseMigration
{
public AddSiteGroups(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteGroupDefinitionEntityBuilder = new SiteGroupDefinitionEntityBuilder(migrationBuilder, ActiveDatabase);
siteGroupDefinitionEntityBuilder.Create();
var siteGroupEntityBuilder = new SiteGroupEntityBuilder(migrationBuilder, ActiveDatabase);
siteGroupEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -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.10.01.00.02")]
public class AddCultureCode : MultiDatabaseMigration
{
public AddCultureCode(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.AddStringColumn("CultureCode", 10, true);
var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase);
userEntityBuilder.AddStringColumn("CultureCode", 10, true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -0,0 +1,28 @@
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.10.01.00.03")]
public class RemoveHomePageId : MultiDatabaseMigration
{
public RemoveHomePageId(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.DropColumn("HomePageId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -19,7 +19,7 @@ using Microsoft.Extensions.Caching.Memory;
namespace Oqtane.Modules.HtmlText.Manager
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISynchronizable, ISearchable
{
private readonly IHtmlTextRepository _htmlText;
private readonly IDBContextDependencies _DBContextDependencies;
@@ -41,7 +41,45 @@ namespace Oqtane.Modules.HtmlText.Manager
_cache = cache;
}
// IInstallable implementation
public bool Install(Tenant tenant, string version)
{
if (tenant.DBType == Constants.DefaultDBType && version == "1.0.1")
{
// version 1.0.0 used SQL scripts rather than migrations, so we need to seed the migration history table
_sqlRepository.ExecuteNonQuery(tenant, MigrationUtils.BuildInsertScript("HtmlText.01.00.00.00"));
}
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Up);
}
public bool Uninstall(Tenant tenant)
{
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Down);
}
// IPortable implementation
public string ExportModule(Module module)
{
return GetModuleContent(module);
}
public void ImportModule(Module module, string content, string version)
{
SaveModuleContent(module, content, version);
}
// ISynchronizable implementation
public string ExtractModule(Module module)
{
return GetModuleContent(module);
}
public void LoadModule(Module module, string content, string version)
{
SaveModuleContent(module, content, version);
}
private string GetModuleContent(Module module)
{
string content = "";
var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId);
@@ -53,6 +91,23 @@ namespace Oqtane.Modules.HtmlText.Manager
return content;
}
private void SaveModuleContent(Module module, string content, string version)
{
content = WebUtility.HtmlDecode(content);
var htmlText = new Models.HtmlText();
htmlText.ModuleId = module.ModuleId;
htmlText.Content = content;
_htmlText.AddHtmlText(htmlText);
//clear the cache for the module
var alias = _tenantManager.GetAlias();
if (alias != null)
{
_cache.Remove($"HtmlText:{alias.SiteKey}:{module.ModuleId}");
}
}
// ISearchable implementation
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
{
var searchContents = new List<SearchContent>();
@@ -70,36 +125,5 @@ namespace Oqtane.Modules.HtmlText.Manager
return Task.FromResult(searchContents);
}
public void ImportModule(Module module, string content, string version)
{
content = WebUtility.HtmlDecode(content);
var htmlText = new Models.HtmlText();
htmlText.ModuleId = module.ModuleId;
htmlText.Content = content;
_htmlText.AddHtmlText(htmlText);
//clear the cache for the module
var alias = _tenantManager.GetAlias();
if(alias != null)
{
_cache.Remove($"HtmlText:{alias.SiteKey}:{module.ModuleId}");
}
}
public bool Install(Tenant tenant, string version)
{
if (tenant.DBType == Constants.DefaultDBType && version == "1.0.1")
{
// version 1.0.0 used SQL scripts rather than migrations, so we need to seed the migration history table
_sqlRepository.ExecuteNonQuery(tenant, MigrationUtils.BuildInsertScript("HtmlText.01.00.00.00"));
}
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Up);
}
public bool Uninstall(Tenant tenant)
{
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Down);
}
}
}

View File

@@ -2,17 +2,29 @@ using System.Linq;
using Oqtane.Documentation;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Oqtane.Repository;
namespace Oqtane.Modules.HtmlText.Repository
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextRepository
{
IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId);
Models.HtmlText GetHtmlText(int htmlTextId);
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
void DeleteHtmlText(int htmlTextId);
}
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextRepository : IHtmlTextRepository, ITransientService
{
private readonly IDbContextFactory<HtmlTextContext> _factory;
private readonly IModuleRepository _moduleRepository;
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory)
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory, IModuleRepository moduleRepository)
{
_factory = factory;
_moduleRepository = moduleRepository;
}
public IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId)
@@ -32,6 +44,11 @@ namespace Oqtane.Modules.HtmlText.Repository
using var db = _factory.CreateDbContext();
db.HtmlText.Add(htmlText);
db.SaveChanges();
// update module ModifiedOn date
var module = _moduleRepository.GetModule(htmlText.ModuleId);
_moduleRepository.UpdateModule(module);
return htmlText;
}
@@ -39,8 +56,15 @@ namespace Oqtane.Modules.HtmlText.Repository
{
using var db = _factory.CreateDbContext();
Models.HtmlText htmlText = db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
if (htmlText != null) db.HtmlText.Remove(htmlText);
db.SaveChanges();
if (htmlText != null)
{
db.HtmlText.Remove(htmlText);
db.SaveChanges();
// update module ModifiedOn date
var module = _moduleRepository.GetModule(htmlText.ModuleId);
_moduleRepository.UpdateModule(module);
}
}
}
}

View File

@@ -1,14 +0,0 @@
using System.Collections.Generic;
using Oqtane.Documentation;
namespace Oqtane.Modules.HtmlText.Repository
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextRepository
{
IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId);
Models.HtmlText GetHtmlText(int htmlTextId);
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
void DeleteHtmlText(int htmlTextId);
}
}

View File

@@ -0,0 +1,13 @@
using Oqtane.Models;
namespace Oqtane.Modules
{
public interface ISynchronizable
{
// You Must Set The "ServerManagerType" In Your IModule Interface
string ExtractModule(Module module);
void LoadModule(Module module, string content, string version);
}
}

View File

@@ -134,5 +134,7 @@ namespace Oqtane.Repository
public virtual DbSet<SearchContentWord> SearchContentWord { get; set; }
public virtual DbSet<SearchWord> SearchWord { get; set; }
public virtual DbSet<MigrationHistory> MigrationHistory { get; set; }
public virtual DbSet<SiteGroupDefinition> SiteGroupDefinition { get; set; }
public virtual DbSet<SiteGroup> SiteGroup { get; set; }
}
}

View File

@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface ISiteGroupDefinitionRepository
{
IEnumerable<SiteGroupDefinition> GetSiteGroupDefinitions();
SiteGroupDefinition AddSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition);
SiteGroupDefinition UpdateSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition);
SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId);
SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId, bool tracking);
void DeleteSiteGroupDefinition(int siteGroupDefinitionId);
}
public class SiteGroupDefinitionRepository : ISiteGroupDefinitionRepository
{
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
public SiteGroupDefinitionRepository(IDbContextFactory<TenantDBContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public IEnumerable<SiteGroupDefinition> GetSiteGroupDefinitions()
{
using var db = _dbContextFactory.CreateDbContext();
return db.SiteGroupDefinition.ToList();
}
public SiteGroupDefinition AddSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition)
{
using var db = _dbContextFactory.CreateDbContext();
db.SiteGroupDefinition.Add(siteGroupDefinition);
db.SaveChanges();
return siteGroupDefinition;
}
public SiteGroupDefinition UpdateSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition)
{
using var db = _dbContextFactory.CreateDbContext();
db.Entry(siteGroupDefinition).State = EntityState.Modified;
db.SaveChanges();
return siteGroupDefinition;
}
public SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId)
{
return GetSiteGroupDefinition(siteGroupDefinitionId, true);
}
public SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId, bool tracking)
{
using var db = _dbContextFactory.CreateDbContext();
if (tracking)
{
return db.SiteGroupDefinition.FirstOrDefault(item => item.SiteGroupDefinitionId == siteGroupDefinitionId);
}
else
{
return db.SiteGroupDefinition.AsNoTracking().FirstOrDefault(item => item.SiteGroupDefinitionId == siteGroupDefinitionId);
}
}
public void DeleteSiteGroupDefinition(int siteGroupDefinitionId)
{
using var db = _dbContextFactory.CreateDbContext();
SiteGroupDefinition group = db.SiteGroupDefinition.Find(siteGroupDefinitionId);
db.SiteGroupDefinition.Remove(group);
db.SaveChanges();
}
}
}

View File

@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface ISiteGroupRepository
{
IEnumerable<SiteGroup> GetSiteGroups();
IEnumerable<SiteGroup> GetSiteGroups(int siteId, int siteGroupDefinitionId);
SiteGroup AddSiteGroup(SiteGroup siteGroup);
SiteGroup UpdateSiteGroup(SiteGroup siteGroup);
SiteGroup GetSiteGroup(int siteSiteGroupDefinitionId);
SiteGroup GetSiteGroup(int siteSiteGroupDefinitionId, bool tracking);
void DeleteSiteGroup(int siteSiteGroupDefinitionId);
}
public class SiteGroupRepository : ISiteGroupRepository
{
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
public SiteGroupRepository(IDbContextFactory<TenantDBContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public IEnumerable<SiteGroup> GetSiteGroups()
{
return GetSiteGroups(-1, -1);
}
public IEnumerable<SiteGroup> GetSiteGroups(int siteId, int siteGroupDefinitionId)
{
using var db = _dbContextFactory.CreateDbContext();
return db.SiteGroup
.Where(item => (siteId == -1 || item.SiteId == siteId) && (siteGroupDefinitionId == -1 || item.SiteGroupDefinitionId == siteGroupDefinitionId))
.Include(item => item.SiteGroupDefinition) // eager load
.ToList();
}
public SiteGroup AddSiteGroup(SiteGroup SiteGroup)
{
using var db = _dbContextFactory.CreateDbContext();
db.SiteGroup.Add(SiteGroup);
db.SaveChanges();
return SiteGroup;
}
public SiteGroup UpdateSiteGroup(SiteGroup SiteGroup)
{
using var db = _dbContextFactory.CreateDbContext();
db.Entry(SiteGroup).State = EntityState.Modified;
db.SaveChanges();
return SiteGroup;
}
public SiteGroup GetSiteGroup(int SiteGroupDefinitionId)
{
return GetSiteGroup(SiteGroupDefinitionId, true);
}
public SiteGroup GetSiteGroup(int SiteGroupDefinitionId, bool tracking)
{
using var db = _dbContextFactory.CreateDbContext();
if (tracking)
{
return db.SiteGroup
.Include(item => item.SiteGroupDefinition) // eager load
.FirstOrDefault(item => item.SiteGroupDefinitionId == SiteGroupDefinitionId);
}
else
{
return db.SiteGroup.AsNoTracking()
.Include(item => item.SiteGroupDefinition) // eager load
.FirstOrDefault(item => item.SiteGroupDefinitionId == SiteGroupDefinitionId);
}
}
public void DeleteSiteGroup(int SiteGroupDefinitionId)
{
using var db = _dbContextFactory.CreateDbContext();
SiteGroup SiteGroup = db.SiteGroup.Find(SiteGroupDefinitionId);
db.SiteGroup.Remove(SiteGroup);
db.SaveChanges();
}
}
}

View File

@@ -21,6 +21,8 @@ namespace Oqtane.Services
public class ServerSiteService : ISiteService
{
private readonly ISiteRepository _sites;
private readonly ISiteGroupRepository _siteGroups;
private readonly IAliasRepository _aliases;
private readonly IPageRepository _pages;
private readonly IThemeRepository _themes;
private readonly IPageModuleRepository _pageModules;
@@ -37,9 +39,11 @@ namespace Oqtane.Services
private readonly IHttpContextAccessor _accessor;
private readonly string _private = "[PRIVATE]";
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserManager userManager, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
public ServerSiteService(ISiteRepository sites, ISiteGroupRepository siteGroups, IAliasRepository aliases, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserManager userManager, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
{
_sites = sites;
_siteGroups = siteGroups;
_aliases = aliases;
_pages = pages;
_themes = themes;
_pageModules = pageModules;
@@ -145,12 +149,7 @@ namespace Oqtane.Services
site.Settings.Add(Constants.PageManagementModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule).ModuleId.ToString());
// languages
site.Languages = _languages.GetLanguages(site.SiteId).ToList();
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
if (!site.Languages.Exists(item => item.Code == defaultCulture.Name))
{
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = "", Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
}
site.Languages = GetLanguages(site.SiteId, alias.TenantId);
// themes
site.Themes = _themes.FilterThemes(_themes.GetThemes(site.SiteId).ToList());
@@ -158,6 +157,7 @@ namespace Oqtane.Services
// installation date used for fingerprinting static assets
site.Fingerprint = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm")));
// set tenant
site.TenantId = alias.TenantId;
}
else
@@ -311,6 +311,39 @@ namespace Oqtane.Services
return modules.OrderBy(item => item.PageId).ThenBy(item => item.Pane).ThenBy(item => item.Order).ToList();
}
private List<Language> GetLanguages(int siteId, int tenantId)
{
var languages = new List<Language>();
var siteGroups = _siteGroups.GetSiteGroups();
if (siteGroups.Any(item => item.SiteId == siteId && item.SiteGroupDefinition.Localization))
{
var sites = _sites.GetSites().ToList();
var aliases = _aliases.GetAliases().ToList();
foreach (var siteGroupDefinitionId in siteGroups.Where(item => item.SiteId == siteId && item.SiteGroupDefinition.Localization).Select(item => item.SiteGroupDefinitionId).Distinct().ToList())
{
foreach (var siteGroup in siteGroups.Where(item => item.SiteGroupDefinitionId == siteGroupDefinitionId))
{
var site = sites.FirstOrDefault(item => item.SiteId == siteGroup.SiteId);
if (site != null && !string.IsNullOrEmpty(site.CultureCode))
{
if (!languages.Any(item => item.Code == site.CultureCode))
{
var alias = aliases.FirstOrDefault(item => item.SiteId == siteGroup.SiteId && item.TenantId == tenantId && item.IsDefault);
if (alias != null)
{
languages.Add(new Language { Code = site.CultureCode, Name = "", AliasName = alias.Name, IsDefault = true });
}
}
}
}
}
}
return languages;
}
[Obsolete("This method is deprecated.", false)]
public void SetAlias(Alias alias)
{