ability to specify if a theme is enabled for a site

This commit is contained in:
sbwalker 2023-05-24 13:09:10 -04:00
parent 666f9c2db9
commit 98c2f012ee
23 changed files with 564 additions and 231 deletions

View File

@ -308,7 +308,7 @@
{
moduledefinition.Categories = _categories;
}
moduledefinition.IsEnabled = (_isenabled == null ? true : Boolean.Parse(_isenabled));
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);

View File

@ -98,37 +98,35 @@
</form>
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
private ElementReference form;
private bool validated = false;
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title;
private string _containerType;
private string _allPages = "false";
private string _permissionNames = "";
private List<Permission> _permissions = null;
private string _pageId;
private PermissionGrid _permissionGrid;
private Type _moduleSettingsType;
private object _moduleSettings;
private string _moduleSettingsTitle = "Module Settings";
private RenderFragment ModuleSettingsComponent { get; set; }
private Type _containerSettingsType;
private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
protected override async Task OnInitializedAsync()
{
_title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
protected override void OnInitialized()
{
_title = ModuleState.Title;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList;
@ -173,7 +171,7 @@
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error);
}
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);

View File

@ -10,7 +10,7 @@
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
@if (PageState.Site.Themes != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
@ -175,7 +175,6 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name;
@ -207,10 +206,9 @@
{
try
{
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings();
@ -262,7 +260,7 @@
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
@ -277,7 +275,7 @@
private void ThemeSettings()
{
_themeSettingsType = null;
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);

View File

@ -11,7 +11,7 @@
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
@if (_themeList != null)
@if (PageState.Site.Themes != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
@ -210,7 +210,6 @@
private ElementReference form;
private bool validated = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private int _pageId;
@ -251,8 +250,7 @@
try
{
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_pageId = Int32.Parse(PageState.QueryString["id"]);
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
@ -294,7 +292,7 @@
{
_themetype = PageState.Site.DefaultThemeType;
}
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype))
{
@ -396,7 +394,7 @@
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
@ -413,7 +411,7 @@
_themeSettingsType = null;
if (PageState.QueryString.ContainsKey("cp")) // can only be displayed if invoked from Control Panel
{
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);

View File

@ -330,7 +330,6 @@
private ElementReference form;
private bool validated = false;
private bool _initialized = false;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty;
@ -383,7 +382,6 @@
{
try
{
_themeList = await ThemeService.GetThemesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{
@ -405,9 +403,9 @@
{
_faviconfileid = site.FaviconFileId.Value;
}
_themes = ThemeService.GetThemeControls(_themeList);
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
@ -484,7 +482,7 @@
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
}
else
{

View File

@ -0,0 +1,161 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
}
@code {
private bool _initialized = false;
private ElementReference form;
private bool validated = false;
private int _themeId;
private string _themeName = "";
private string _isenabled;
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeId = Int32.Parse(PageState.QueryString["id"]);
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
if (theme != null)
{
_name = theme.Name;
_isenabled =theme.IsEnabled.ToString();
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;
_modifiedby = theme.ModifiedBy;
_modifiedon = theme.ModifiedOn;
_initialized = true;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
private async Task SaveTheme()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
await ThemeService.UpdateThemeAsync(theme);
await logger.LogInformation("Theme Saved {Theme}", theme);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Theme {ThemeId} {Error}", _themeId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
}

View File

@ -23,11 +23,12 @@ else
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditModule" /></td>
<td>
@if (context.AssemblyName != Constants.ClientId)
{
@ -36,6 +37,16 @@ else
</td>
<td>@context.Name</td>
<td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>

View File

@ -1,97 +0,0 @@
@namespace Oqtane.Modules.Admin.Themes
@using System.Net
@inherits ModuleBase
@inject IThemeService ThemeService
@inject NavigationManager NavigationManager
@inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<div class="col-sm-9">
<input id="themename" class="form-control" @bind="@_themeName" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed" ResourceKey="PackageName">Package Name: </Label>
<div class="col-sm-9">
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<div class="col-sm-9">
<input id="owner" class="form-control" @bind="@_owner" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<div class="col-sm-9">
<input id="contact" class="form-control" @bind="@_contact" disabled />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<div class="col-sm-9">
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</div>
</div>
</div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code {
private string _themeName = "";
private string _name;
private string _version;
private string _packagename;
private string _owner = "";
private string _url = "";
private string _contact = "";
private string _license = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
var themes = await ThemeService.GetThemesAsync();
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
if (theme != null)
{
_name = theme.Name;
_version = theme.Version;
_packagename = theme.PackageName;
_owner = theme.Owner;
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
}
}
}

View File

@ -168,4 +168,13 @@
<data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value>
</data>
<data name="Information.Heading" xml:space="preserve">
<value>Information</value>
</data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is theme enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

@ -144,4 +144,7 @@
<data name="ViewTheme.Text" xml:space="preserve">
<value>View</value>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root>

View File

@ -16,6 +16,14 @@ namespace Oqtane.Services
/// <returns></returns>
Task<List<Theme>> GetThemesAsync();
/// <summary>
/// Returns a specific thenme
/// </summary>
/// <param name="themeId"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task<Theme> GetThemeAsync(int themeId, int siteId);
/// <summary>
/// Returns a list of <see cref="ThemeControl"/>s from the given themes
/// </summary>
@ -39,6 +47,13 @@ namespace Oqtane.Services
/// <returns></returns>
List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName);
/// <summary>
/// Updates a existing theem
/// </summary>
/// <param name="theme"></param>
/// <returns></returns>
Task UpdateThemeAsync(Theme theme);
/// <summary>
/// Deletes a theme
/// </summary>

View File

@ -20,6 +20,10 @@ namespace Oqtane.Services
List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl);
return themes.OrderBy(item => item.Name).ToList();
}
public async Task<Theme> GetThemeAsync(int themeId, int siteId)
{
return await GetJsonAsync<Theme>($"{ApiUrl}/{themeId}?siteid={siteId}");
}
public List<ThemeControl> GetThemeControls(List<Theme> themes)
{
@ -38,6 +42,11 @@ namespace Oqtane.Services
.SelectMany(item => item.Containers).ToList();
}
public async Task UpdateThemeAsync(Theme theme)
{
await PutJsonAsync($"{ApiUrl}/{theme.ThemeId}", theme);
}
public async Task DeleteThemeAsync(string themeName)
{
await DeleteAsync($"{ApiUrl}/{themeName}");

View File

@ -256,7 +256,7 @@ namespace Oqtane.Controllers
}
// remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid);
_moduleDefinitions.DeleteModuleDefinition(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
}

View File

@ -12,6 +12,8 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Text.Json;
using System.Net;
using System.Reflection.Metadata;
using System;
// ReSharper disable StringIndexOfIsCultureSpecific.1
@ -23,14 +25,20 @@ namespace Oqtane.Controllers
private readonly IThemeRepository _themes;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
private readonly ITenantManager _tenantManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ILogManager logger)
public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
{
_themes = themes;
_installationManager = installationManager;
_environment = environment;
_tenantManager = tenantManager;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>
@ -41,6 +49,41 @@ namespace Oqtane.Controllers
return _themes.GetThemes();
}
// GET api/<controller>/5?siteid=x
[HttpGet("{id}")]
public Theme Get(int id, string siteid)
{
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _themes.GetTheme(id, SiteId);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {ThemeId} {SiteId}", id, siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public void Put(int id, [FromBody] Theme theme)
{
if (ModelState.IsValid && theme.SiteId == _alias.SiteId && _themes.GetTheme(theme.ThemeId,theme.SiteId) != null)
{
_themes.UpdateTheme(theme);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Theme, theme.ThemeId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Theme Updated {Theme}", theme);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Put Attempt {Theme}", theme);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
// DELETE api/<controller>/xxx
[HttpDelete("{themename}")]
[Authorize(Roles = RoleNames.Host)]
@ -74,7 +117,7 @@ namespace Oqtane.Controllers
}
// remove theme
_themes.DeleteTheme(theme.ThemeName);
//_themes.DeleteTheme(theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
}
else

View File

@ -0,0 +1,36 @@
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 ThemeEntityBuilder : AuditableBaseEntityBuilder<ThemeEntityBuilder>
{
private const string _entityTableName = "Theme";
private readonly PrimaryKey<ThemeEntityBuilder> _primaryKey = new("PK_Theme", x => x.ThemeId);
public ThemeEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
}
protected override ThemeEntityBuilder BuildTable(ColumnsBuilder table)
{
ThemeId = AddAutoIncrementColumn(table, "ThemeId");
ThemeName = AddStringColumn(table, "ThemeName", 200);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> ThemeId { get; private set; }
public OperationBuilder<AddColumnOperation> ThemeName { get; private set; }
}
}

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.Master
{
[DbContext(typeof(MasterDBContext))]
[Migration("Master.04.00.00.01")]
public class AddThemeTable : MultiDatabaseMigration
{
public AddThemeTable(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
themeEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -68,6 +68,7 @@ namespace Oqtane.Repository
public virtual DbSet<Job> Job { get; set; }
public virtual DbSet<JobLog> JobLog { get; set; }
public virtual DbSet<Setting> Setting { get; set; }
public virtual DbSet<Theme> Theme { get; set; }
public override int SaveChanges()
{

View File

@ -9,7 +9,7 @@ namespace Oqtane.Repository
IEnumerable<ModuleDefinition> GetModuleDefinitions(int siteId);
ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int siteId);
void UpdateModuleDefinition(ModuleDefinition moduleDefinition);
void DeleteModuleDefinition(int moduleDefinitionId, int siteId);
void DeleteModuleDefinition(int moduleDefinitionId);
ModuleDefinition FilterModuleDefinition(ModuleDefinition moduleDefinition);
}
}

View File

@ -6,7 +6,9 @@ namespace Oqtane.Repository
public interface IThemeRepository
{
IEnumerable<Theme> GetThemes();
Theme GetTheme(int themeId, int siteId);
void UpdateTheme(Theme theme);
void DeleteTheme(int themeId);
List<Theme> FilterThemes(List<Theme> themes);
void DeleteTheme(string ThemeName);
}
}

View File

@ -68,7 +68,7 @@ namespace Oqtane.Repository
_cache.Remove($"moduledefinitions:{_tenants.GetAlias().SiteKey}");
}
public void DeleteModuleDefinition(int moduleDefinitionId,int siteId)
public void DeleteModuleDefinition(int moduleDefinitionId)
{
ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId);
_settings.DeleteSettings(EntityNames.ModuleDefinition, moduleDefinitionId);
@ -126,48 +126,48 @@ namespace Oqtane.Repository
private List<ModuleDefinition> ProcessModuleDefinitions(int siteId)
{
// get module assemblies
List<ModuleDefinition> moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
List<ModuleDefinition> ModuleDefinitions = LoadModuleDefinitionsFromAssemblies();
// get module definitions in database
List<ModuleDefinition> moduledefs = _db.ModuleDefinition.ToList();
List<ModuleDefinition> moduledefinitions = _db.ModuleDefinition.ToList();
// sync module assemblies with database
foreach (ModuleDefinition moduledefinition in moduleDefinitions)
foreach (ModuleDefinition ModuleDefinition in ModuleDefinitions)
{
ModuleDefinition moduledef = moduledefs.Where(item => item.ModuleDefinitionName == moduledefinition.ModuleDefinitionName).FirstOrDefault();
if (moduledef == null)
ModuleDefinition moduledefinition = moduledefinitions.Where(item => item.ModuleDefinitionName == ModuleDefinition.ModuleDefinitionName).FirstOrDefault();
if (moduledefinition == null)
{
// new module definition
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledef);
moduledefinition = new ModuleDefinition { ModuleDefinitionName = ModuleDefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledefinition);
_db.SaveChanges();
moduledefinition.Version = "";
ModuleDefinition.Version = "";
}
else
{
// override user customizable property values
moduledefinition.Name = (!string.IsNullOrEmpty(moduledef.Name)) ? moduledef.Name : moduledefinition.Name;
moduledefinition.Description = (!string.IsNullOrEmpty(moduledef.Description)) ? moduledef.Description : moduledefinition.Description;
moduledefinition.Categories = (!string.IsNullOrEmpty(moduledef.Categories)) ? moduledef.Categories : moduledefinition.Categories;
ModuleDefinition.Name = (!string.IsNullOrEmpty(moduledefinition.Name)) ? moduledefinition.Name : ModuleDefinition.Name;
ModuleDefinition.Description = (!string.IsNullOrEmpty(moduledefinition.Description)) ? moduledefinition.Description : ModuleDefinition.Description;
ModuleDefinition.Categories = (!string.IsNullOrEmpty(moduledefinition.Categories)) ? moduledefinition.Categories : ModuleDefinition.Categories;
// manage releaseversions in cases where it was not provided or is lower than the module version
if (string.IsNullOrEmpty(moduledefinition.ReleaseVersions) || Version.Parse(moduledefinition.Version).CompareTo(Version.Parse(moduledefinition.ReleaseVersions.Split(',').Last())) > 0)
if (string.IsNullOrEmpty(ModuleDefinition.ReleaseVersions) || Version.Parse(ModuleDefinition.Version).CompareTo(Version.Parse(ModuleDefinition.ReleaseVersions.Split(',').Last())) > 0)
{
moduledefinition.ReleaseVersions = moduledefinition.Version;
ModuleDefinition.ReleaseVersions = ModuleDefinition.Version;
}
moduledefinition.Version = moduledef.Version;
ModuleDefinition.Version = moduledefinition.Version;
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
moduledefinitions.Remove(moduledefinition);
}
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.CreatedBy = moduledef.CreatedBy;
moduledefinition.CreatedOn = moduledef.CreatedOn;
moduledefinition.ModifiedBy = moduledef.ModifiedBy;
moduledefinition.ModifiedOn = moduledef.ModifiedOn;
ModuleDefinition.ModuleDefinitionId = moduledefinition.ModuleDefinitionId;
ModuleDefinition.CreatedBy = moduledefinition.CreatedBy;
ModuleDefinition.CreatedOn = moduledefinition.CreatedOn;
ModuleDefinition.ModifiedBy = moduledefinition.ModifiedBy;
ModuleDefinition.ModifiedOn = moduledefinition.ModifiedOn;
}
// any remaining module definitions are orphans
foreach (ModuleDefinition moduledefinition in moduledefs)
foreach (ModuleDefinition moduledefinition in moduledefinitions)
{
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
@ -181,8 +181,8 @@ namespace Oqtane.Repository
// get settings for site
var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList();
// populate module definition permissions
foreach (ModuleDefinition moduledefinition in moduleDefinitions)
// populate module definition site settings and permissions
foreach (ModuleDefinition moduledefinition in ModuleDefinitions)
{
moduledefinition.SiteId = siteId;
@ -218,7 +218,7 @@ namespace Oqtane.Repository
}
// clean up any orphaned permissions
var ids = new HashSet<int>(moduleDefinitions.Select(item => item.ModuleDefinitionId));
var ids = new HashSet<int>(ModuleDefinitions.Select(item => item.ModuleDefinitionId));
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
{
try
@ -232,7 +232,7 @@ namespace Oqtane.Repository
}
}
return moduleDefinitions;
return ModuleDefinitions;
}
private List<ModuleDefinition> LoadModuleDefinitionsFromAssemblies()

