From 153a689bdbb5da18128af08782e7053553e894c1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 27 Jan 2025 16:34:47 -0500 Subject: [PATCH] fix #5005 - adds versioning (ie. fingerprinting) for static assets - core, modules, and themes. --- Oqtane.Client/UI/SiteRouter.razor | 18 +++++----- Oqtane.Server/Components/App.razor | 35 ++++++++++--------- .../Controllers/InstallationController.cs | 22 +----------- .../Controllers/PackageController.cs | 2 +- Oqtane.Server/Infrastructure/ConfigManager.cs | 6 ++++ .../Infrastructure/DatabaseManager.cs | 12 ++++++- .../Master/06000201_AddThemeVersion.cs | 28 +++++++++++++++ .../Repository/ModuleDefinitionRepository.cs | 1 + Oqtane.Server/Repository/ThemeRepository.cs | 9 +++++ Oqtane.Server/Services/SiteService.cs | 7 +++- Oqtane.Shared/Models/ModuleDefinition.cs | 5 ++- Oqtane.Shared/Models/Resource.cs | 17 ++++++++- Oqtane.Shared/Models/Site.cs | 9 ++++- Oqtane.Shared/Models/Theme.cs | 15 ++++++-- Oqtane.Shared/Shared/Utilities.cs | 19 ++++++---- 15 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 68996c8b..da50e350 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -391,7 +391,7 @@ if (themetype != null) { // get resources for theme (ITheme) - page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName)); + page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Hash); var themeobject = Activator.CreateInstance(themetype) as IThemeControl; if (themeobject != null) @@ -401,7 +401,7 @@ panes = themeobject.Panes; } // get resources for theme control - page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace); + page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace, theme.Hash); } } // theme settings components are dynamically loaded within the framework Page Management module @@ -411,7 +411,7 @@ if (settingsType != null) { var objSettings = Activator.CreateInstance(settingsType) as IModuleControl; - page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace); + page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Hash); } } @@ -455,7 +455,7 @@ if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) { - page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName)); + page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Hash); // handle default action if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) @@ -504,7 +504,7 @@ module.RenderMode = moduleobject.RenderMode; module.Prerender = moduleobject.Prerender; - page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash); // settings components are dynamically loaded within the framework Settings module if (action.ToLower() == "settings" && module.ModuleDefinition != null) @@ -525,7 +525,7 @@ if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash); } // container settings component @@ -536,7 +536,7 @@ if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Hash); } } } @@ -595,7 +595,7 @@ return (page, modules); } - private List ManagePageResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name) + private List ManagePageResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name, string version) { if (resources != null) { @@ -615,7 +615,7 @@ // ensure resource does not exist already if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) { - pageresources.Add(resource.Clone(level, name)); + pageresources.Add(resource.Clone(level, name, version)); } } } diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index e487eecf..cce1ab33 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -39,7 +39,7 @@ - + @if (_scripts.Contains("PWA Manifest")) { @@ -70,15 +70,15 @@ } - - - + + + @((MarkupString)_scripts) @((MarkupString)_bodyResources) @if (_renderMode == RenderModes.Static) { - + } } else @@ -94,6 +94,7 @@ private string _renderMode = RenderModes.Interactive; private string _runtime = Runtimes.Server; private bool _prerender = true; + private string _hash = ""; private int _visitorId = -1; private string _antiForgeryToken = ""; private string _remoteIPAddress = ""; @@ -136,6 +137,8 @@ _renderMode = site.RenderMode; _runtime = site.Runtime; _prerender = site.Prerender; + _hash = site.Hash; + var modules = new List(); Route route = new Route(url, alias.Path); @@ -605,13 +608,13 @@ var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType)); if (theme != null) { - resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode); + resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Hash, site.RenderMode); } else { // fallback to default Oqtane theme theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme)); - resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode); + resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Hash, site.RenderMode); } var type = Type.GetType(themeType); if (type != null) @@ -619,7 +622,7 @@ var obj = Activator.CreateInstance(type) as IThemeControl; if (obj != null) { - resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode); + resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, theme.Hash, site.RenderMode); } } // theme settings components are dynamically loaded within the framework Page Management module @@ -629,7 +632,7 @@ if (settingsType != null) { var objSettings = Activator.CreateInstance(settingsType) as IModuleControl; - resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode); + resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Hash, site.RenderMode); } } @@ -638,7 +641,7 @@ var typename = ""; if (module.ModuleDefinition != null) { - resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); + resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Hash, site.RenderMode); // handle default action if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) @@ -684,7 +687,7 @@ var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; if (moduleobject != null) { - resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode); + resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash, site.RenderMode); // settings components are dynamically loaded within the framework Settings module if (action.ToLower() == "settings" && module.ModuleDefinition != null) @@ -705,7 +708,7 @@ if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode); + resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Hash, site.RenderMode); } // container settings component @@ -715,7 +718,7 @@ if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; - resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode); + resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Hash, site.RenderMode); } } } @@ -731,7 +734,7 @@ { if (module.ModuleDefinition?.Resources != null) { - resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); + resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Hash, site.RenderMode); } } } @@ -739,7 +742,7 @@ return resources; } - private List AddResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name, string rendermode) + private List AddResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name, string version, string rendermode) { if (resources != null) { @@ -759,7 +762,7 @@ // ensure resource does not exist already if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) { - pageresources.Add(resource.Clone(level, name)); + pageresources.Add(resource.Clone(level, name, version)); } } } diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 48bbf8b8..207d082c 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -286,7 +286,7 @@ namespace Oqtane.Controllers DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath); if (hashfilename) { - HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath); + HashedName = Utilities.GenerateSimpleHash(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath); } else { @@ -297,25 +297,5 @@ namespace Oqtane.Controllers public string FilePath { get; private set; } public string HashedName { get; private set; } } - - private static int GetDeterministicHashCode(string value) - { - unchecked - { - int hash1 = (5381 << 16) + 5381; - int hash2 = hash1; - - for (int i = 0; i < value.Length; i += 2) - { - hash1 = ((hash1 << 5) + hash1) ^ value[i]; - if (i == value.Length - 1) - break; - hash2 = ((hash2 << 5) + hash2) ^ value[i + 1]; - } - - return hash1 + (hash2 * 1566083941); - } - } - } } diff --git a/Oqtane.Server/Controllers/PackageController.cs b/Oqtane.Server/Controllers/PackageController.cs index f1aba718..c0b943bc 100644 --- a/Oqtane.Server/Controllers/PackageController.cs +++ b/Oqtane.Server/Controllers/PackageController.cs @@ -132,7 +132,7 @@ namespace Oqtane.Controllers if (user != null) { email = user.Email; - _configManager.AddOrUpdateSetting("PackageRegistryEmail", email, false); + _configManager.AddOrUpdateSetting("PackageRegistryEmail", email, true); } } } diff --git a/Oqtane.Server/Infrastructure/ConfigManager.cs b/Oqtane.Server/Infrastructure/ConfigManager.cs index 8b96569d..d6b0d8d9 100644 --- a/Oqtane.Server/Infrastructure/ConfigManager.cs +++ b/Oqtane.Server/Infrastructure/ConfigManager.cs @@ -175,6 +175,12 @@ namespace Oqtane.Infrastructure installationid = Guid.NewGuid().ToString(); AddOrUpdateSetting("InstallationId", installationid, true); } + var version = GetSetting("InstallationVersion", ""); + if (version != Constants.Version) + { + AddOrUpdateSetting("InstallationVersion", Constants.Version, true); + AddOrUpdateSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm"), true); + } return installationid; } } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 3509eafa..2273cded 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -265,6 +265,7 @@ namespace Oqtane.Infrastructure var installation = IsInstalled(); try { + UpdateInstallation(); UpdateConnectionString(install.ConnectionString); UpdateDatabaseType(install.DatabaseType); @@ -491,6 +492,7 @@ namespace Oqtane.Infrastructure moduleDefinition.Categories = moduledef.Categories; // update version moduleDefinition.Version = versions[versions.Length - 1]; + moduleDefinition.ModifiedOn = DateTime.UtcNow; db.Entry(moduleDefinition).State = EntityState.Modified; db.SaveChanges(); } @@ -666,6 +668,11 @@ namespace Oqtane.Infrastructure return connectionString; } + public void UpdateInstallation() + { + _config.GetInstallationId(); + } + public void UpdateConnectionString(string connectionString) { connectionString = DenormalizeConnectionString(connectionString); @@ -677,7 +684,10 @@ namespace Oqtane.Infrastructure public void UpdateDatabaseType(string databaseType) { - _configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true); + if (_config.GetSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", "") != databaseType) + { + _configManager.AddOrUpdateSetting($"{SettingKeys.DatabaseSection}:{SettingKeys.DatabaseTypeKey}", databaseType, true); + } } public void AddEFMigrationsHistory(ISqlRepository sql, string connectionString, string databaseType, string version, bool isMaster) diff --git a/Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs b/Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs new file mode 100644 index 00000000..def80a48 --- /dev/null +++ b/Oqtane.Server/Migrations/Master/06000201_AddThemeVersion.cs @@ -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.Master +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.06.00.02.01")] + public class AddThemeVersion : MultiDatabaseMigration + { + public AddThemeVersion(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase); + themeEntityBuilder.AddStringColumn("Version", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 15eadf9e..cae8cf15 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -101,6 +101,7 @@ namespace Oqtane.Repository ModuleDefinition.Resources = moduleDefinition.Resources; ModuleDefinition.IsEnabled = moduleDefinition.IsEnabled; ModuleDefinition.PackageName = moduleDefinition.PackageName; + ModuleDefinition.Hash = Utilities.GenerateSimpleHash(moduleDefinition.ModifiedOn.ToString("yyyyMMddHHmm")); } return ModuleDefinition; diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 2802f8f0..22af55ee 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; @@ -87,6 +88,7 @@ namespace Oqtane.Repository Theme.ThemeSettingsType = theme.ThemeSettingsType; Theme.ContainerSettingsType = theme.ContainerSettingsType; Theme.PackageName = theme.PackageName; + Theme.Hash = Utilities.GenerateSimpleHash(theme.ModifiedOn.ToString("yyyyMMddHHmm")); Themes.Add(Theme); } @@ -126,6 +128,13 @@ namespace Oqtane.Repository } else { + if (theme.Version != Theme.Version) + { + // update theme version + theme.Version = Theme.Version; + _db.SaveChanges(); + } + // override user customizable property values Theme.Name = (!string.IsNullOrEmpty(theme.Name)) ? theme.Name : Theme.Name; diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 2a12b38b..dbf65656 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -29,12 +29,13 @@ namespace Oqtane.Services private readonly ISettingRepository _settings; private readonly ITenantManager _tenantManager; private readonly ISyncManager _syncManager; + private readonly IConfigManager _configManager; private readonly ILogManager _logger; private readonly IMemoryCache _cache; private readonly IHttpContextAccessor _accessor; private readonly string _private = "[PRIVATE]"; - public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor) + public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor) { _sites = sites; _pages = pages; @@ -46,6 +47,7 @@ namespace Oqtane.Services _settings = settings; _tenantManager = tenantManager; _syncManager = syncManager; + _configManager = configManager; _logger = logger; _cache = cache; _accessor = accessor; @@ -143,6 +145,9 @@ namespace Oqtane.Services // themes site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList()); + + // installation date used for fingerprinting static assets + site.Hash = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm"))); } else { diff --git a/Oqtane.Shared/Models/ModuleDefinition.cs b/Oqtane.Shared/Models/ModuleDefinition.cs index 5c374667..d5553675 100644 --- a/Oqtane.Shared/Models/ModuleDefinition.cs +++ b/Oqtane.Shared/Models/ModuleDefinition.cs @@ -65,7 +65,7 @@ namespace Oqtane.Models public string Categories { get; set; } /// - /// Version information of this Module based on the DLL / NuGet package. + /// Version information of this Module based on the information stored in its assembly /// public string Version { get; set; } @@ -144,6 +144,9 @@ namespace Oqtane.Models [NotMapped] public bool IsPortable { get; set; } + [NotMapped] + public string Hash { get; set; } + #region Deprecated Properties [Obsolete("The Permissions property is deprecated. Use PermissionList instead", false)] diff --git a/Oqtane.Shared/Models/Resource.cs b/Oqtane.Shared/Models/Resource.cs index f3ee3349..5f5ab968 100644 --- a/Oqtane.Shared/Models/Resource.cs +++ b/Oqtane.Shared/Models/Resource.cs @@ -83,7 +83,21 @@ namespace Oqtane.Models /// public string Namespace { get; set; } - public Resource Clone(ResourceLevel level, string name) + /// + /// The version of the theme or module that declared the resource - only used in SiteRouter + /// + public string Version + { + set + { + if (!string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(Url) && !Url.Contains("?")) + { + Url += "?v=" + value; + } + } + } + + public Resource Clone(ResourceLevel level, string name, string version) { var resource = new Resource(); resource.ResourceType = ResourceType; @@ -106,6 +120,7 @@ namespace Oqtane.Models } resource.Level = level; resource.Namespace = name; + resource.Version = version; return resource; } diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index b108cea2..c45cfe4e 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -187,6 +187,12 @@ namespace Oqtane.Models [NotMapped] public List Themes { get; set; } + /// + /// hash code for static assets + /// + [NotMapped] + public string Hash { get; set; } + public Site Clone() { return new Site @@ -227,7 +233,8 @@ namespace Oqtane.Models Settings = Settings.ToDictionary(setting => setting.Key, setting => setting.Value), Pages = Pages.ConvertAll(page => page.Clone()), Languages = Languages.ConvertAll(language => language.Clone()), - Themes = Themes + Themes = Themes, + Hash = Hash }; } diff --git a/Oqtane.Shared/Models/Theme.cs b/Oqtane.Shared/Models/Theme.cs index 4ff9fe2f..95715c47 100644 --- a/Oqtane.Shared/Models/Theme.cs +++ b/Oqtane.Shared/Models/Theme.cs @@ -40,10 +40,13 @@ namespace Oqtane.Models /// public string Name { get; set; } - // additional ITheme properties - [NotMapped] + /// + /// Version information of this Theme based on the information stored in its assembly + /// public string Version { get; set; } + // additional ITheme properties + [NotMapped] public string Owner { get; set; } @@ -78,17 +81,25 @@ namespace Oqtane.Models // internal properties [NotMapped] public int SiteId { get; set; } + [NotMapped] public bool IsEnabled { get; set; } + [NotMapped] public string AssemblyName { get; set; } + [NotMapped] public List Themes { get; set; } + [NotMapped] public List Containers { get; set; } + [NotMapped] public string Template { get; set; } + [NotMapped] + public string Hash { get; set; } + #region Obsolete Properties [Obsolete("This property is obsolete. Use Themes instead.", false)] diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 6e8f3933..826fac70 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -575,7 +575,6 @@ namespace Oqtane.Shared } else if (expiryDate.HasValue) { - // Include equality check here return currentUtcTime <= expiryDate.Value; } else @@ -586,32 +585,40 @@ namespace Oqtane.Shared public static bool ValidateEffectiveExpiryDates(DateTime? effectiveDate, DateTime? expiryDate) { - // Treat DateTime.MinValue as null effectiveDate ??= DateTime.MinValue; expiryDate ??= DateTime.MinValue; - // Check if both effectiveDate and expiryDate have values if (effectiveDate != DateTime.MinValue && expiryDate != DateTime.MinValue) { return effectiveDate <= expiryDate; } - // Check if only effectiveDate has a value else if (effectiveDate != DateTime.MinValue) { return true; } - // Check if only expiryDate has a value else if (expiryDate != DateTime.MinValue) { return true; } - // If neither effectiveDate nor expiryDate has a value, consider the page/module visible else { return true; } } + public static string GenerateSimpleHash(string text) + { + unchecked // prevent overflow exception + { + int hash = 23; + foreach (char c in text) + { + hash = hash * 31 + c; + } + return hash.ToString("X8"); + } + } + [Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)] public static string ContentUrl(Alias alias, int fileId) {