View File

@ -4,39 +4,168 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using System.Security;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes;
using System.Reflection.Metadata;
namespace Oqtane.Repository
{
public class ThemeRepository : IThemeRepository
{
private MasterDBContext _db;
private readonly IMemoryCache _cache;
private readonly ITenantManager _tenants;
private readonly ISettingRepository _settings;
private readonly string settingprefix = "SiteEnabled:";
public ThemeRepository(IMemoryCache cache)
public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings)
{
_db = context;
_cache = cache;
_tenants = tenants;
_settings = settings;
}
public IEnumerable<Theme> GetThemes()
{
return LoadThemes();
// for consistency siteid should be passed in as parameter, but this would require breaking change
return LoadThemes(_tenants.GetAlias().SiteId);
}
private List<Theme> LoadThemes()
public Theme GetTheme(int themeId, int siteId)
{
// get module definitions
List<Theme> themes = _cache.GetOrCreate("themes", entry =>
List<Theme> themes = LoadThemes(siteId);
return themes.Find(item => item.ThemeId == themeId);
}
public void UpdateTheme(Theme theme)
{
_db.Entry(theme).State = EntityState.Modified;
_db.SaveChanges();
var settingname = $"{settingprefix}{_tenants.GetAlias().SiteKey}";
var setting = _settings.GetSetting(EntityNames.Theme, theme.ThemeId, settingname);
if (setting == null)
{
_settings.AddSetting(new Setting { EntityName = EntityNames.Theme, EntityId = theme.ThemeId, SettingName = settingname, SettingValue = theme.IsEnabled.ToString(), IsPrivate = true });
}
else
{
setting.SettingValue = theme.IsEnabled.ToString();
_settings.UpdateSetting(setting);
}
_cache.Remove($"themes:{_tenants.GetAlias().SiteKey}");
}
public void DeleteTheme(int themeId)
{
Theme theme = _db.Theme.Find(themeId);
_settings.DeleteSettings(EntityNames.Theme, themeId);
_db.Theme.Remove(theme);
_db.SaveChanges();
_cache.Remove($"themes:{_tenants.GetAlias().SiteKey}");
}
public List<Theme> FilterThemes(List<Theme> themes)
{
var Themes = new List<Theme>();
foreach (Theme theme in themes.Where(item => item.IsEnabled))
{
var Theme = new Theme();
Theme.ThemeName = theme.ThemeName;
Theme.Name = theme.Name;
Theme.Resources = theme.Resources;
Theme.Themes = theme.Themes;
Theme.Containers = theme.Containers;
Themes.Add(Theme);
}
return Themes;
}
private List<Theme> LoadThemes(int siteId)
{
// get themes
List<Theme> themes = _cache.GetOrCreate($"themes:{_tenants.GetAlias().SiteKey}", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return LoadThemesFromAssemblies();
return ProcessThemes(siteId);
});
return themes;
}
private List<Theme> ProcessThemes(int siteId)
{
// get themes
List<Theme> Themes = LoadThemesFromAssemblies();
// get themes in database
List<Theme> themes = _db.Theme.ToList();
// sync theme assemblies with database
foreach (Theme Theme in Themes)
{
Theme theme = themes.Where(item => item.ThemeName == Theme.ThemeName).FirstOrDefault();
if (theme == null)
{
// new theme
theme = new Theme { ThemeName = Theme.ThemeName };
_db.Theme.Add(theme);
_db.SaveChanges();
}
else
{
// remove theme from list as it is already synced
themes.Remove(theme);
}
Theme.ThemeId = theme.ThemeId;
Theme.CreatedBy = theme.CreatedBy;
Theme.CreatedOn = theme.CreatedOn;
Theme.ModifiedBy = theme.ModifiedBy;
Theme.ModifiedOn = theme.ModifiedOn;
}
// any remaining themes are orphans
foreach (Theme theme in themes)
{
_db.Theme.Remove(theme); // delete
_db.SaveChanges();
}
if (siteId != -1)
{
// get settings for site
var settings = _settings.GetSettings(EntityNames.Theme).ToList();
// populate theme site settings
foreach (Theme theme in Themes)
{
theme.SiteId = siteId;
var setting = settings.FirstOrDefault(item => item.EntityId == theme.ThemeId && item.SettingName == $"{settingprefix}{_tenants.GetAlias().SiteKey}");
if (setting != null)
{
theme.IsEnabled = bool.Parse(setting.SettingValue);
}
else
{
theme.IsEnabled = theme.IsAutoEnabled;
}
}
}
return Themes;
}
private List<Theme> LoadThemesFromAssemblies()
{
List<Theme> themes = new List<Theme>();
@ -143,28 +272,5 @@ namespace Oqtane.Repository
}
return themes;
}
public List<Theme> FilterThemes(List<Theme> themes)
{
var Themes = new List<Theme>();
foreach (Theme theme in themes)
{
var Theme = new Theme();
Theme.ThemeName = theme.ThemeName;
Theme.Name = theme.Name;
Theme.Resources = theme.Resources;
Theme.Themes = theme.Themes;
Theme.Containers = theme.Containers;
Themes.Add(Theme);
}
return Themes;
}
public void DeleteTheme(string ThemeName)
{
_cache.Remove("themes");
}
}
}

View File

@ -7,7 +7,7 @@ namespace Oqtane.Models
/// <summary>
/// Information about a Theme in Oqtane.
/// </summary>
public class Theme
public class Theme : ModelBase
{
public Theme()
{
@ -25,68 +25,81 @@ namespace Oqtane.Models
Resources = null;
}
/// <summary>
/// Reference to the <see cref="Theme"/>.
/// </summary>
public int ThemeId { get; set; }
/// <summary>
/// Full Namespace / Identifier of the Theme.
/// </summary>
public string ThemeName { get; set; }
/// <summary>
/// Nice Name of the Theme.
/// </summary>
// additional ITheme properties
[NotMapped]
public string Name { get; set; }
/// <summary>
/// Version as determined by the DLL / NuGet Package.
/// </summary>
[NotMapped]
public string Version { get; set; }
/// <summary>
/// Author / Creator of the Theme.
/// </summary>
[NotMapped]
public string Owner { get; set; }
/// <summary>
/// URL (in NuGet) of the Theme
/// </summary>
[NotMapped]
public string Url { get; set; }
/// <summary>
/// Author Contact information
/// </summary>
[NotMapped]
public string Contact { get; set; }
/// <summary>
/// Theme License, like `MIT` etc.
/// </summary>
[NotMapped]
public string License { get; set; }
/// <summary>
/// Theme Dependencies (DLLs) which the system will check if they exist
/// </summary>
[NotMapped]
public string Dependencies { get; set; }
[NotMapped]
public string ThemeSettingsType { get; set; } // added in 2.0.2
[NotMapped]
public string ContainerSettingsType { get; set; } // added in 2.0.2
[NotMapped]
public string PackageName { get; set; } // added in 2.1.0
[NotMapped]
public List<Resource> Resources { get; set; } // added in 4.0.0
[NotMapped]
public bool IsAutoEnabled { get; set; } = true; // added in 4.0.0
// internal properties
[NotMapped]
public int SiteId { get; set; }
[NotMapped]
public bool IsEnabled { get; set; }
[NotMapped]
public string AssemblyName { get; set; }
[NotMapped]
public List<ThemeControl> Themes { get; set; }
[NotMapped]
public List<ThemeControl> Containers { get; set; }
[NotMapped]
public string Template { get; set; }
#region Obsolete Properties
[Obsolete("This property is obsolete. Use Themes instead.", false)]
[NotMapped]
public string ThemeControls { get; set; }
[Obsolete("This property is obsolete. Use Layouts instead.", false)]
[NotMapped]
public string PaneLayouts { get; set; }
[Obsolete("This property is obsolete. Use Containers instead.", false)]
[NotMapped]
public string ContainerControls { get; set; }
[Obsolete("This property is obsolete.", false)]
[NotMapped]
public List<ThemeControl> Layouts { get; set; }
#endregion

View File

@ -17,6 +17,7 @@ namespace Oqtane.Shared
public const string Setting = "Setting";
public const string Site = "Site";
public const string Tenant = "Tenant";
public const string Theme = "Theme";
public const string UrlMapping = "UrlMapping";
public const string User = "User";
public const string UserRole = "UserRole";