Merge pull request #2935 from oqtane/dev

4.0.0 Release
This commit is contained in:
Shaun Walker
2023-06-26 11:30:59 -04:00
committed by GitHub
195 changed files with 5070 additions and 3598 deletions

View File

@ -1,6 +1,8 @@
@using Microsoft.AspNetCore.Http
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject SiteState SiteState @inject SiteState SiteState
@inject IServiceProvider ServiceProvider
@if (_initialized) @if (_initialized)
{ {
@ -30,35 +32,47 @@
} }
@code { @code {
[Parameter] [Parameter]
public string AntiForgeryToken { get; set; } public string AntiForgeryToken { get; set; }
[Parameter] [Parameter]
public string Runtime { get; set; } public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter] [Parameter]
public int VisitorId { get; set; } public int VisitorId { get; set; }
[Parameter] [Parameter]
public string RemoteIPAddress { get; set; } public string RemoteIPAddress { get; set; }
[Parameter] [Parameter]
public string AuthorizationToken { get; set; } public string AuthorizationToken { get; set; }
private bool _initialized = false; private bool _initialized = false;
private string _display = "display: none;"; private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" }; private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; } private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync() private IHttpContextAccessor accessor;
{
SiteState.RemoteIPAddress = RemoteIPAddress; protected override async Task OnParametersSetAsync()
SiteState.AntiForgeryToken = AntiForgeryToken; {
SiteState.AuthorizationToken = AuthorizationToken; SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken;
accessor = (IHttpContextAccessor)ServiceProvider.GetService(typeof(IHttpContextAccessor));
if (accessor != null)
{
SiteState.IsPrerendering = !accessor.HttpContext.Response.HasStarted;
}
else
{
SiteState.IsPrerendering = true;
}
_installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
if (_installation.Alias != null) if (_installation.Alias != null)
@ -72,6 +86,7 @@
{ {
if (firstRender) if (firstRender)
{ {
// prevents flash on initial page load
_display = ""; _display = "";
StateHasChanged(); StateHasChanged();
} }

64
Oqtane.Client/Head.razor Normal file
View File

@ -0,0 +1,64 @@
@using System.ComponentModel
@using Oqtane.Shared
@inject SiteState SiteState
@if (!string.IsNullOrEmpty(_title))
{
@((MarkupString)_title)
}
@if (!string.IsNullOrEmpty(_content))
{
@((MarkupString)_content)
}
@code {
private string _title = "";
private string _content = "";
protected override void OnInitialized()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged += PropertyChanged;
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "PageTitle":
var title = "\n<title>" + SiteState.Properties.PageTitle + "</title>";
if (title != _title)
{
_title = title;
StateHasChanged();
}
break;
case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content)
{
_content = content;
StateHasChanged();
}
break;
}
}
private string RemoveScripts(string headcontent)
{
if (!string.IsNullOrEmpty(headcontent))
{
var index = headcontent.IndexOf("<script");
while (index >= 0)
{
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index);
index = headcontent.IndexOf("<script");
}
}
return headcontent;
}
public void Dispose()
{
((INotifyPropertyChanged)SiteState.Properties).PropertyChanged -= PropertyChanged;
}
}

View File

@ -5,15 +5,17 @@
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IUserService UserService @inject IUserService UserService
@inject IDatabaseService DatabaseService @inject IDatabaseService DatabaseService
@inject ISiteTemplateService SiteTemplateService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer @inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject SiteState SiteState
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<img src="oqtane-black.png" /> <img src="oqtane-black.png" />
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div> <div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 7)</div>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
@ -61,7 +63,7 @@
</div> </div>
</div> </div>
} }
</div> </div>
</div> </div>
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</h2><br /> <h2>@Localizer["ApplicationAdmin"]</h2><br />
@ -96,6 +98,20 @@
<input type="text" class="form-control" @bind="@_hostEmail" /> <input type="text" class="form-control" @bind="@_hostEmail" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="template" HelpText="Select a site template" ResourceKey="Template">Template:</Label>
<div class="col-sm-9">
@if (_templates != null)
{
<select id="template" class="form-select" @bind="@_template" required>
@foreach (var template in _templates)
{
<option value="@template.TypeName">@template.Name</option>
}
</select>
}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -103,7 +119,13 @@
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br /> <button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage> @if (!string.IsNullOrEmpty(_message))
{
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
@((MarkupString)_message)
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div>
}
</div> </div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div> <div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div> </div>
@ -115,29 +137,35 @@
</div> </div>
@code { @code {
private List<Database> _databases; private List<Database> _databases;
private string _databaseName; private string _databaseName;
private Type _databaseConfigType; private Type _databaseConfigType;
private object _databaseConfig; private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; } private RenderFragment DatabaseConfigComponent { get; set; }
private bool _showConnectionString = false; private bool _showConnectionString = false;
private string _connectionString = string.Empty; private string _connectionString = string.Empty;
private string _hostUsername = string.Empty; private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty; private string _hostPassword = string.Empty;
private string _passwordType = "password"; private string _passwordType = "password";
private string _confirmPasswordType = "password"; private string _confirmPasswordType = "password";
private string _togglePassword = string.Empty; private string _togglePassword = string.Empty;
private string _toggleConfirmPassword = string.Empty; private string _toggleConfirmPassword = string.Empty;
private string _confirmPassword = string.Empty; private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty; private string _hostEmail = string.Empty;
private bool _register = true; private List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true;
private string _message = string.Empty; private string _message = string.Empty;
private string _loadingDisplay = "display: none;"; private string _loadingDisplay = "display: none;";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_togglePassword = SharedLocalizer["ShowPassword"]; // include CSS
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
SiteState.AppendHeadContent(content);
_togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"]; _toggleConfirmPassword = SharedLocalizer["ShowPassword"];
_databases = await DatabaseService.GetDatabasesAsync(); _databases = await DatabaseService.GetDatabasesAsync();
@ -150,7 +178,9 @@
_databaseName = "LocalDB"; _databaseName = "LocalDB";
} }
LoadDatabaseConfigComponent(); LoadDatabaseConfigComponent();
}
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
}
private void DatabaseChanged(ChangeEventArgs eventArgs) private void DatabaseChanged(ChangeEventArgs eventArgs)
{ {
@ -185,9 +215,9 @@
{ {
if (firstRender) if (firstRender)
{ {
// include JavaScript
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", "text/css", "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", "anonymous", ""); await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", "anonymous", "", "head");
} }
} }
@ -229,7 +259,8 @@
TenantName = TenantNames.Master, TenantName = TenantNames.Master,
IsNewTenant = true, IsNewTenant = true,
SiteName = Constants.DefaultSite, SiteName = Constants.DefaultSite,
Register = _register Register = _register,
SiteTemplate = _template
}; };
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);
@ -291,4 +322,9 @@
_showConnectionString = !_showConnectionString; _showConnectionString = !_showConnectionString;
} }
private void DismissModal()
{
_message = "";
StateHasChanged();
}
} }

View File

@ -195,7 +195,7 @@ else
if (_owner != "" && _module != "" && _template != "-") if (_owner != "" && _module != "" && _template != "-")
{ {
var template = _templates.FirstOrDefault(item => item.Name == _template); var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _module; _location = template.Location + _owner + ".Module." + _module;
} }
StateHasChanged(); StateHasChanged();

View File

@ -33,7 +33,16 @@
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required /> <input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
</div> </div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isenabled" HelpText="Is module 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> </form>
<Section Name="Information" ResourceKey="Information"> <Section Name="Information" ResourceKey="Information">
<div class="container"> <div class="container">
@ -199,6 +208,7 @@
private string _name; private string _name;
private string _description = ""; private string _description = "";
private string _categories; private string _categories;
private string _isenabled;
private string _moduledefinitionname = ""; private string _moduledefinitionname = "";
private string _version; private string _version;
private string _packagename = ""; private string _packagename = "";
@ -234,6 +244,7 @@
_name = moduleDefinition.Name; _name = moduleDefinition.Name;
_description = moduleDefinition.Description; _description = moduleDefinition.Description;
_categories = moduleDefinition.Categories; _categories = moduleDefinition.Categories;
_isenabled = moduleDefinition.IsEnabled.ToString();
_moduledefinitionname = moduleDefinition.ModuleDefinitionName; _moduledefinitionname = moduleDefinition.ModuleDefinitionName;
_version = moduleDefinition.Version; _version = moduleDefinition.Version;
_packagename = moduleDefinition.PackageName; _packagename = moduleDefinition.PackageName;
@ -297,6 +308,7 @@
{ {
moduledefinition.Categories = _categories; moduledefinition.Categories = _categories;
} }
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
moduledefinition.PermissionList = _permissionGrid.GetPermissionList(); moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition); await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition); await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);

View File

@ -43,6 +43,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@Localizer["InUse"]</th> <th>@Localizer["InUse"]</th>
<th>@SharedLocalizer["Expires"]</th> <th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -57,6 +58,16 @@ else
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td> <td>
@if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) @if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
{ {

View File

@ -44,12 +44,20 @@
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label> <Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required> <select id="page" class="form-select" @bind="@_pageId" required>
@foreach (Page p in PageState.Pages) @if (PageState.Page.UserId != null)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) <option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
{
foreach (Page p in PageState.Pages)
{ {
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option> if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
} }
} }
</select> </select>
</div> </div>
@ -90,37 +98,35 @@
</form> </form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings"; public override string Title => "Module Settings";
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private List<Theme> _themes; private List<ThemeControl> _containers = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private string _title;
private string _title; private string _containerType;
private string _containerType; private string _allPages = "false";
private string _allPages = "false"; private string _permissionNames = "";
private string _permissionNames = ""; private List<Permission> _permissions = null;
private List<Permission> _permissions = null; private string _pageId;
private string _pageId; private PermissionGrid _permissionGrid;
private PermissionGrid _permissionGrid; private Type _moduleSettingsType;
private Type _moduleSettingsType; private object _moduleSettings;
private object _moduleSettings; private string _moduleSettingsTitle = "Module Settings";
private string _moduleSettingsTitle = "Module Settings"; private RenderFragment ModuleSettingsComponent { get; set; }
private RenderFragment ModuleSettingsComponent { get; set; } private Type _containerSettingsType;
private Type _containerSettingsType; private object _containerSettings;
private object _containerSettings; private RenderFragment ContainerSettingsComponent { get; set; }
private RenderFragment ContainerSettingsComponent { get; set; } private string createdby;
private string createdby; private DateTime createdon;
private DateTime createdon; private string modifiedby;
private string modifiedby; private DateTime modifiedon;
private DateTime modifiedon;
protected override async Task OnInitializedAsync() protected override void OnInitialized()
{ {
_title = ModuleState.Title; _title = ModuleState.Title;
_themes = await ThemeService.GetThemesAsync(); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
_containerType = ModuleState.ContainerType; _containerType = ModuleState.ContainerType;
_allPages = ModuleState.AllPages.ToString(); _allPages = ModuleState.AllPages.ToString();
_permissions = ModuleState.PermissionList; _permissions = ModuleState.PermissionList;
@ -165,7 +171,7 @@
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); 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)) if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
{ {
_containerSettingsType = Type.GetType(theme.ContainerSettingsType); _containerSettingsType = Type.GetType(theme.ContainerSettingsType);

View File

@ -6,12 +6,11 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> {
<TabStrip Refresh="@_refresh"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabPanel Name="Settings" ResourceKey="Settings"> <TabStrip Refresh="@_refresh">
@if (_themeList != null) <TabPanel Name="Settings" ResourceKey="Settings">
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
@ -19,42 +18,67 @@
<input id="name" class="form-control" @bind="@_name" required /> <input id="name" class="form-control" @bind="@_name" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label> {
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required> <Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <div class="col-sm-9">
@foreach (Page page in PageState.Pages) <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
{ <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> @foreach (Page page in PageState.Pages)
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{ {
<option value="@(page.PageId)">@(page.Name)</option> if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
} }
</select> </select>
} </div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
}
</div>
</div>
}
else
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<div class="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<option value="@(_parent.PageId)">@(new string('-', _parent.Level * 2))@(_parent.Name)</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value=">>">@Localizer["AtEnd"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label> <Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -85,9 +109,24 @@
<input id="url" class="form-control" @bind="@_url" /> <input id="url" class="form-control" @bind="@_url" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div> </div>
<Section Name="Appearance" ResourceKey="Appearance"> <Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label> <Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
@ -95,16 +134,10 @@
<input id="title" class="form-control" @bind="@_title" /> <input id="title" class="form-control" @bind="@_title" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
<div class="col-sm-9">
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required> <select id="theme" class="form-select" @bind="@_themetype" required>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
@ -116,7 +149,6 @@
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label> <Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required> <select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
@ -124,90 +156,113 @@
</select> </select>
</div> </div>
</div> </div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label> <Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" /> <textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label> <Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required> <textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div> </div>
</div> </div>
</div> </div>
</Section> </Section>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel> </TabPanel>
} <TabPanel Name="Permissions" ResourceKey="Permissions">
</TabStrip> <div class="container">
<br /> <div class="row mb-1 align-items-center">
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> </div>
</form> </div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
}
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList; private bool _initialized = false;
private List<ThemeControl> _themes = new List<ThemeControl>(); private ElementReference form;
private List<ThemeControl> _containers = new List<ThemeControl>(); private bool validated = false;
private string _name; private List<ThemeControl> _themes = new List<ThemeControl>();
private string _title; private List<ThemeControl> _containers = new List<ThemeControl>();
private string _meta; private int _pageId;
private string _path = string.Empty; private string _name;
private string _parentid = "-1"; private string _parentid = "-1";
private string _insert = ">>"; private string _insert = ">>";
private List<Page> _children; private List<Page> _children;
private int _childid = -1; private int _childid = -1;
private string _isnavigation = "True"; private string _isnavigation = "True";
private string _isclickable = "True"; private string _isclickable = "True";
private string _url; private string _path = string.Empty;
private string _ispersonalizable = "False"; private string _url;
private string _themetype = string.Empty; private string _ispersonalizable = "False";
private string _containertype = string.Empty; private string _title;
private string _icon = string.Empty; private string _icon = string.Empty;
private string _permissions = null; private string _themetype = string.Empty;
private PermissionGrid _permissionGrid; private string _containertype = string.Empty;
private Type _themeSettingsType; private string _headcontent;
private object _themeSettings; private string _bodycontent;
private RenderFragment ThemeSettingsComponent { get; set; } private string _permissions = null;
private bool _refresh = false; private PermissionGrid _permissionGrid;
private ElementReference form; private Type _themeSettingsType;
private bool validated = false; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false;
protected Page _parent = null;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); if (PageState.QueryString.ContainsKey("id"))
_themes = ThemeService.GetThemeControls(_themeList); {
_themetype = PageState.Site.DefaultThemeType; _pageId = Int32.Parse(PageState.QueryString["id"]);
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _parent = await PageService.GetPageAsync(_pageId);
_containertype = PageState.Site.DefaultContainerType; if (_parent != null)
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); {
ThemeSettings(); _parentid = _parent.PageId.ToString();
} }
}
// if admin or user has edit access to parent page
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
{
_themetype = PageState.Site.DefaultThemeType;
_themes = ThemeService.GetThemeControls(PageState.Site.Themes, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings();
_initialized = true;
}
else
{
await logger.LogWarning("Error Loading Page {ParentId}", _parentid);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
}
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message); await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
} }
} }
@ -246,27 +301,10 @@
} }
} }
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = "-";
ThemeSettings();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
}
}
private void ThemeSettings() private void ThemeSettings()
{ {
_themeSettingsType = null; _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)) if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
{ {
_themeSettingsType = Type.GetType(theme.ThemeSettingsType); _themeSettingsType = Type.GetType(theme.ThemeSettingsType);
@ -292,12 +330,11 @@
Page page = null; Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
{ {
page = new Page(); page = new Page();
page.SiteId = PageState.Page.SiteId; page.SiteId = PageState.Page.SiteId;
page.Name = _name; page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
@ -365,34 +402,42 @@
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable)); page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
// appearance
page.Title = _title;
page.Icon = (_icon == null ? string.Empty : _icon);
page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{ {
page.ThemeType = string.Empty; page.ThemeType = string.Empty;
} }
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty; page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{ {
page.DefaultContainerType = string.Empty; page.DefaultContainerType = string.Empty;
} }
page.Icon = (_icon == null ? string.Empty : _icon);
// page content
page.HeadContent = _headcontent;
page.BodyContent = _bodycontent;
// permissions
page.PermissionList = _permissionGrid.GetPermissionList(); page.PermissionList = _permissionGrid.GetPermissionList();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
page = await PageService.AddPageAsync(page); page = await PageService.AddPageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page); await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp")) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); NavigationManager.NavigateTo(page.Path); // redirect to new page
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl(page.Path)); NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
} }
} }
else else
@ -415,9 +460,9 @@
private void Cancel() private void Cancel()
{ {
if (PageState.QueryString.ContainsKey("cp")) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{ {
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); NavigationManager.NavigateTo(PageState.ReturnUrl);
} }
else else
{ {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null) @if (PageState.Pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" /> <ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />

View File

@ -64,6 +64,12 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
<div class="col-sm-9">
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label> <Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -97,6 +103,7 @@
private string _maxlength = "0"; private string _maxlength = "0";
private string _defaultvalue = string.Empty; private string _defaultvalue = string.Empty;
private string _options = string.Empty; private string _options = string.Empty;
private string _validation = string.Empty;
private string _isrequired = "False"; private string _isrequired = "False";
private string _isprivate = "False"; private string _isprivate = "False";
private string createdby; private string createdby;
@ -126,6 +133,7 @@
_maxlength = profile.MaxLength.ToString(); _maxlength = profile.MaxLength.ToString();
_defaultvalue = profile.DefaultValue; _defaultvalue = profile.DefaultValue;
_options = profile.Options; _options = profile.Options;
_validation = profile.Validation;
_isrequired = profile.IsRequired.ToString(); _isrequired = profile.IsRequired.ToString();
_isprivate = profile.IsPrivate.ToString(); _isprivate = profile.IsPrivate.ToString();
createdby = profile.CreatedBy; createdby = profile.CreatedBy;
@ -169,6 +177,7 @@
profile.MaxLength = int.Parse(_maxlength); profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue; profile.DefaultValue = _defaultvalue;
profile.Options = _options; profile.Options = _options;
profile.Validation = _validation;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired)); profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate)); profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1) if (_profileid != -1)

View File

@ -76,8 +76,8 @@ else
} }
@code { @code {
private List<Page> _pages; private List<Page> _pages;
private List<Module> _modules; private List<Module> _modules;
private int _pagePage = 1; private int _pagePage = 1;
private int _pageModule = 1; private int _pageModule = 1;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -184,13 +184,6 @@ else
try try
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
if (!_modules.Exists (item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
await logger.LogInformation("Module Permanently Deleted {Module}", module); await logger.LogInformation("Module Permanently Deleted {Module}", module);
await Load(); await Load();
StateHasChanged(); StateHasChanged();
@ -210,16 +203,7 @@ else
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList()) foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
{ {
await PageModuleService.DeletePageModuleAsync(module.PageModuleId); await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// DeletePageModuleAsync does not update _modules so remove it.
_modules.Remove(module);
// check if there are any remaining module instances in the site
if (!_modules.Exists(item => item.ModuleId == module.ModuleId && item.PageModuleId != module.PageModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
} }
await logger.LogInformation("Modules Permanently Deleted"); await logger.LogInformation("Modules Permanently Deleted");
await Load(); await Load();
ModuleInstance.HideProgressIndicator(); ModuleInstance.HideProgressIndicator();

View File

@ -22,55 +22,6 @@
<input id="name" class="form-control" @bind="@_name" maxlength="200" required /> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label> <Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
@ -101,7 +52,80 @@
<input id="sitemap" class="form-control" @bind="@_sitemap" required disabled /> <input id="sitemap" class="form-control" @bind="@_sitemap" required disabled />
</div> </div>
</div> </div>
</div> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="version" HelpText="The site version (for site content migrations)" ResourceKey="Version">Version: </Label>
<div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" required disabled />
</div>
</div>
</div>
<br />
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
</div>
</Section>
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
<div class="col-sm-9">
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
<div class="col-sm-9">
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
</div>
</div>
</div>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -124,9 +148,9 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label> <Label Class="col-sm-3" For="smtpssl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" > <select id="smtpssl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
@ -162,7 +186,16 @@
</select> </select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="smtpenabled" HelpText="Specify if SMTP is enabled for this site" ResourceKey="SMTPEnabled">Enabled? </Label>
<div class="col-sm-9">
<select id="smtpenabled" class="form-select" @bind="@_smtpenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label> <Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" /> <input id="retention" class="form-control" @bind="@_retention" />
@ -306,222 +339,234 @@
} }
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = false;
private bool _initialized = false; private bool _initialized = false;
private List<Theme> _themeList; private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private string _name = string.Empty;
private string _name = string.Empty; private string _homepageid = "-";
private List<Alias> _aliases; private string _isdeleted;
private int _aliasid = -1; private string _sitemap = "";
private string _aliasname; private string _version = "";
private string _defaultalias; private int _logofileid = -1;
private string _runtime = ""; private FileManager _logofilemanager;
private string _prerender = ""; private int _faviconfileid = -1;
private int _logofileid = -1; private FileManager _faviconfilemanager;
private FileManager _logofilemanager; private string _themetype = "";
private int _faviconfileid = -1; private string _containertype = "";
private FileManager _faviconfilemanager; private string _admincontainertype = "";
private string _themetype = "-"; private string _headcontent = string.Empty;
private string _containertype = "-"; private string _bodycontent = string.Empty;
private string _admincontainertype = "-"; private string _smtphost = string.Empty;
private string _homepageid = "-"; private string _smtpport = string.Empty;
private string _sitemap = ""; private string _smtpssl = "False";
private string _smtphost = string.Empty; private string _smtpusername = string.Empty;
private string _smtpport = string.Empty; private string _smtppassword = string.Empty;
private string _smtpssl = "False"; private string _smtppasswordtype = "password";
private string _smtpusername = string.Empty; private string _togglesmtppassword = string.Empty;
private string _smtppassword = string.Empty; private string _smtpsender = string.Empty;
private string _smtppasswordtype = "password"; private string _smtprelay = "False";
private string _togglesmtppassword = string.Empty; private string _smtpenabled = "True";
private string _smtpsender = string.Empty; private string _retention = string.Empty;
private string _smtprelay = "False"; private string _pwaisenabled;
private string _retention = string.Empty; private int _pwaappiconfileid = -1;
private string _pwaisenabled; private FileManager _pwaappiconfilemanager;
private int _pwaappiconfileid = -1; private int _pwasplashiconfileid = -1;
private FileManager _pwaappiconfilemanager; private FileManager _pwasplashiconfilemanager;
private int _pwasplashiconfileid = -1; private List<Alias> _aliases;
private FileManager _pwasplashiconfilemanager; private int _aliasid = -1;
private string _tenant = string.Empty; private string _aliasname;
private string _database = string.Empty; private string _defaultalias;
private string _connectionstring = string.Empty; private string _runtime = "";
private string _createdby; private string _prerender = "";
private DateTime _createdon; private string _tenant = string.Empty;
private string _modifiedby; private string _database = string.Empty;
private DateTime _modifiedon; private string _connectionstring = string.Empty;
private string _deletedby; private string _createdby;
private DateTime? _deletedon; private DateTime _createdon;
private string _isdeleted; private string _modifiedby;
private DateTime _modifiedon;
private string _deletedby;
private DateTime? _deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null)
if (site != null) {
{ _name = site.Name;
_name = site.Name; if (site.HomePageId != null)
_runtime = site.Runtime; {
_prerender = site.RenderMode.Replace(_runtime, ""); _homepageid = site.HomePageId.Value.ToString();
_isdeleted = site.IsDeleted.ToString(); }
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml"; _isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
_version = site.Version;
await GetAliases(); // appearance
if (site.LogoFileId != null)
{
_logofileid = site.LogoFileId.Value;
}
if (site.LogoFileId != null) if (site.FaviconFileId != null)
{ {
_logofileid = site.LogoFileId.Value; _faviconfileid = site.FaviconFileId.Value;
} }
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_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;
if (site.FaviconFileId != null) // page content
{ _headcontent = site.HeadContent;
_faviconfileid = site.FaviconFileId.Value; _bodycontent = site.BodyContent;
}
_themes = ThemeService.GetThemeControls(_themeList); // PWA
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme; _pwaisenabled = site.PwaIsEnabled.ToString();
_containers = ThemeService.GetContainerControls(_themeList, _themetype); if (site.PwaAppIconFileId != null)
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; {
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
if (site.HomePageId != null) // SMTP
{ var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_homepageid = site.HomePageId.Value.ToString(); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
} _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
_pwaisenabled = site.PwaIsEnabled.ToString(); // aliases
if (site.PwaAppIconFileId != null) await GetAliases();
{
_pwaappiconfileid = site.PwaAppIconFileId.Value;
}
if (site.PwaSplashIconFileId != null)
{
_pwasplashiconfileid = site.PwaSplashIconFileId.Value;
}
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); // hosting model
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _runtime = site.Runtime;
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); _prerender = site.RenderMode.Replace(_runtime, "");
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) // database
{ if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
var tenants = await TenantService.GetTenantsAsync(); {
var _databases = await DatabaseService.GetDatabasesAsync(); var tenants = await TenantService.GetTenantsAsync();
var tenant = tenants.Find(item => item.TenantId == site.TenantId); var _databases = await DatabaseService.GetDatabasesAsync();
if (tenant != null) var tenant = tenants.Find(item => item.TenantId == site.TenantId);
{ if (tenant != null)
_tenant = tenant.Name; {
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name; _tenant = tenant.Name;
_connectionstring = tenant.DBConnectionString; _database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
} _connectionstring = tenant.DBConnectionString;
} }
}
_createdby = site.CreatedBy; // audit
_createdon = site.CreatedOn; _createdby = site.CreatedBy;
_modifiedby = site.ModifiedBy; _createdon = site.CreatedOn;
_modifiedon = site.ModifiedOn; _modifiedby = site.ModifiedBy;
_deletedby = site.DeletedBy; _modifiedon = site.ModifiedOn;
_deletedon = site.DeletedOn; _deletedby = site.DeletedBy;
_deletedon = site.DeletedOn;
_initialized = true; _initialized = true;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Loading Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
private async void ThemeChanged(ChangeEventArgs e) private async void ThemeChanged(ChangeEventArgs e)
{ {
try try
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
if (_themetype != "-") _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
{ _containertype = _containers.First().TypeName;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _admincontainertype = Constants.DefaultAdminContainer;
} StateHasChanged();
else }
{ catch (Exception ex)
_containers = new List<ThemeControl>(); {
} await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
_containertype = "-"; AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
_admincontainertype = Constants.DefaultAdminContainer; }
StateHasChanged(); }
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
}
}
private async Task SaveSite() private async Task SaveSite()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
try try
{ {
if (_name != string.Empty && _themetype != "-" && _containertype != "-") if (_name != string.Empty && _themetype != "-" && _containertype != "-")
{ {
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId); var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
bool refresh = false; bool refresh = false;
bool reload = false; bool reload = false;
site.Name = _name; site.Name = _name;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
{ site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null; // appearance
var logofileid = _logofilemanager.GetFileId(); site.LogoFileId = null;
if (logofileid != -1) var logofileid = _logofilemanager.GetFileId();
{ if (logofileid != -1)
site.LogoFileId = logofileid; {
} site.LogoFileId = logofileid;
int? faviconFieldId = _faviconfilemanager.GetFileId(); }
if (faviconFieldId == -1) faviconFieldId = null; int? faviconFieldId = _faviconfilemanager.GetFileId();
if (site.FaviconFileId != faviconFieldId) if (faviconFieldId == -1) faviconFieldId = null;
{ if (site.FaviconFileId != faviconFieldId)
site.FaviconFileId = faviconFieldId; {
reload = true; // needs to be reloaded on server site.FaviconFileId = faviconFieldId;
} reload = true; // needs to be reloaded on server
if (site.DefaultThemeType != _themetype) }
{ if (site.DefaultThemeType != _themetype)
site.DefaultThemeType = _themetype; {
refresh = true; // needs to be refreshed on client site.DefaultThemeType = _themetype;
} refresh = true; // needs to be refreshed on client
if (site.DefaultContainerType != _containertype) }
{ if (site.DefaultContainerType != _containertype)
site.DefaultContainerType = _containertype; {
refresh = true; // needs to be refreshed on client site.DefaultContainerType = _containertype;
} refresh = true; // needs to be refreshed on client
site.AdminContainerType = _admincontainertype; }
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.AdminContainerType = _admincontainertype;
// page content
if (site.HeadContent != _headcontent)
{
site.HeadContent = _headcontent;
reload = true;
}
if (site.BodyContent != _bodycontent)
{
site.BodyContent = _bodycontent;
reload = true;
}
// PWA
if (site.PwaIsEnabled.ToString() != _pwaisenabled) if (site.PwaIsEnabled.ToString() != _pwaisenabled)
{ {
site.PwaIsEnabled = Boolean.Parse(_pwaisenabled); site.PwaIsEnabled = Boolean.Parse(_pwaisenabled);
@ -542,8 +587,20 @@
reload = true; // needs to be reloaded on server reload = true; // needs to be reloaded on server
} }
// hosting model
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
if (site.Runtime != _runtime || site.RenderMode != _runtime + _prerender)
{
site.Runtime = _runtime;
site.RenderMode = _runtime + _prerender;
reload = true; // needs to be reloaded on server
}
}
site = await SiteService.UpdateSiteAsync(site); site = await SiteService.UpdateSiteAsync(site);
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
@ -552,7 +609,8 @@
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
@ -564,8 +622,8 @@
else else
{ {
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth"); await ScrollToPageTop();
} }
} }
} }
else else
@ -633,9 +691,8 @@
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
var interop = new Interop(JSRuntime); await ScrollToPageTop();
await interop.ScrollTo(0, 0, "smooth"); }
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Testing SMTP Configuration"); await logger.LogError(ex, "Error Testing SMTP Configuration");

View File

@ -142,7 +142,7 @@
if (_owner != "" && _theme != "" && _template != "-") if (_owner != "" && _theme != "" && _template != "-")
{ {
var template = _templates.FirstOrDefault(item => item.Name == _template); var template = _templates.FirstOrDefault(item => item.Name == _template);
_location = template.Location + _owner + "." + _theme; _location = template.Location + _owner + ".Theme." + _theme;
} }
StateHasChanged(); StateHasChanged();

View File

@ -0,0 +1,162 @@
@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" />
</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.Name = _name;
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 style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@Localizer["Enabled"]</th>
<th>@SharedLocalizer["Expires"]</th> <th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <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> <td>
@if (context.AssemblyName != Constants.ClientId) @if (context.AssemblyName != Constants.ClientId)
{ {
@ -36,6 +37,16 @@ else
</td> </td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@if (context.IsEnabled)
{
<span>@SharedLocalizer["Yes"]</span>
}
else
{
<span>@SharedLocalizer["No"]</span>
}
</td>
<td> <td>
@((MarkupString)PurchaseLink(context.PackageName)) @((MarkupString)PurchaseLink(context.PackageName))
</td> </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

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.UserProfile @namespace Oqtane.Modules.Admin.UserProfile
@using System.Text.RegularExpressions;
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@ -227,140 +228,140 @@ else
<br /><br /> <br /><br />
@code { @code {
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private bool allowtwofactor = false; private bool allowtwofactor = false;
private string twofactor = "False"; private string twofactor = "False";
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
private FileManager filemanager; private FileManager filemanager;
private int folderid = -1; private int folderid = -1;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
private string filter = "to"; private string filter = "to";
private List<Notification> notifications; private List<Notification> notifications;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{ {
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
} }
if (PageState.User != null) if (PageState.User != null)
{ {
username = PageState.User.Username; username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString(); twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email; email = PageState.User.Email;
displayname = PageState.User.DisplayName; displayname = PageState.User.DisplayName;
// get user folder // get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null) if (folder != null)
{ {
folderid = folder.FolderId; folderid = folder.FolderId;
} }
if (PageState.User.PhotoFileId != null) if (PageState.User.PhotoFileId != null)
{ {
photofileid = PageState.User.PhotoFileId.Value; photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
await LoadNotificationsAsync(); await LoadNotificationsAsync();
} }
else else
{ {
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning); AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
} }
} }
private async Task LoadNotificationsAsync() private async Task LoadNotificationsAsync()
{ {
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
} }
return value; return value;
} }
private async Task Save() private async Task Save()
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty && ValidateProfiles())
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = PageState.User; var user = PageState.User;
user.Username = username; user.Username = username;
user.Password = _password; user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor); user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email; user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname); user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId(); user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
} }
if (user.PhotoFileId != null) if (user.PhotoFileId != null)
{ {
photofileid = user.PhotoFileId.Value; photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved"); await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged(); StateHasChanged();
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
@ -370,6 +371,8 @@ else
{ {
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
} }
await ScrollToPageTop();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -389,10 +392,15 @@ else
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{ {
valid = false; valid = false;
} }
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
} }
} }
return valid; return valid;

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users @namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@ -95,8 +96,8 @@
@code { @code {
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
@ -121,15 +122,15 @@
} }
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
} }
return value; return value;
} }
private async Task SaveUser() private async Task SaveUser()
{ {
@ -195,9 +196,17 @@
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
} }
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
valid = false; if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
} }
} }
return valid; return valid;

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Users @namespace Oqtane.Modules.Admin.Users
@using System.Text.RegularExpressions;
@inherits ModuleBase @inherits ModuleBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@ -148,124 +149,124 @@ else
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo> <AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
@code { @code {
private int userid; private int userid;
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
private FileManager filemanager; private FileManager filemanager;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private string isdeleted; private string isdeleted;
private string lastlogin; private string lastlogin;
private string lastipaddress; private string lastipaddress;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
private string createdby; private string createdby;
private DateTime createdon; private DateTime createdon;
private string modifiedby; private string modifiedby;
private DateTime modifiedon; private DateTime modifiedon;
private string deletedby; private string deletedby;
private DateTime? deletedon; private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{ {
if (PageState.QueryString.ContainsKey("id")) if (PageState.QueryString.ContainsKey("id"))
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]); userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null) if (user != null)
{ {
username = user.Username; username = user.Username;
email = user.Email; email = user.Email;
displayname = user.DisplayName; displayname = user.DisplayName;
if (user.PhotoFileId != null) if (user.PhotoFileId != null)
{ {
photofileid = user.PhotoFileId.Value; photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
isdeleted = user.IsDeleted.ToString(); isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress; lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId); settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy; createdby = user.CreatedBy;
createdon = user.CreatedOn; createdon = user.CreatedOn;
modifiedby = user.ModifiedBy; modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn; modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy; deletedby = user.DeletedBy;
deletedon = user.DeletedOn; deletedon = user.DeletedOn;
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
} }
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
} }
return value; return value;
} }
private async Task SaveUser() private async Task SaveUser()
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty && ValidateProfiles())
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId; user.SiteId = PageState.Site.SiteId;
user.Username = username; user.Username = username;
user.Password = _password; user.Password = _password;
user.Email = email; user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null; user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId(); user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
} }
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, user.UserId); await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user); await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
} }
} }
else else
{ {
@ -293,9 +294,17 @@ else
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
} }
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
valid = false; if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
{
valid = false;
}
if (valid == true && !string.IsNullOrEmpty(profile.Validation))
{
Regex regex = new Regex(profile.Validation);
valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success;
}
} }
} }
return valid; return valid;

View File

@ -53,7 +53,6 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" }
}; };

View File

@ -15,11 +15,6 @@
} }
@code { @code {
public override List<Resource> Resources => new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
};
private string content = ""; private string content = "";
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText namespace Oqtane.Modules.HtmlText
{ {
@ -13,7 +15,11 @@ namespace Oqtane.Modules.HtmlText
Version = "1.0.1", Version = "1.0.1",
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server", ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
ReleaseVersions = "1.0.0,1.0.1", ReleaseVersions = "1.0.0,1.0.1",
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client" SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
}
}; };
} }
} }

View File

@ -9,6 +9,7 @@ using Oqtane.UI;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using System.Linq; using System.Linq;
using Oqtane.Themes;
namespace Oqtane.Modules namespace Oqtane.Modules
{ {
@ -70,17 +71,33 @@ namespace Oqtane.Modules
{ {
if (firstRender) if (firstRender)
{ {
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script)) List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ModuleBase))
{
if (PageState.Page.Resources != null)
{
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // modulecontrolbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null &&resources.Any())
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0; var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module }); scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
} }
else else
{ {
@ -104,6 +121,7 @@ namespace Oqtane.Modules
} }
// url methods // url methods
public string NavigateUrl() public string NavigateUrl()
{ {
return NavigateUrl(PageState.Page.Path); return NavigateUrl(PageState.Page.Path);
@ -273,6 +291,33 @@ namespace Oqtane.Modules
SiteState.Properties.ModuleVisibility = obj; SiteState.Properties.ModuleVisibility = obj;
} }
public void SetPageTitle(string title)
{
SiteState.Properties.PageTitle = title;
}
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content)
{
SiteState.AppendHeadContent(content);
}
public void AddScript(Resource resource)
{
resource.ResourceType = ResourceType.Script;
if (Resources == null) Resources = new List<Resource>();
if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content)))
{
Resources.Add(resource);
}
}
public async Task ScrollToPageTop()
{
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
// logging methods // logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{ {

View File

@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
<Description>Modular Application Framework for Blazor and MAUI</Description> <Description>CMS and Application Framework for Blazor and .NET MAUI</Description>
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
@ -22,13 +22,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -16,7 +16,6 @@ using Microsoft.JSInterop;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
namespace Oqtane.Client namespace Oqtane.Client

View File

@ -124,7 +124,7 @@
<value>Database:</value> <value>Database:</value>
</data> </data>
<data name="ApplicationAdmin" xml:space="preserve"> <data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value> <value>Application Administration</value>
</data> </data>
<data name="InstallNow" xml:space="preserve"> <data name="InstallNow" xml:space="preserve">
<value>Install Now</value> <value>Install Now</value>
@ -180,4 +180,7 @@
<data name="EnterConnectionString" xml:space="preserve"> <data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value> <value>Enter Connection String</value>
</data> </data>
<data name="Template" xml:space="preserve">
<value>Select a site template</value>
</data>
</root> </root>

View File

@ -219,4 +219,10 @@
<data name="Message.DuplicateName" xml:space="preserve"> <data name="Message.DuplicateName" xml:space="preserve">
<value>A Module With The Name Specified Already Exists</value> <value>A Module With The Name Specified Already Exists</value>
</data> </data>
<data name="IsEnabled.HelpText" xml:space="preserve">
<value>Is module enabled for this site?</value>
</data>
<data name="IsEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
</root> </root>

View File

@ -145,7 +145,7 @@
<value>Delete Module</value> <value>Delete Module</value>
</data> </data>
<data name="InUse" xml:space="preserve"> <data name="InUse" xml:space="preserve">
<value>In Use</value> <value>In Use?</value>
</data> </data>
<data name="EditModule.Text" xml:space="preserve"> <data name="EditModule.Text" xml:space="preserve">
<value>Edit</value> <value>Edit</value>
@ -153,4 +153,7 @@
<data name="Modules" xml:space="preserve"> <data name="Modules" xml:space="preserve">
<value>Modules</value> <value>Modules</value>
</data> </data>
<data name="Enabled" xml:space="preserve">
<value>Enabled?</value>
</data>
</root> </root>

View File

@ -150,8 +150,8 @@
<data name="Container.Select" xml:space="preserve"> <data name="Container.Select" xml:space="preserve">
<value>Select Container</value> <value>Select Container</value>
</data> </data>
<data name="Error.Page.Initialize" xml:space="preserve"> <data name="Error.Page.Load" xml:space="preserve">
<value>Error Initializing Page</value> <value>Error Loading Page</value>
</data> </data>
<data name="Error.ChildPage.Load" xml:space="preserve"> <data name="Error.ChildPage.Load" xml:space="preserve">
<value>Error Loading Child Pages For Parent</value> <value>Error Loading Child Pages For Parent</value>
@ -228,13 +228,22 @@
<data name="Appearance.Name" xml:space="preserve"> <data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value> <value>Appearance</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve"> <data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value> <value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data> </data>
<data name="Meta.Text" xml:space="preserve"> <data name="HeadContent.Text" xml:space="preserve">
<value>Meta:</value> <value>Head Content:</value>
</data> </data>
<data name="Message.Page.Reserved" xml:space="preserve"> <data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value> <value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data> </data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
</root> </root>

View File

@ -264,13 +264,22 @@
<data name="Clickable.Text" xml:space="preserve"> <data name="Clickable.Text" xml:space="preserve">
<value>Clickable?</value> <value>Clickable?</value>
</data> </data>
<data name="Meta.HelpText" xml:space="preserve"> <data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter meta tags (in exactly the form you want them to be included in the page output).</value> <value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data> </data>
<data name="Meta.Text" xml:space="preserve"> <data name="HeadContent.Text" xml:space="preserve">
<value>Meta:</value> <value>Head Content:</value>
</data> </data>
<data name="Message.Page.Reserved" xml:space="preserve"> <data name="Message.Page.Reserved" xml:space="preserve">
<value>The page name {0} is reserved. Please enter a different name for your page.</value> <value>The page name {0} is reserved. Please enter a different name for your page.</value>
</data> </data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
</root> </root>

View File

@ -183,4 +183,10 @@
<data name="Private.Text" xml:space="preserve"> <data name="Private.Text" xml:space="preserve">
<value>Private? </value> <value>Private? </value>
</data> </data>
<data name="Validation.HelpText" xml:space="preserve">
<value>Optionally provide a regular expression (RegExp) for validating the value entered</value>
</data>
<data name="Validation.Text" xml:space="preserve">
<value>Validation:</value>
</data>
</root> </root>

View File

@ -175,7 +175,7 @@
<value>Specify a logo for the site</value> <value>Specify a logo for the site</value>
</data> </data>
<data name="FavoriteIcon.HelpText" xml:space="preserve"> <data name="FavoriteIcon.HelpText" xml:space="preserve">
<value>Specify a Favicon</value> <value>Specify a Favicon. The format for the image must be 16×16, 32×32, 48×48, or 64×64 pixels in size, and 8-bit, 24-bit, or 32-bit in color depth. The format of the image must be ICO, PNG, or GIF.</value>
</data> </data>
<data name="DefaultTheme.HelpText" xml:space="preserve"> <data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the sites default theme</value> <value>Select the sites default theme</value>
@ -351,4 +351,34 @@
<data name="SiteMap.Text" xml:space="preserve"> <data name="SiteMap.Text" xml:space="preserve">
<value>Site Map:</value> <value>Site Map:</value>
</data> </data>
<data name="Appearance.Heading" xml:space="preserve">
<value>Appearance</value>
</data>
<data name="HeadContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
</data>
<data name="HeadContent.Text" xml:space="preserve">
<value>Head Content:</value>
</data>
<data name="BodyContent.HelpText" xml:space="preserve">
<value>Optionally enter content to be included in the page body (ie. script tags)</value>
</data>
<data name="BodyContent.Text" xml:space="preserve">
<value>Body Content:</value>
</data>
<data name="PageContent.Heading" xml:space="preserve">
<value>Page Content</value>
</data>
<data name="SMTPEnabled.HelpText" xml:space="preserve">
<value>Specify if SMTP is enabled for this site</value>
</data>
<data name="SMTPEnabled.Text" xml:space="preserve">
<value>Enabled?</value>
</data>
<data name="Version.HelpText" xml:space="preserve">
<value>The site version (for site content migrations)</value>
</data>
<data name="Version.Text" xml:space="preserve">
<value>Version:</value>
</data>
</root> </root>

View File

@ -168,4 +168,13 @@
<data name="PackageName.Text" xml:space="preserve"> <data name="PackageName.Text" xml:space="preserve">
<value>Package Name:</value> <value>Package Name:</value>
</data> </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> </root>

View File

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

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -29,9 +30,9 @@ namespace Oqtane.Services
public async Task<List<File>> GetFilesAsync(int siteId, string folderPath) public async Task<List<File>> GetFilesAsync(int siteId, string folderPath)
{ {
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar))) if (!(string.IsNullOrEmpty(folderPath) || folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{ {
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString()); folderPath = Utilities.UrlCombine(folderPath) + "/";
} }
var path = WebUtility.UrlEncode(folderPath); var path = WebUtility.UrlEncode(folderPath);

View File

@ -23,14 +23,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<Page> GetPageAsync(int pageId); Task<Page> GetPageAsync(int pageId);
/// <summary>
/// Returns a specific page personalized for the given user
/// </summary>
/// <param name="pageId"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task<Page> GetPageAsync(int pageId, int userId);
/// <summary> /// <summary>
/// Returns a specific page by its defined path /// Returns a specific page by its defined path
/// </summary> /// </summary>

View File

@ -16,6 +16,22 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<List<Theme>> GetThemesAsync(); 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 theme <see cref="ThemeControl"/>s containing a specific theme control type
/// </summary>
/// <param name="themes"></param>
/// <param name="themeControlType"></param>
/// <returns></returns>
Theme GetTheme(List<Theme> themes, string themeControlType);
/// <summary> /// <summary>
/// Returns a list of <see cref="ThemeControl"/>s from the given themes /// Returns a list of <see cref="ThemeControl"/>s from the given themes
/// </summary> /// </summary>
@ -24,20 +40,27 @@ namespace Oqtane.Services
List<ThemeControl> GetThemeControls(List<Theme> themes); List<ThemeControl> GetThemeControls(List<Theme> themes);
/// <summary> /// <summary>
/// Returns a list of layouts (<see cref="ThemeControl"/>) from the given themes with a matching theme name /// Returns a list of <see cref="ThemeControl"/>s for a theme containing a specific theme control type
/// </summary> /// </summary>
/// <param name="themes"></param> /// <param name="themes"></param>
/// <param name="themeName"></param> /// <param name="themeControlType"></param>
/// <returns></returns> /// <returns></returns>
List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName); List<ThemeControl> GetThemeControls(List<Theme> themes, string themeControlType);
/// <summary> /// <summary>
/// Returns a list of containers (<see cref="ThemeControl"/>) from the given themes with a matching theme name /// Returns a list of containers (<see cref="ThemeControl"/>) for a theme containing a specific theme control type
/// </summary> /// </summary>
/// <param name="themes"></param> /// <param name="themes"></param>
/// <param name="themeName"></param> /// <param name="themeControlType"></param>
/// <returns></returns> /// <returns></returns>
List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName); List<ThemeControl> GetContainerControls(List<Theme> themes, string themeControlType);
/// <summary>
/// Updates a existing theem
/// </summary>
/// <param name="theme"></param>
/// <returns></returns>
Task UpdateThemeAsync(Theme theme);
/// <summary> /// <summary>
/// Deletes a theme /// Deletes a theme
@ -58,5 +81,14 @@ namespace Oqtane.Services
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<List<Template>> GetThemeTemplatesAsync(); Task<List<Template>> GetThemeTemplatesAsync();
/// <summary>
/// Returns a list of layouts (<see cref="ThemeControl"/>) from the given themes with a matching theme name
/// </summary>
/// <param name="themes"></param>
/// <param name="themeName"></param>
/// <returns></returns>
List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName);
} }
} }

View File

@ -25,11 +25,6 @@ namespace Oqtane.Services
return await GetJsonAsync<Page>($"{Apiurl}/{pageId}"); return await GetJsonAsync<Page>($"{Apiurl}/{pageId}");
} }
public async Task<Page> GetPageAsync(int pageId, int userId)
{
return await GetJsonAsync<Page>($"{Apiurl}/{pageId}?userid={userId}");
}
public async Task<Page> GetPageAsync(string path, int siteId) public async Task<Page> GetPageAsync(string path, int siteId)
{ {
try try

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Services namespace Oqtane.Services
{ {
@ -20,22 +21,35 @@ namespace Oqtane.Services
List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl); List<Theme> themes = await GetJsonAsync<List<Theme>>(ApiUrl);
return themes.OrderBy(item => item.Name).ToList(); 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 Theme GetTheme(List<Theme> themes, string themeControlType)
{
return themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeControlType));
}
public List<ThemeControl> GetThemeControls(List<Theme> themes) public List<ThemeControl> GetThemeControls(List<Theme> themes)
{ {
return themes.SelectMany(item => item.Themes).ToList(); return themes.SelectMany(item => item.Themes).OrderBy(item => item.Name).ToList();
} }
//[Obsolete("This method is deprecated.", false)] public List<ThemeControl> GetThemeControls(List<Theme> themes, string themeControlType)
public List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName)
{ {
return null; return GetTheme(themes, themeControlType)?.Themes.OrderBy(item => item.Name).ToList();
} }
public List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName)
public List<ThemeControl> GetContainerControls(List<Theme> themes, string themeControlType)
{ {
return themes.Where(item => Utilities.GetTypeName(themeName).StartsWith(Utilities.GetTypeName(item.ThemeName))) return GetTheme(themes, themeControlType)?.Containers.OrderBy(item => item.Name).ToList();
.SelectMany(item => item.Containers).ToList(); }
public async Task UpdateThemeAsync(Theme theme)
{
await PutJsonAsync($"{ApiUrl}/{theme.ThemeId}", theme);
} }
public async Task DeleteThemeAsync(string themeName) public async Task DeleteThemeAsync(string themeName)
@ -53,5 +67,11 @@ namespace Oqtane.Services
List<Template> templates = await GetJsonAsync<List<Template>>($"{ApiUrl}/templates"); List<Template> templates = await GetJsonAsync<List<Template>>($"{ApiUrl}/templates");
return templates; return templates;
} }
//[Obsolete("This method is deprecated.", false)]
public List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName)
{
return null;
}
} }
} }

View File

@ -31,9 +31,9 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/css/bootstrap.min.css", Integrity = "sha512-XWTTruHZEYJsxV3W/lSXG1n3Q39YIWOstqvmFsdNEEQfHoZ6vm6E9GK2OrF6DSJSpIbRbi+Nn0WDPID9O7xB2Q==", CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css", Integrity = "sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" } new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
}; };
} }

View File

@ -2,12 +2,9 @@
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ContainerBase @inherits ContainerBase
@attribute [OqtaneIgnore] @attribute [OqtaneIgnore]
@inject SiteState SiteState
<span class="app-moduletitle"> <span class="app-moduletitle">
<a id="@ModuleState.PageModuleId.ToString()"> @((MarkupString)title)
@((MarkupString)title)
</a>
</span> </span>
@code { @code {

View File

@ -1,3 +1,4 @@
@using System.Net
@namespace Oqtane.Themes.Controls @namespace Oqtane.Themes.Controls
@inherits ThemeControlBase @inherits ThemeControlBase
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@ -55,7 +56,7 @@
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
} }
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
@ -64,7 +65,10 @@
</div> </div>
<div class="row d-flex mb-2"> <div class="row d-flex mb-2">
<div class="col d-flex justify-content-between"> <div class="col d-flex justify-content-between">
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button> @if (PageState.Page.UserId == null)
{
<button type="button" class="btn btn-secondary col me-1" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Add"))>@SharedLocalizer["Add"]</button>
}
<button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button> <button type="button" class="btn btn-secondary col" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Edit"))>@SharedLocalizer["Edit"]</button>
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div> </div>
@ -144,7 +148,7 @@
} }
@foreach (var moduledefinition in _moduleDefinitions) @foreach (var moduledefinition in _moduleDefinitions)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList)) if (moduledefinition.IsEnabled && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.PermissionList))
{ {
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString())) if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{ {
@ -223,270 +227,274 @@
} }
@code{ @code{
private bool _canViewAdminDashboard = false; private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false; private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>(); private List<string> _categories = new List<string>();
private List<ModuleDefinition> _allModuleDefinitions; private List<ModuleDefinition> _allModuleDefinitions;
private List<ModuleDefinition> _moduleDefinitions; private List<ModuleDefinition> _moduleDefinitions;
private List<Page> _pages = new List<Page>(); private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>(); private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _category = "Common"; private string _category = "Common";
protected string PageId { get; private set; } = "-"; protected string PageId { get; private set; } = "-";
protected string ModuleId { get; private set; } = "-"; protected string ModuleId { get; private set; } = "-";
protected string ModuleType { get; private set; } = "new"; protected string ModuleType { get; private set; } = "new";
protected string ModuleDefinitionName { get; private set; } = "-"; protected string ModuleDefinitionName { get; private set; } = "-";
protected string Category protected string Category
{ {
get => _category; get => _category;
private set private set
{ {
if (_category != value) if (_category != value)
{ {
_category = value; _category = value;
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList(); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
ModuleDefinitionName = "-"; ModuleDefinitionName = "-";
Message = ""; Message = "";
StateHasChanged(); StateHasChanged();
_ = UpdateSettingsAsync(); _ = UpdateSettingsAsync();
} }
} }
} }
protected string Pane protected string Pane
{ {
get => _pane; get => _pane;
private set private set
{ {
if (_pane != value) if (_pane != value)
{ {
_pane = value; _pane = value;
_ = UpdateSettingsAsync(); _ = UpdateSettingsAsync();
} }
} }
} }
protected string Title { get; private set; } = ""; protected string Title { get; private set; } = "";
protected string ContainerType { get; private set; } = ""; protected string ContainerType { get; private set; } = "";
protected string Visibility { get; private set; } = "view"; protected string Visibility { get; private set; } = "view";
protected string Message { get; private set; } = ""; protected string Message { get; private set; } = "";
[Parameter] [Parameter]
public string ButtonClass { get; set; } = "btn-outline-secondary"; public string ButtonClass { get; set; } = "btn-outline-secondary";
[Parameter] [Parameter]
public string ContainerClass { get; set; } = "offcanvas offcanvas-end"; public string ContainerClass { get; set; } = "offcanvas offcanvas-end";
[Parameter] [Parameter]
public string HeaderClass { get; set; } = "offcanvas-header"; public string HeaderClass { get; set; } = "offcanvas-header";
[Parameter] [Parameter]
public string BodyClass { get; set; } = "offcanvas-body overflow-auto"; public string BodyClass { get; set; } = "offcanvas-body overflow-auto";
[Parameter] [Parameter]
public bool ShowLanguageSwitcher { get; set; } = true; public bool ShowLanguageSwitcher { get; set; } = true;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard(); _canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false; _showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
_pages?.Clear(); _pages?.Clear();
foreach (Page p in PageState.Pages) foreach (Page p in PageState.Pages)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{ {
_pages.Add(p); _pages.Add(p);
} }
} }
await LoadSettingsAsync(); await LoadSettingsAsync();
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType); _containers = ThemeService.GetContainerControls(themes, PageState.Page.ThemeType);
ContainerType = PageState.Site.DefaultContainerType; ContainerType = PageState.Site.DefaultContainerType;
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList(); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(Category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList(); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
} }
else else
{ {
foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId)) foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList))
{ {
_showEditMode = true; _showEditMode = true;
break; break;
} }
} }
} }
} }
private bool CanViewAdminDashboard() private bool CanViewAdminDashboard()
{ {
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null) if (admin != null)
{ {
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId)) foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{ {
return true; return true;
} }
} }
} }
return false; return false;
} }
private void CategoryChanged(ChangeEventArgs e) private void CategoryChanged(ChangeEventArgs e)
{ {
Category = (string)e.Value; Category = (string)e.Value;
} }
private void ModuleChanged(ChangeEventArgs e) private void ModuleChanged(ChangeEventArgs e)
{ {
ModuleDefinitionName = (string)e.Value; ModuleDefinitionName = (string)e.Value;
if (ModuleDefinitionName != "-") if (ModuleDefinitionName != "-")
{ {
var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName); var moduleDefinition = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName);
Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>"; Message = "<div class=\"alert alert-info mt-2 text-center\" role=\"alert\">" + moduleDefinition.Description + "</div>";
} }
else else
{ {
Message = ""; Message = "";
} }
StateHasChanged(); StateHasChanged();
} }
private void PageChanged(ChangeEventArgs e) private void PageChanged(ChangeEventArgs e)
{ {
PageId = (string)e.Value; PageId = (string)e.Value;
if (PageId != "-") if (PageId != "-")
{ {
_modules = PageState.Modules _modules = PageState.Modules
.Where(module => module.PageId == int.Parse(PageId) && .Where(module => module.PageId == int.Parse(PageId) &&
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList)) UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
.ToList(); .ToList();
} }
ModuleId = "-"; ModuleId = "-";
StateHasChanged(); StateHasChanged();
} }
private async Task AddModule() private async Task AddModule()
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
{ {
if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-")) if ((ModuleType == "new" && ModuleDefinitionName != "-") || (ModuleType != "new" && ModuleId != "-"))
{ {
if (ModuleType == "new") if (ModuleType == "new")
{ {
Module module = new Module(); Module module = new Module();
module.SiteId = PageState.Site.SiteId; module.SiteId = PageState.Site.SiteId;
module.PageId = PageState.Page.PageId; module.PageId = PageState.Page.PageId;
module.ModuleDefinitionName = ModuleDefinitionName; module.ModuleDefinitionName = ModuleDefinitionName;
module.AllPages = false; module.AllPages = false;
var permissions = new List<Permission>(); var permissions = new List<Permission>();
if (Visibility == "view") if (Visibility == "view")
{ {
// set module view permissions to page view permissions // set module view permissions to page view permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.View);
} }
else else
{ {
// set module view permissions to page edit permissions // set module view permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.View, PermissionNames.Edit);
} }
// set module edit permissions to page edit permissions // set module edit permissions to page edit permissions
permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit); permissions = SetPermissions(permissions, module.SiteId, PermissionNames.Edit, PermissionNames.Edit);
module.PermissionList = permissions; module.PermissionList = permissions;
module = await ModuleService.AddModuleAsync(module); module = await ModuleService.AddModuleAsync(module);
ModuleId = module.ModuleId.ToString(); ModuleId = module.ModuleId.ToString();
} }
var pageModule = new PageModule var pageModule = new PageModule
{ {
PageId = PageState.Page.PageId, PageId = PageState.Page.PageId,
ModuleId = int.Parse(ModuleId), ModuleId = int.Parse(ModuleId),
Title = Title Title = Title
}; };
if (pageModule.Title == "") if (pageModule.Title == "")
{ {
if (ModuleType == "new") if (ModuleType == "new")
{ {
pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name; pageModule.Title = _moduleDefinitions.FirstOrDefault(item => item.ModuleDefinitionName == ModuleDefinitionName)?.Name;
} }
else else
{ {
pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title; pageModule.Title = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(ModuleId))?.Title;
} }
} }
pageModule.Pane = Pane; pageModule.Pane = Pane;
pageModule.Order = int.MaxValue; pageModule.Order = int.MaxValue;
pageModule.ContainerType = ContainerType; pageModule.ContainerType = ContainerType;
if (pageModule.ContainerType == PageState.Site.DefaultContainerType) if (pageModule.ContainerType == PageState.Site.DefaultContainerType)
{ {
pageModule.ContainerType = ""; pageModule.ContainerType = "";
} }
await PageModuleService.AddPageModuleAsync(pageModule); await PageModuleService.AddPageModuleAsync(pageModule);
await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane);
Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>"; Message = $"<div class=\"alert alert-success mt-2 text-center\" role=\"alert\">{Localizer["Success.Page.ModuleAdd"]}</div>";
Title = ""; Title = "";
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>"; Message = $"<div class=\"alert alert-warning mt-2 text-center\" role=\"alert\">{Localizer["Message.Require.ModuleSelect"]}</div>";
} }
} }
else else
{ {
Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>"; Message = $"<div class=\"alert alert-error mt-2 text-center\" role=\"alert\">{Localizer["Error.Authorize.No"]}</div>";
} }
} }
private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission) private List<Permission> SetPermissions(List<Permission> permissions, int siteId, string modulePermission, string pagePermission)
{ {
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission)) foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
{ {
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized }); permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
} }
return permissions; return permissions;
} }
private async Task ToggleEditMode(bool EditMode) private async Task ToggleEditMode(bool EditMode)
{ {
if (_showEditMode) if (_showEditMode)
{ {
if (EditMode) if (EditMode)
{ {
PageState.EditMode = false; PageState.EditMode = false;
} }
else else
{ {
PageState.EditMode = true; PageState.EditMode = true;
} }
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false"))); // preserve other querystring parameters
if (PageState.QueryString.ContainsKey("edit")) PageState.QueryString.Remove("edit");
PageState.QueryString.Add("edit", PageState.EditMode.ToString().ToLower());
var url = PageState.Route.AbsolutePath + Utilities.CreateQueryString(PageState.QueryString);
NavigationManager.NavigateTo(url);
} }
else else
{ {
if (PageState.Page.IsPersonalizable && PageState.User != null) if (PageState.Page.IsPersonalizable && PageState.User != null)
{ {
await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId); var page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
PageState.EditMode = true; PageState.EditMode = true;
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false"))); NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
} }
} }
} }
@ -501,7 +509,7 @@
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null) if (module != null)
{ {
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); NavigationManager.NavigateTo(EditUrl("admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
} }
break; break;
case "Add": case "Add":
@ -515,10 +523,10 @@
switch (location) switch (location)
{ {
case "Add": case "Add":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "cp=" + PageState.Page.PageId); url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break; break;
case "Edit": case "Edit":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString() + "&cp=" + PageState.Page.PageId); url = EditUrl("admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}");
break; break;
} }
} }

View File

@ -19,7 +19,6 @@ namespace Oqtane.Themes.Controls
[Inject] public IUserService UserService { get; set; } [Inject] public IUserService UserService { get; set; }
[Inject] public IJSRuntime jsRuntime { get; set; } [Inject] public IJSRuntime jsRuntime { get; set; }
[Inject] public IServiceProvider ServiceProvider { get; set; } [Inject] public IServiceProvider ServiceProvider { get; set; }
[Inject] public SiteState SiteState { get; set; }
[Inject] public ILogService LoggingService { get; set; } [Inject] public ILogService LoggingService { get; set; }
protected void LoginUser() protected void LoginUser()

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Themes.OqtaneTheme namespace Oqtane.Themes.OqtaneTheme
{ {
@ -11,7 +13,13 @@ namespace Oqtane.Themes.OqtaneTheme
Name = "Oqtane Theme", Name = "Oqtane Theme",
Version = "1.0.0", Version = "1.0.0",
ThemeSettingsType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client", ThemeSettingsType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client",
ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client" ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client",
Resources = new List<Resource>()
{
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.0/cyborg/bootstrap.min.css", Integrity = "sha512-jwIqEv8o/kTBMJVtbNCBrDqhBojl0YSUam+EFpLjVOC86Ci6t4ZciTnIkelFNOik+dEQVymKGcQLiaJZNAfWRg==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", Integrity = "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", CrossOrigin = "anonymous" }
}
}; };
} }
} }

View File

@ -110,14 +110,6 @@
public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer"; public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer";
public override List<Resource> Resources => new List<Resource>()
{
// obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.0/cyborg/bootstrap.min.css", Integrity = "sha512-d6pZJl/sNcj0GFkp4kTjXtPE14deuUsOqFQtxkj0KyBJQl+4e0qsEyuIDcNqrYuGoauAW3sWyDCQp49mhF4Syw==", CrossOrigin = "anonymous" },
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" }
};
private bool _login = true; private bool _login = true;
private bool _register = true; private bool _register = true;
private bool _footer = false; private bool _footer = false;

View File

@ -52,17 +52,23 @@
</div> </div>
@code { @code {
private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization private int pageId = -1;
private string _scope = "page"; private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization
private string _login = "-"; private string _scope = "page";
private string _register = "-"; private string _login = "-";
private string _footer = "-"; private string _register = "-";
private string _footer = "-";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try if (PageState.QueryString.ContainsKey("id"))
{ {
await LoadSettings(); pageId = int.Parse(PageState.QueryString["id"]);
}
try
{
await LoadSettings();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -82,10 +88,11 @@
} }
else else
{ {
var settings = SettingService.MergeSettings(PageState.Site.Settings, PageState.Page.Settings); var settings = await SettingService.GetPageSettingsAsync(pageId);
settings = SettingService.MergeSettings(PageState.Site.Settings, settings);
_login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-"); _login = SettingService.GetSetting(settings, GetType().Namespace + ":Login", "-");
_register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-"); _register = SettingService.GetSetting(settings, GetType().Namespace + ":Register", "-");
_footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-"); _footer = SettingService.GetSetting(settings, GetType().Namespace + ":Footer", "-");
} }
await Task.Yield(); await Task.Yield();
} }
@ -128,7 +135,7 @@
} }
else else
{ {
var settings = await SettingService.GetPageSettingsAsync(PageState.Page.PageId); var settings = await SettingService.GetPageSettingsAsync(pageId);
if (_login != "-") if (_login != "-")
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
@ -141,7 +148,7 @@
{ {
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer); settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer);
} }
await SettingService.UpdatePageSettingsAsync(settings, PageState.Page.PageId); await SettingService.UpdatePageSettingsAsync(settings, pageId);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -6,6 +6,7 @@ using Oqtane.UI;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Themes namespace Oqtane.Themes
@ -15,10 +16,13 @@ namespace Oqtane.Themes
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } protected IJSRuntime JSRuntime { get; set; }
// optional interface properties [Inject]
protected SiteState SiteState { get; set; }
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
// optional interface properties
public virtual string Name { get; set; } public virtual string Name { get; set; }
public virtual string Thumbnail { get; set; } public virtual string Thumbnail { get; set; }
public virtual string Panes { get; set; } public virtual string Panes { get; set; }
@ -30,17 +34,33 @@ namespace Oqtane.Themes
{ {
if (firstRender) if (firstRender)
{ {
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script)) List<Resource> resources = null;
var type = GetType();
if (type.BaseType == typeof(ThemeBase))
{
if (PageState.Page.Resources != null)
{
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
}
}
else // themecontrolbase, containerbase
{
if (Resources != null)
{
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
}
}
if (resources != null && resources.Any())
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0; var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in resources)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module }); scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() });
} }
else else
{ {
@ -139,6 +159,33 @@ namespace Oqtane.Themes
return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate); return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate);
} }
public void SetPageTitle(string title)
{
SiteState.Properties.PageTitle = title;
}
// note - only supports links and meta tags - not scripts
public void AddHeadContent(string content)
{
SiteState.AppendHeadContent(content);
}
public void AddScript(Resource resource)
{
resource.ResourceType = ResourceType.Script;
if (Resources == null) Resources = new List<Resource>();
if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content)))
{
Resources.Add(resource);
}
}
public async Task ScrollToPageTop()
{
var interop = new Interop(JSRuntime);
await interop.ScrollTo(0, 0, "smooth");
}
[Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)] [Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)]
public string ContentUrl(int fileid) public string ContentUrl(int fileid)
{ {

View File

@ -4,7 +4,8 @@
@if (_visible) @if (_visible)
{ {
<CascadingValue Value="@ModuleState"> <a id="@ModuleState.PageModuleId.ToString()"></a>
<CascadingValue Value="@ModuleState">
@if (_useadminborder) @if (_useadminborder)
{ {
<div class="app-pane-admin-border"> <div class="app-pane-admin-border">
@ -16,6 +17,7 @@
@DynamicComponent @DynamicComponent
} }
</CascadingValue> </CascadingValue>
} }
@code { @code {
@ -38,7 +40,7 @@
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
string container = ModuleState.ContainerType; string container = ModuleState.ContainerType;
if (PageState.ModuleId != -1 && ModuleState.UseAdminContainer) if (PageState.ModuleId != -1 && PageState.Route.Action != "" && ModuleState.UseAdminContainer)
{ {
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer; container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
} }

View File

@ -95,8 +95,8 @@ namespace Oqtane.UI
try try
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeLinks", "Oqtane.Interop.includeLinks",
(object) links); (object)links);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch
@ -107,12 +107,17 @@ namespace Oqtane.UI
// external scripts need to specify src, inline scripts need to specify id and content // external scripts need to specify src, inline scripts need to specify id and content
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location) public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, "", content, location);
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{ {
try try
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript", "Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, content, location); id, src, integrity, crossorigin, type, content, location);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch

View File

@ -13,6 +13,7 @@ namespace Oqtane.UI
public Page Page { get; set; } public Page Page { get; set; }
public User User { get; set; } public User User { get; set; }
public Uri Uri { get; set; } public Uri Uri { get; set; }
public Route Route { get; set; }
public Dictionary<string, string> QueryString { get; set; } public Dictionary<string, string> QueryString { get; set; }
public string UrlParameters { get; set; } public string UrlParameters { get; set; }
public int ModuleId { get; set; } public int ModuleId { get; set; }
@ -23,6 +24,7 @@ namespace Oqtane.UI
public int VisitorId { get; set; } public int VisitorId { get; set; }
public string RemoteIPAddress { get; set; } public string RemoteIPAddress { get; set; }
public string ReturnUrl { get; set; } public string ReturnUrl { get; set; }
public bool IsInternalNavigation { get; set; }
public List<Page> Pages public List<Page> Pages
{ {

View File

@ -17,61 +17,59 @@ else
} }
@code { @code {
private bool _useadminborder = false; private bool _useadminborder = false;
private string _panetitle = ""; private string _panetitle = "";
[CascadingParameter] [CascadingParameter]
protected PageState PageState { get; set; } protected PageState PageState { get; set; }
[Parameter] [Parameter]
public string Name { get; set; } public string Name { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction) if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList) && PageState.Action == Constants.DefaultAction)
{ {
_useadminborder = true; _useadminborder = true;
_panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>"; _panetitle = "<div class=\"app-pane-admin-title\">" + Name + " Pane</div>";
} }
else else
{ {
_useadminborder = false; _useadminborder = false;
_panetitle = ""; _panetitle = "";
} }
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState.ModuleId != -1 && PageState.Action != Constants.DefaultAction) foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId))
{ {
// action route needs to inject module control into specific pane var pane = module.Pane;
string pane = ""; if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) {
{ if (PageState.Page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
pane = PaneNames.Default; {
} pane = PaneNames.Default;
else }
{ else
pane = PaneNames.Admin; {
pane = PaneNames.Admin;
}
}
} // pane matches current pane
if (Name.ToLower() == pane.ToLower()) if (Name.ToLower() == pane.ToLower())
{ {
Module module = PageState.Modules.FirstOrDefault(item => item.PageId == PageState.Page.PageId && item.ModuleId == PageState.ModuleId); if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
if (module == null) {
{ var moduleType = Type.GetType(module.ModuleType);
module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId); if (moduleType != null)
} {
if (module != null) bool authorized = false;
{ if (Constants.DefaultModuleActions.Contains(PageState.Action))
var moduleType = Type.GetType(module.ModuleType); {
if (moduleType != null) authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
{
bool authorized = false;
if (Constants.DefaultModuleActions.Contains(PageState.Action))
{
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
} }
else else
{ {
@ -100,43 +98,20 @@ else
CreateComponent(builder, module); CreateComponent(builder, module);
} }
} }
else
{
// module type does not exist
}
} }
} else
}
else
{
if (PageState.ModuleId != -1)
{
Module module = PageState.Modules.FirstOrDefault(item => item.PageId == PageState.Page.PageId && item.ModuleId == PageState.ModuleId);
if (module == null)
{
module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId);
}
if (module != null && module.Pane.ToLower() == Name.ToLower())
{ {
// check if user is authorized to view module if (PageState.ModuleId == -1 || PageState.ModuleId == module.ModuleId)
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
{ {
CreateComponent(builder, module); // check if user is authorized to view module
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
{
CreateComponent(builder, module);
}
} }
} }
} }
else }
{
foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId && item.Pane.ToLower() == Name.ToLower()))
{
// check if user is authorized to view module
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
{
CreateComponent(builder, module);
}
}
}
}
}; };
} }

View File

@ -22,207 +22,243 @@
@DynamicComponent @DynamicComponent
@code { @code {
private string _absoluteUri; private string _absoluteUri;
private bool _navigationInterceptionEnabled; private bool _isInternalNavigation = false;
private PageState _pagestate; private bool _navigationInterceptionEnabled;
private string _error = ""; private PageState _pagestate;
private string _error = "";
[Parameter] [Parameter]
public string Runtime { get; set; } public string Runtime { get; set; }
[Parameter] [Parameter]
public string RenderMode { get; set; } public string RenderMode { get; set; }
[Parameter] [Parameter]
public int VisitorId { get; set; } public int VisitorId { get; set; }
[CascadingParameter] [CascadingParameter]
PageState PageState { get; set; } PageState PageState { get; set; }
[Parameter] [Parameter]
public Action<PageState> OnStateChange { get; set; } public Action<PageState> OnStateChange { get; set; }
private RenderFragment DynamicComponent { get; set; } private RenderFragment DynamicComponent { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
_absoluteUri = NavigationManager.Uri; _absoluteUri = NavigationManager.Uri;
NavigationManager.LocationChanged += LocationChanged; NavigationManager.LocationChanged += LocationChanged;
DynamicComponent = builder => DynamicComponent = builder =>
{ {
if (PageState != null) if (PageState != null)
{ {
builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); builder.OpenComponent(0, Type.GetType(Constants.PageComponent));
builder.CloseComponent(); builder.CloseComponent();
} }
}; };
} }
public void Dispose() public void Dispose()
{ {
NavigationManager.LocationChanged -= LocationChanged; NavigationManager.LocationChanged -= LocationChanged;
} }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (PageState == null) if (PageState == null)
{ {
await Refresh(); await Refresh();
} }
} }
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] private async void LocationChanged(object sender, LocationChangedEventArgs args)
private async Task Refresh() {
{ _absoluteUri = args.Location;
Site site; _isInternalNavigation = true;
Page page; await Refresh();
User user = null; }
var editmode = false;
var refresh = false;
var lastsyncdate = DateTime.UtcNow.AddHours(-1);
var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
_error = "";
Route route = new Route(_absoluteUri, SiteState.Alias.Path); Task IHandleAfterRender.OnAfterRenderAsync()
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; {
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; if (!_navigationInterceptionEnabled)
var querystring = ParseQueryString(route.Query); {
var returnurl = ""; _navigationInterceptionEnabled = true;
if (querystring.ContainsKey("returnurl")) return NavigationInterception.EnableNavigationInterceptionAsync();
{ }
returnurl = WebUtility.UrlDecode(querystring["returnurl"]); return Task.CompletedTask;
} }
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) private async Task Refresh()
{ {
if (querystring.ContainsKey("reload") && querystring["reload"] == "post") Site site;
{ Page page;
// post back so that the cookies are set correctly - required on any change to the principal User user = null;
var interop = new Interop(JSRuntime); var editmode = false;
var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) }; var refresh = false;
string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/"); var lastsyncdate = DateTime.UtcNow.AddHours(-1);
await interop.SubmitForm(url, fields); var runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime);
return; _error = "";
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
}
}
// the refresh parameter is used to refresh the client-side PageState Route route = new Route(_absoluteUri, SiteState.Alias.Path);
if (querystring.ContainsKey("refresh")) int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
{ var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
refresh = true; var querystring = Utilities.ParseQueryString(route.Query);
} var returnurl = "";
if (querystring.ContainsKey("returnurl"))
{
returnurl = WebUtility.UrlDecode(querystring["returnurl"]);
}
if (PageState != null) // reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
{ if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
editmode = PageState.EditMode; {
lastsyncdate = PageState.LastSyncDate; if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
} {
if (PageState?.Page.Path != route.PagePath) // post back so that the cookies are set correctly - required on any change to the principal
{ var interop = new Interop(JSRuntime);
editmode = false; // reset edit mode when navigating to different page var fields = new { returnurl = "/" + NavigationManager.ToBaseRelativePath(_absoluteUri) };
} string url = Utilities.TenantUrl(SiteState.Alias, "/pages/external/");
await interop.SubmitForm(url, fields);
return;
}
else
{
NavigationManager.NavigateTo(_absoluteUri.Replace("?reload", ""), true);
return;
}
}
// get user // the refresh parameter is used to refresh the client-side PageState
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) if (querystring.ContainsKey("refresh"))
{ {
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); refresh = true;
if (authState.User.Identity.IsAuthenticated) }
{
user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
if (user != null)
{
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
}
}
}
else
{
user = PageState.User;
}
// process any sync events if (PageState != null)
var sync = await SyncService.GetSyncAsync(lastsyncdate); {
lastsyncdate = sync.SyncDate; editmode = PageState.EditMode;
if (sync.SyncEvents.Any()) lastsyncdate = PageState.LastSyncDate;
{ }
// reload client application if server was restarted or site runtime/rendermode was modified if (PageState?.Page.Path != route.PagePath)
if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload))) {
{ editmode = false; // reset edit mode when navigating to different page
NavigationManager.NavigateTo(_absoluteUri, true); }
return;
}
// when site information has changed the PageState needs to be refreshed
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
}
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
refresh = true;
}
}
if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId) // get user
{ if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId); {
refresh = true; var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
} if (authState.User.Identity.IsAuthenticated)
else {
{ user = await UserService.GetUserAsync(authState.User.Identity.Name, SiteState.Alias.SiteId);
site = PageState.Site; if (user != null)
} {
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
}
}
}
else
{
user = PageState.User;
}
if (site != null) // process any sync events
{ var sync = await SyncService.GetSyncAsync(lastsyncdate);
if (PageState == null || refresh || PageState.Page.Path != route.PagePath) lastsyncdate = sync.SyncDate;
{ if (sync.SyncEvents.Any())
page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); {
} // reload client application if server was restarted or site runtime/rendermode was modified
else if (PageState != null && sync.SyncEvents.Exists(item => (item.Action == SyncEventActions.Reload)))
{ {
page = PageState.Page; NavigationManager.NavigateTo(_absoluteUri, true);
} return;
}
// when site information has changed the PageState needs to be refreshed
if (sync.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == SiteState.Alias.SiteId))
{
refresh = true;
}
// when user information has changed the PageState needs to be refreshed as the list of pages/modules may have changed
if (user != null && sync.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId))
{
refresh = true;
}
}
if (page == null && route.PagePath == "") // naked path refers to site home page if (PageState == null || refresh || PageState.Alias.SiteId != SiteState.Alias.SiteId)
{ {
if (site.HomePageId != null) site = await SiteService.GetSiteAsync(SiteState.Alias.SiteId);
{ refresh = true;
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId); }
} else
if (page == null) {
{ site = PageState.Site;
// fallback to use the first page in the collection }
page = site.Pages.FirstOrDefault();
}
}
if (page != null) if (site != null)
{ {
// check if user is authorized to view page if (PageState == null || refresh || PageState.Page.Path != route.PagePath)
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList)) {
{ page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
// load additional metadata for current page }
page = await ProcessPage(page, site, user); else
{
page = PageState.Page;
}
// load additional metadata for modules if (page == null && route.PagePath == "") // naked path refers to site home page
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); {
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();
}
}
if (page == null)
{
// look for personalized page
page = await PageService.GetPageAsync(route.PagePath, site.SiteId);
}
else
{
if (user != null && page.IsPersonalizable)
{
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + user.Username, site.SiteId);
if (personalized != null)
{
// redirect to the personalized page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, personalized.Path, ""), false);
}
}
}
// populate page state (which acts as a client-side cache for subsequent requests) if (page != null)
_pagestate = new PageState {
// check if user is authorized to view page
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList))
{
// load additional metadata for current page
page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
// populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState
{ {
Alias = SiteState.Alias, Alias = SiteState.Alias,
Site = site, Site = site,
Page = page, Page = page,
User = user, User = user,
Uri = new Uri(_absoluteUri, UriKind.Absolute), Uri = new Uri(_absoluteUri, UriKind.Absolute),
Route = route,
QueryString = querystring, QueryString = querystring,
UrlParameters = route.UrlParameters, UrlParameters = route.UrlParameters,
ModuleId = moduleid, ModuleId = moduleid,
@ -232,300 +268,270 @@
Runtime = runtime, Runtime = runtime,
VisitorId = VisitorId, VisitorId = VisitorId,
RemoteIPAddress = SiteState.RemoteIPAddress, RemoteIPAddress = SiteState.RemoteIPAddress,
ReturnUrl = returnurl ReturnUrl = returnurl,
IsInternalNavigation = _isInternalNavigation
}; };
OnStateChange?.Invoke(_pagestate); OnStateChange?.Invoke(_pagestate);
await ScrollToFragment(_pagestate.Uri); await ScrollToFragment(_pagestate.Uri);
} }
} }
else // page not found else // page not found
{ {
// look for url mapping // look for url mapping
var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath);
if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl))
{ {
var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl;
NavigationManager.NavigateTo(url, false); NavigationManager.NavigateTo(url, false);
} }
else // not mapped else // not mapped
{ {
if (user == null) if (user == null)
{ {
// redirect to login page if user not logged in as they may need to be authenticated // redirect to login page if user not logged in as they may need to be authenticated
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery)));
} }
else else
{ {
if (route.PagePath != "404") if (route.PagePath != "404")
{ {
// redirect to 404 page // redirect to 404 page
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", ""));
} }
else else
{ {
// redirect to home page as a fallback // redirect to home page as a fallback
NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", ""));
} }
} }
} }
} }
} }
else else
{ {
// site does not exist // site does not exist
} }
} }
private async void LocationChanged(object sender, LocationChangedEventArgs args) private Page ProcessPage(Page page, Site site, User user, Alias alias)
{ {
_absoluteUri = args.Location; try
await Refresh(); {
} page.Panes = new List<string>();
page.Resources = new List<Resource>();
Task IHandleAfterRender.OnAfterRenderAsync() // validate theme
{ if (string.IsNullOrEmpty(page.ThemeType))
if (!_navigationInterceptionEnabled) {
{ page.ThemeType = site.DefaultThemeType;
_navigationInterceptionEnabled = true; }
return NavigationInterception.EnableNavigationInterceptionAsync(); var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
} Type themetype = Type.GetType(page.ThemeType);
return Task.CompletedTask; if (themetype == null || theme == null)
} {
// fallback to default Oqtane theme
page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme);
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
}
private Dictionary<string, string> ParseQueryString(string query) string panes = "";
{ if (themetype != null && theme != null)
Dictionary<string, string> querystring = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // case insensistive keys {
if (!string.IsNullOrEmpty(query)) // get resources for theme (ITheme)
{ page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
if (query.StartsWith("?"))
{
query = query.Substring(1); // ignore "?"
}
foreach (string kvp in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "")
{
if (kvp.Contains("="))
{
string[] pair = kvp.Split('=');
if (!querystring.ContainsKey(pair[0]))
{
querystring.Add(pair[0], pair[1]);
}
}
else
{
if (!querystring.ContainsKey(kvp))
{
querystring.Add(kvp, "true"); // default parameter when no value is provided
}
}
}
}
}
return querystring;
}
private async Task<Page> ProcessPage(Page page, Site site, User user) var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
{ if (themeobject != null)
try {
{ if (!string.IsNullOrEmpty(themeobject.Panes))
if (page.IsPersonalizable && user != null) {
{ panes = themeobject.Panes;
// load the personalized page }
page = await PageService.GetPageAsync(page.PageId, user.UserId); // get resources for theme control
} page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
}
}
if (string.IsNullOrEmpty(page.ThemeType)) if (!string.IsNullOrEmpty(panes))
{ {
page.ThemeType = site.DefaultThemeType; page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
} if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
{
_error = "The Current Theme Does Not Contain A Default Or Admin Pane";
}
}
else
{
page.Panes.Add(PaneNames.Admin);
_error = "The Current Theme Does Not Contain Any Panes";
}
}
catch
{
// error loading theme or layout
}
page.Panes = new List<string>(); return page;
page.Resources = new List<Resource>(); }
string panes = ""; private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
Type themetype = Type.GetType(page.ThemeType); {
if (themetype == null) var paneindex = new Dictionary<string, int>();
{ foreach (Module module in modules)
// fallback {
page.ThemeType = Constants.DefaultTheme; // initialize module control properties
themetype = Type.GetType(Constants.DefaultTheme); module.SecurityAccessLevel = SecurityAccessLevel.Host;
} module.ControlTitle = "";
if (themetype != null) module.Actions = "";
{ module.UseAdminContainer = false;
var themeobject = Activator.CreateInstance(themetype) as IThemeControl; module.PaneModuleIndex = -1;
if (themeobject != null) module.PaneModuleCount = 0;
{
if (!string.IsNullOrEmpty(themeobject.Panes))
{
panes = themeobject.Panes;
}
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page);
}
}
if (!string.IsNullOrEmpty(panes))
{
page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
if (!page.Panes.Contains(PaneNames.Default) && !page.Panes.Contains(PaneNames.Admin))
{
_error = "The Current Theme Does Not Contain A Default Or Admin Pane";
}
}
else
{
page.Panes.Add(PaneNames.Admin);
_error = "The Current Theme Does Not Contain Any Panes";
}
}
catch
{
// error loading theme or layout
}
return page; if ((module.PageId == page.PageId || module.ModuleId == moduleid))
} {
var typename = Constants.ErrorModule;
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype) if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{ {
var paneindex = new Dictionary<string, int>(); typename = module.ModuleDefinition.ControlTypeTemplate;
foreach (Module module in modules)
{
// initialize module control properties
module.SecurityAccessLevel = SecurityAccessLevel.Host;
module.ControlTitle = "";
module.Actions = "";
module.UseAdminContainer = false;
module.PaneModuleIndex = -1;
module.PaneModuleCount = 0;
if ((module.PageId == page.PageId || module.ModuleId == moduleid)) // handle default action
{ if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
var typename = Constants.ErrorModule; {
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) action = module.ModuleDefinition.DefaultAction;
{ }
typename = module.ModuleDefinition.ControlTypeTemplate;
// handle default action // check if the module defines custom action routes
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) if (module.ModuleDefinition.ControlTypeRoutes != "")
{ {
action = module.ModuleDefinition.DefaultAction; foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
} {
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
// check if the module defines custom action routes // get module resources
if (module.ModuleDefinition.ControlTypeRoutes != "") page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
{ }
foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
if (route.StartsWith(action + "="))
{
typename = route.Replace(action + "=", "");
}
}
}
}
// ensure component exists and implements IModuleControl // ensure component exists and implements IModuleControl
module.ModuleType = ""; module.ModuleType = "";
if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase))
{ {
typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action);
} }
else else
{ {
typename = typename.Replace(Constants.ActionToken, action); typename = typename.Replace(Constants.ActionToken, action);
} }
Type moduletype = Type.GetType(typename, false, true); // case insensitive Type moduletype = Type.GetType(typename, false, true); // case insensitive
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl))) if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
{ {
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
} }
// get additional metadata from IModuleControl interface // get additional metadata from IModuleControl interface
if (moduletype != null && module.ModuleType != "") if (moduletype != null && module.ModuleType != "")
{ {
// retrieve module component resources // retrieve module component resources
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
if (action.ToLower() == "settings" && module.ModuleDefinition != null) if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{ {
// settings components are embedded within a framework settings module // settings components are embedded within a framework settings module
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true); moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
if (moduletype != null) if (moduletype != null)
{ {
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module); page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
} }
} }
// additional metadata needed for admin components // additional metadata needed for admin components
if (module.ModuleId == moduleid && action != "") if (module.ModuleId == moduleid && action != "")
{ {
module.SecurityAccessLevel = moduleobject.SecurityAccessLevel; module.SecurityAccessLevel = moduleobject.SecurityAccessLevel;
module.ControlTitle = moduleobject.Title; module.ControlTitle = moduleobject.Title;
module.Actions = moduleobject.Actions; module.Actions = moduleobject.Actions;
module.UseAdminContainer = moduleobject.UseAdminContainer; module.UseAdminContainer = moduleobject.UseAdminContainer;
} }
} }
// validate that module's pane exists in current page // validate that module's pane exists in current page
if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1) if (page.Panes.FindIndex(item => item.Equals(module.Pane, StringComparison.OrdinalIgnoreCase)) == -1)
{ {
// fallback to default pane if it exists // fallback to default pane if it exists
if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1) if (page.Panes.FindIndex(item => item.Equals(PaneNames.Default, StringComparison.OrdinalIgnoreCase)) != -1)
{ {
module.Pane = PaneNames.Default; module.Pane = PaneNames.Default;
} }
else // otherwise admin pane (legacy) else // otherwise admin pane (legacy)
{ {
module.Pane = PaneNames.Admin; module.Pane = PaneNames.Admin;
} }
} }
// calculate module position within pane // calculate module position within pane
if (paneindex.ContainsKey(module.Pane.ToLower())) if (paneindex.ContainsKey(module.Pane.ToLower()))
{ {
paneindex[module.Pane.ToLower()] += 1; paneindex[module.Pane.ToLower()] += 1;
} }
else else
{ {
paneindex.Add(module.Pane.ToLower(), 0); paneindex.Add(module.Pane.ToLower(), 0);
} }
module.PaneModuleIndex = paneindex[module.Pane.ToLower()]; module.PaneModuleIndex = paneindex[module.Pane.ToLower()];
// container fallback // container fallback
if (string.IsNullOrEmpty(module.ContainerType)) if (string.IsNullOrEmpty(module.ContainerType))
{ {
module.ContainerType = defaultcontainertype; module.ContainerType = defaultcontainertype;
} }
} }
} }
foreach (Module module in modules.Where(item => item.PageId == page.PageId)) foreach (Module module in modules.Where(item => item.PageId == page.PageId))
{ {
if (paneindex.ContainsKey(module.Pane.ToLower())) if (paneindex.ContainsKey(module.Pane.ToLower()))
{ {
module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1; module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1;
} }
} }
return (page, modules); return (page, modules);
} }
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level) private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
{ {
if (resources != null) if (resources != null)
{ {
foreach (var resource in resources) foreach (var resource in resources)
{ {
// ensure resource does not exist already if (resource.Level != ResourceLevel.Site)
if (pageresources.Find(item => item.Url == resource.Url) == null) {
{ if (resource.Url.StartsWith("~"))
resource.Level = level; {
pageresources.Add(resource); resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
} }
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
resource.Level = level;
resource.Namespace = name;
pageresources.Add(resource);
}
}
} }
} }
return pageresources; return pageresources;

View File

@ -1,59 +1,186 @@
@namespace Oqtane.UI @namespace Oqtane.UI
@inject IJSRuntime JsRuntime @inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject SiteState SiteState
@DynamicComponent @DynamicComponent
@code { @code {
[CascadingParameter] PageState PageState { get; set; } [CascadingParameter] PageState PageState { get; set; }
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
// handle page redirection // handle page redirection
if (!string.IsNullOrEmpty(PageState.Page.Url)) if (!string.IsNullOrEmpty(PageState.Page.Url))
{
NavigationManager.NavigateTo(PageState.Page.Url);
return;
}
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
var interop = new Interop(JsRuntime);
// manage stylesheets for this page
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
}
if (links.Any())
{ {
await interop.IncludeLinks(links.ToArray()); NavigationManager.NavigateTo(PageState.Page.Url);
return;
} }
await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00");
await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00");
// set page title // set page title
if (!string.IsNullOrEmpty(PageState.Page.Title)) if (!string.IsNullOrEmpty(PageState.Page.Title))
{ {
await interop.UpdateTitle(PageState.Page.Title); SiteState.Properties.PageTitle = PageState.Page.Title;
} }
else else
{ {
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); SiteState.Properties.PageTitle = PageState.Site.Name + " - " + PageState.Page.Name;
} }
}
// set page head content
var headcontent = "";
// favicon
var favicon = "favicon.ico";
var favicontype = "x-icon";
if (PageState.Site.FaviconFileId != null)
{
favicon = Utilities.FileUrl(PageState.Alias, PageState.Site.FaviconFileId.Value);
favicontype = favicon.Substring(favicon.LastIndexOf(".") + 1);
}
headcontent += $"<link id=\"app-favicon\" rel=\"shortcut icon\" type=\"image/{favicontype}\" href=\"{favicon}\" />\n";
// head content
AddHeadContent(headcontent, PageState.Site.HeadContent);
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
{
headcontent = AddHeadContent(headcontent, PageState.Site.HeadContent);
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent))
{
headcontent = AddHeadContent(headcontent, PageState.Page.HeadContent);
}
SiteState.Properties.HeadContent = headcontent;
DynamicComponent = builder =>
{
var themeType = Type.GetType(PageState.Page.ThemeType);
builder.OpenComponent(0, themeType);
builder.CloseComponent();
};
}
private string AddHeadContent(string headcontent, string content)
{
if (!string.IsNullOrEmpty(content))
{
// format head content, remove scripts, and filter duplicate elements
var elements = (">" + content.Replace("\n", "") + "<").Split("><");
foreach (var element in elements)
{
if (!string.IsNullOrEmpty(element) && !element.Contains("script"))
{
if (!headcontent.Contains("<" + element + ">"))
{
headcontent += "<" + element + ">" + "\n";
}
}
}
}
return headcontent;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender)
{
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
}
if (!string.IsNullOrEmpty(PageState.Page.BodyContent) && PageState.Page.BodyContent.Contains("<script"))
{
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
}
}
// style sheets
if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Stylesheet))
{
var interop = new Interop(JSRuntime);
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
{
var prefix = "app-stylesheet-" + resource.Level.ToString().ToLower();
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
links.Add(new { id = prefix + "-" + batch + "-" + (links.Count + 1).ToString("00"), rel = "stylesheet", href = url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", insertbefore = prefix });
}
if (links.Any())
{
await interop.IncludeLinks(links.ToArray());
}
await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00");
await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00");
}
}
private async Task InjectScripts(string content, ResourceLocation location)
{
// inject scripts into page dynamically
var interop = new Interop(JSRuntime);
var scripts = new List<object>();
var count = 0;
var index = content.IndexOf("<script");
while (index >= 0)
{
var script = content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
// get script attributes
var attributes = script.Substring(0, script.IndexOf(">")).Replace("\"", "").Split(" ");
string id = "";
string src = "";
string integrity = "";
string crossorigin = "";
string type = "";
foreach (var attribute in attributes)
{
if (attribute.Contains("="))
{
var value = attribute.Split("=");
switch (value[0])
{
case "id":
id = value[1];
break;
case "src":
src = value[1];
break;
case "integrity":
integrity = value[1];
break;
case "crossorigin":
crossorigin = value[1];
break;
case "type":
type = value[1];
break;
}
}
}
// inject script
if (!string.IsNullOrEmpty(src))
{
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location });
}
else
{
// inline script must have an id attribute
if (id == "")
{
count += 1;
id = $"page{PageState.Page.PageId}-script{count}";
}
index = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower());
}
index = content.IndexOf("<script", index + 1);
}
if (scripts.Any())
{
await interop.IncludeScripts(scripts.ToArray());
}
}
} }

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MySql.EntityFrameworkCore" Version="6.0.0" /> <PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.MySQL</id> <id>Oqtane.Database.MySQL</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title> <title>Oqtane MySQL Provider</title>
@ -12,15 +12,15 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.MySQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.MySQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.MySQL.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.MySQL.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Mysql.EntityFrameworkCore.dll" target="lib\net6.0" /> <file src="bin\net7.0\Mysql.EntityFrameworkCore.dll" target="lib\net7.0" />
<file src="bin\net6.0\Mysql.Data.dll" target="lib\net6.0" /> <file src="bin\net7.0\Mysql.Data.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,9 +29,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" /> <PackageReference Include="EFCore.NamingConventions" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.PostgreSQL</id> <id>Oqtane.Database.PostgreSQL</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title> <title>Oqtane PostgreSQL Provider</title>
@ -12,16 +12,16 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.PostgreSQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.PostgreSQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.PostgreSQL.pdb" target="lib\net7.0" />
<file src="bin\net6.0\EFCore.NamingConventions.dll" target="lib\net6.0" /> <file src="bin\net7.0\EFCore.NamingConventions.dll" target="lib\net7.0" />
<file src="bin\net6.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net6.0" /> <file src="bin\net7.0\Npgsql.EntityFrameworkCore.PostgreSQL.dll" target="lib\net7.0" />
<file src="bin\net6.0\Npgsql.dll" target="lib\net6.0" /> <file src="bin\net7.0\Npgsql.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.SqlServer</id> <id>Oqtane.Database.SqlServer</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title> <title>Oqtane SQL Server Provider</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.SqlServer.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.SqlServer.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.SqlServer.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.SqlServer.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net6.0" /> <file src="bin\net7.0\Microsoft.EntityFrameworkCore.SqlServer.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,7 +29,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.Sqlite</id> <id>Oqtane.Database.Sqlite</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title> <title>Oqtane SQLite Provider</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="bin\net6.0\Oqtane.Database.Sqlite.dll" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.Sqlite.dll" target="lib\net7.0" />
<file src="bin\net6.0\Oqtane.Database.Sqlite.pdb" target="lib\net6.0" /> <file src="bin\net7.0\Oqtane.Database.Sqlite.pdb" target="lib\net7.0" />
<file src="bin\net6.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net6.0" /> <file src="bin\net7.0\Microsoft.EntityFrameworkCore.Sqlite.dll" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

6
Oqtane.Maui/Head.razor Normal file
View File

@ -0,0 +1,6 @@
<DynamicComponent Type="@ComponentType"></DynamicComponent>
@code {
Type ComponentType = Type.GetType("Oqtane.Head, Oqtane.Client");
}

View File

@ -7,6 +7,7 @@
<BlazorWebView HostPage="wwwroot/index.html"> <BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents> <BlazorWebView.RootComponents>
<RootComponent Selector="head::after" ComponentType="{x:Type local:Head}" />
<RootComponent Selector="#app" ComponentType="{x:Type local:Main}" /> <RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
</BlazorWebView.RootComponents> </BlazorWebView.RootComponents>
</BlazorWebView> </BlazorWebView>

View File

@ -12,8 +12,8 @@ namespace Oqtane.Maui;
public static class MauiProgram public static class MauiProgram
{ {
// the API service url // the API service url
static string apiurl = "https://www.dnfprojects.com"; // for testing //static string apiurl = "https://www.dnfprojects.com"; // for testing
//static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect) static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
public static MauiApp CreateMauiApp() public static MauiApp CreateMauiApp()
{ {

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Razor"> <Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks> <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks> --> <!-- <TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.4.3</Version> <Version>4.0.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>3.4.3</ApplicationDisplayVersion> <ApplicationDisplayVersion>4.0.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -65,14 +65,21 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.8" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="7.0.5" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
<PackageReference Include="Oqtane.Client" Version="3.4.3" /> </ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="3.4.3" />
<ItemGroup>
<Reference Include="Oqtane.Client">
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Client.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared">
<HintPath>..\Oqtane.Server\bin\Debug\net7.0\Oqtane.Shared.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with you package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

View File

@ -9,8 +9,8 @@
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script> <script src="js/loadjs.min.js"></script>
<link rel="stylesheet" href="css/app.css" /> <link rel="stylesheet" href="css/app.css" />
<link id="app-stylesheet-page" rel="stylesheet" href="css/empty.css" disabled /> <link id="app-stylesheet-page" />
<link id="app-stylesheet-module" rel="stylesheet" href="css/empty.css" disabled /> <link id="app-stylesheet-module" />
</head> </head>
<body> <body>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Client\bin\Release\net6.0\Oqtane.Client.dll" target="lib\net6.0" /> <file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.dll" target="lib\net7.0" />
<file src="..\Oqtane.Client\bin\Release\net6.0\Oqtane.Client.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Client\bin\Release\net7.0\Oqtane.Client.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright> <copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.4.3/Oqtane.Framework.3.4.3.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v4.0.0/Oqtane.Framework.4.0.0.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Server\bin\Release\net6.0\Oqtane.Server.dll" target="lib\net6.0" /> <file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.dll" target="lib\net7.0" />
<file src="..\Oqtane.Server\bin\Release\net6.0\Oqtane.Server.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Server\bin\Release\net7.0\Oqtane.Server.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Shared\bin\Release\net6.0\Oqtane.Shared.dll" target="lib\net6.0" /> <file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.dll" target="lib\net7.0" />
<file src="..\Oqtane.Shared\bin\Release\net6.0\Oqtane.Shared.pdb" target="lib\net6.0" /> <file src="..\Oqtane.Shared\bin\Release\net7.0\Oqtane.Shared.pdb" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.4.3</version> <version>4.0.0</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,12 +12,12 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Updater\bin\Release\net6.0\publish\*.*" target="lib\net6.0" /> <file src="..\Oqtane.Updater\bin\Release\net7.0\publish\*.*" target="lib\net7.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>
</package> </package>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.4.3.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.0.Install.zip" -Force

View File

@ -8,14 +8,14 @@ nuget.exe pack Oqtane.Client.nuspec
nuget.exe pack Oqtane.Server.nuspec nuget.exe pack Oqtane.Server.nuspec
nuget.exe pack Oqtane.Shared.nuspec nuget.exe pack Oqtane.Shared.nuspec
nuget.exe pack Oqtane.Framework.nuspec nuget.exe pack Oqtane.Framework.nuspec
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish" > NUL del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish" rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL del /F/Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" rmdir /Q/S "..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Modules\*") do ( for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Modules\*") do (
set /A found=0 set /A found=0
for %%j in (%retain%) do ( for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1 if "%%~nxi" == "%%j" set /A found=1
@ -23,18 +23,18 @@ if "%%~nxi" == "%%j" set /A found=1
if not !found! == 1 rmdir /Q/S "%%i" if not !found! == 1 rmdir /Q/S "%%i"
) )
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Themes\*") do ( for /D %%i in ("..\Oqtane.Server\bin\Release\net7.0\publish\wwwroot\Themes\*") do (
set /A found=0 set /A found=0
for %%j in (%retain%) do ( for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1 if "%%~nxi" == "%%j" set /A found=1
) )
if not !found! == 1 rmdir /Q/S "%%i" if not !found! == 1 rmdir /Q/S "%%i"
) )
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json" del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json" ren "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json" del "..\Oqtane.Server\bin\Release\net7.0\publish\appsettings.json"
del "..\Oqtane.Server\bin\Release\net6.0\publish\web.config" del "..\Oqtane.Server\bin\Release\net7.0\publish\web.config"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
dotnet clean -c Release ..\Oqtane.Updater.sln dotnet clean -c Release ..\Oqtane.Updater.sln
dotnet build -c Release ..\Oqtane.Updater.sln dotnet build -c Release ..\Oqtane.Updater.sln

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.4.3.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net7.0\publish\*" -DestinationPath "Oqtane.Framework.4.0.0.Upgrade.zip" -Force

View File

@ -33,8 +33,10 @@ namespace Oqtane.Controllers
private readonly IHttpContextAccessor _accessor; private readonly IHttpContextAccessor _accessor;
private readonly IAliasRepository _aliases; private readonly IAliasRepository _aliases;
private readonly ILogger<InstallationController> _filelogger; private readonly ILogger<InstallationController> _filelogger;
private readonly ITenantManager _tenantManager;
private readonly ServerStateManager _serverState;
public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger) public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger<InstallationController> filelogger, ITenantManager tenantManager, ServerStateManager serverState)
{ {
_configManager = configManager; _configManager = configManager;
_installationManager = installationManager; _installationManager = installationManager;
@ -44,6 +46,8 @@ namespace Oqtane.Controllers
_accessor = accessor; _accessor = accessor;
_aliases = aliases; _aliases = aliases;
_filelogger = filelogger; _filelogger = filelogger;
_tenantManager = tenantManager;
_serverState = serverState;
} }
// POST api/<controller> // POST api/<controller>
@ -115,7 +119,9 @@ namespace Oqtane.Controllers
private List<ClientAssembly> GetAssemblyList() private List<ClientAssembly> GetAssemblyList()
{ {
return _cache.GetOrCreate("assemblieslist", entry => int siteId = _tenantManager.GetAlias().SiteId;
return _cache.GetOrCreate($"assemblieslist:{siteId}", entry =>
{ {
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblyList = new List<ClientAssembly>(); var assemblyList = new List<ClientAssembly>();
@ -126,78 +132,43 @@ namespace Oqtane.Controllers
{ {
hashfilename = false; hashfilename = false;
} }
// get list of assemblies which should be downloaded to client
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies();
var list = assemblies.Select(a => a.GetName().Name).ToList();
// populate assemblies // get site assemblies which should be downloaded to client
for (int i = 0; i < list.Count; i++) var assemblies = _serverState.GetServerState(siteId).Assemblies;
// populate assembly list
foreach (var assembly in assemblies)
{ {
assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll"), hashfilename)); if (assembly != Constants.ClientId)
{
var filepath = Path.Combine(binFolder, assembly) + ".dll";
if (System.IO.File.Exists(filepath))
{
assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, assembly + ".dll"), hashfilename));
}
}
} }
// insert satellite assemblies at beginning of list // insert satellite assemblies at beginning of list
foreach (var culture in _localizationManager.GetInstalledCultures()) foreach (var culture in _localizationManager.GetInstalledCultures())
{ {
var assembliesFolderPath = Path.Combine(binFolder, culture); if (culture != Constants.DefaultCulture)
if (culture == Constants.DefaultCulture)
{ {
continue; var assembliesFolderPath = Path.Combine(binFolder, culture);
} if (Directory.Exists(assembliesFolderPath))
if (Directory.Exists(assembliesFolderPath))
{
foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath))
{ {
assemblyList.Insert(0, new ClientAssembly(resourceFile, hashfilename)); foreach (var assembly in assemblies)
}
}
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
}
}
// insert module and theme dependencies at beginning of list
foreach (var assembly in assemblies)
{
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))))
{
var instance = Activator.CreateInstance(type) as IModule;
foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{
var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll");
if (System.IO.File.Exists(filepath))
{ {
if (!assemblyList.Exists(item => item.FilePath == filepath)) var filepath = Path.Combine(assembliesFolderPath, assembly) + ".resources.dll";
if (System.IO.File.Exists(filepath))
{ {
assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename)); assemblyList.Insert(0, new ClientAssembly(Path.Combine(assembliesFolderPath, assembly + ".resources.dll"), hashfilename));
} }
} }
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"));
}
} }
} else
foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))))
{
var instance = Activator.CreateInstance(type) as ITheme;
foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse())
{ {
var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll"); _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist"));
if (System.IO.File.Exists(filepath))
{
if (!assemblyList.Exists(item => item.FilePath == filepath))
{
assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename));
}
}
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist"));
}
} }
} }
} }
@ -240,21 +211,24 @@ namespace Oqtane.Controllers
{ {
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
if (System.IO.File.Exists(assembly.FilePath)) if (Path.GetFileNameWithoutExtension(assembly.FilePath) != Constants.ClientId)
{ {
using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) if (System.IO.File.Exists(assembly.FilePath))
using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
{ {
filestream.CopyTo(entrystream); using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName).Open())
{
filestream.CopyTo(entrystream);
}
} }
} var pdb = assembly.FilePath.Replace(".dll", ".pdb");
var pdb = assembly.FilePath.Replace(".dll", ".pdb"); if (System.IO.File.Exists(pdb))
if (System.IO.File.Exists(pdb))
{
using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
{ {
filestream.CopyTo(entrystream); using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read))
using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open())
{
filestream.CopyTo(entrystream);
}
} }
} }
} }

View File

@ -123,14 +123,14 @@ namespace Oqtane.Controllers
if (moduleDefinition.Template.ToLower().Contains("internal")) if (moduleDefinition.Template.ToLower().Contains("internal"))
{ {
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", Oqtane.Client"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ", Oqtane.Client";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
} }
else else
{ {
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, moduleDefinition.Owner + "." + moduleDefinition.Name, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, moduleDefinition.Owner + ".Module." + moduleDefinition.Name, Path.DirectorySeparatorChar.ToString());
moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + "." + moduleDefinition.Name + ", " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Client.Oqtane"; moduleDefinition.ModuleDefinitionName = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ", " + moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Client.Oqtane";
moduleDefinition.ServerManagerType = moduleDefinition.Owner + "." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + "." + moduleDefinition.Name + ".Server.Oqtane"; moduleDefinition.ServerManagerType = moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + ".Module." + moduleDefinition.Name + ".Server.Oqtane";
} }
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, moduleDefinition);
@ -255,10 +255,10 @@ namespace Oqtane.Controllers
_modules.DeleteModule(moduleToRemove.ModuleId); _modules.DeleteModule(moduleToRemove.ModuleId);
} }
// remove module definition // remove module definition
_moduleDefinitions.DeleteModuleDefinition(id); _moduleDefinitions.DeleteModuleDefinition(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, moduledefinition.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
} }
else else
@ -331,9 +331,9 @@ namespace Oqtane.Controllers
if (moduleDefinition.Version == "local") if (moduleDefinition.Version == "local")
{ {
text = text.Replace("[FrameworkVersion]", Constants.Version); text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>"); text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Server.dll</HintPath></Reference>"); text = text.Replace("[ServerReference]", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Server.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>"); text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll</HintPath></Reference>");
} }
else else
{ {

View File

@ -10,6 +10,8 @@ using Oqtane.Enums;
using Oqtane.Extensions; using Oqtane.Extensions;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Modules.Admin.Users;
using System.IO;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -73,19 +75,11 @@ namespace Oqtane.Controllers
return pages; return pages;
} }
// GET api/<controller>/5?userid=x // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
public Page Get(int id, string userid) public Page Get(int id)
{ {
Page page = null; var page = _pages.GetPage(id);
if (string.IsNullOrEmpty(userid))
{
page = _pages.GetPage(id);
}
else
{
page = _pages.GetPage(id, int.Parse(userid));
}
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, page.PermissionList))
{ {
page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId) page.Settings = _settings.GetSettings(EntityNames.Page, page.PageId)
@ -95,7 +89,7 @@ namespace Oqtane.Controllers
} }
else else
{ {
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId} {UserId}", id, userid); _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Page Get Attempt {PageId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null; return null;
} }
@ -180,29 +174,30 @@ namespace Oqtane.Controllers
{ {
Page page = null; Page page = null;
Page parent = _pages.GetPage(id); Page parent = _pages.GetPage(id);
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && _userPermissions.GetUser(User).UserId == int.Parse(userid)) User user = _userPermissions.GetUser(User);
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
{ {
page = new Page(); page = new Page();
page.SiteId = parent.SiteId; page.SiteId = parent.SiteId;
page.Name = parent.Name;
page.Title = parent.Title;
page.Path = parent.Path;
page.ParentId = parent.PageId; page.ParentId = parent.PageId;
page.Name = user.Username;
page.Path = parent.Path + "/" + page.Name;
page.Title = parent.Name + " - " + page.Name;
page.Order = 0; page.Order = 0;
page.IsNavigation = false; page.IsNavigation = false;
page.Url = ""; page.Url = "";
page.ThemeType = parent.ThemeType; page.ThemeType = parent.ThemeType;
page.DefaultContainerType = parent.DefaultContainerType; page.DefaultContainerType = parent.DefaultContainerType;
page.Icon = parent.Icon; page.Icon = parent.Icon;
page.PermissionList = new List<Permission> { page.PermissionList = new List<Permission>()
{
new Permission(PermissionNames.View, int.Parse(userid), true), new Permission(PermissionNames.View, int.Parse(userid), true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, int.Parse(userid), true) new Permission(PermissionNames.Edit, int.Parse(userid), true)
}; };
page.IsPersonalizable = false; page.IsPersonalizable = false;
page.UserId = int.Parse(userid); page.UserId = int.Parse(userid);
page = _pages.AddPage(page); page = _pages.AddPage(page);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
// copy modules // copy modules
List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList(); List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
@ -213,8 +208,10 @@ namespace Oqtane.Controllers
module.PageId = page.PageId; module.PageId = page.PageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName; module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false; module.AllPages = false;
module.PermissionList = new List<Permission> { module.PermissionList = new List<Permission>()
{
new Permission(PermissionNames.View, int.Parse(userid), true), new Permission(PermissionNames.View, int.Parse(userid), true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, int.Parse(userid), true) new Permission(PermissionNames.Edit, int.Parse(userid), true)
}; };
module = _modules.AddModule(module); module = _modules.AddModule(module);
@ -235,6 +232,9 @@ namespace Oqtane.Controllers
_pageModules.AddPageModule(pagemodule); _pageModules.AddPageModule(pagemodule);
} }
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
} }
else else
{ {

View File

@ -200,6 +200,8 @@ namespace Oqtane.Controllers
case EntityNames.Tenant: case EntityNames.Tenant:
case EntityNames.ModuleDefinition: case EntityNames.ModuleDefinition:
case EntityNames.Host: case EntityNames.Host:
case EntityNames.Job:
case EntityNames.Theme:
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Host); authorized = User.IsInRole(RoleNames.Host);
@ -262,6 +264,8 @@ namespace Oqtane.Controllers
case EntityNames.Tenant: case EntityNames.Tenant:
case EntityNames.ModuleDefinition: case EntityNames.ModuleDefinition:
case EntityNames.Host: case EntityNames.Host:
case EntityNames.Job:
case EntityNames.Theme:
filter = !User.IsInRole(RoleNames.Host); filter = !User.IsInRole(RoleNames.Host);
break; break;
case EntityNames.Site: case EntityNames.Site:

View File

@ -21,6 +21,7 @@ namespace Oqtane.Controllers
{ {
private readonly ISiteRepository _sites; private readonly ISiteRepository _sites;
private readonly IPageRepository _pages; private readonly IPageRepository _pages;
private readonly IThemeRepository _themes;
private readonly IModuleRepository _modules; private readonly IModuleRepository _modules;
private readonly IPageModuleRepository _pageModules; private readonly IPageModuleRepository _pageModules;
private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly IModuleDefinitionRepository _moduleDefinitions;
@ -32,10 +33,11 @@ namespace Oqtane.Controllers
private readonly IMemoryCache _cache; private readonly IMemoryCache _cache;
private readonly Alias _alias; private readonly Alias _alias;
public SiteController(ISiteRepository sites, IPageRepository pages, IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache) public SiteController(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IModuleRepository modules, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IMemoryCache cache)
{ {
_sites = sites; _sites = sites;
_pages = pages; _pages = pages;
_themes = themes;
_modules = modules; _modules = modules;
_pageModules = pageModules; _pageModules = pageModules;
_moduleDefinitions = moduleDefinitions; _moduleDefinitions = moduleDefinitions;
@ -144,6 +146,9 @@ namespace Oqtane.Controllers
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture); var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) }); site.Languages.Add(new Language { Code = defaultCulture.Name, Name = defaultCulture.DisplayName, Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
// themes
site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList());
return site; return site;
} }
else else

View File

@ -19,7 +19,6 @@ namespace Oqtane.Controllers
// GET: api/<controller> // GET: api/<controller>
[HttpGet] [HttpGet]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<SiteTemplate> Get() public IEnumerable<SiteTemplate> Get()
{ {
return _siteTemplates.GetSiteTemplates(); return _siteTemplates.GetSiteTemplates();

View File

@ -12,6 +12,8 @@ using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.Text.Json; using System.Text.Json;
using System.Net; using System.Net;
using System.Reflection.Metadata;
using System;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -23,14 +25,20 @@ namespace Oqtane.Controllers
private readonly IThemeRepository _themes; private readonly IThemeRepository _themes;
private readonly IInstallationManager _installationManager; private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment; private readonly IWebHostEnvironment _environment;
private readonly ITenantManager _tenantManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; 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; _themes = themes;
_installationManager = installationManager; _installationManager = installationManager;
_environment = environment; _environment = environment;
_tenantManager = tenantManager;
_syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias();
} }
// GET: api/<controller> // GET: api/<controller>
@ -41,6 +49,41 @@ namespace Oqtane.Controllers
return _themes.GetThemes(); 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 // DELETE api/<controller>/xxx
[HttpDelete("{themename}")] [HttpDelete("{themename}")]
[Authorize(Roles = RoleNames.Host)] [Authorize(Roles = RoleNames.Host)]
@ -74,7 +117,9 @@ namespace Oqtane.Controllers
} }
// remove theme // remove theme
_themes.DeleteTheme(theme.ThemeName); _themes.DeleteTheme(theme.ThemeId);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Theme, theme.ThemeId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, theme.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
} }
else else
@ -128,12 +173,12 @@ namespace Oqtane.Controllers
if (theme.Template.ToLower().Contains("internal")) if (theme.Template.ToLower().Contains("internal"))
{ {
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
theme.ThemeName = theme.Owner + "." + theme.Name + ", Oqtane.Client"; theme.ThemeName = theme.Owner + ".Theme." + theme.Name + ", Oqtane.Client";
} }
else else
{ {
rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, theme.Owner + "." + theme.Name, Path.DirectorySeparatorChar.ToString()); rootPath = Utilities.PathCombine(rootFolder.Parent.FullName, theme.Owner + ".Theme." + theme.Name, Path.DirectorySeparatorChar.ToString());
theme.ThemeName = theme.Owner + "." + theme.Name + ", " + theme.Owner + "." + theme.Name + ".Client.Oqtane"; theme.ThemeName = theme.Owner + ".Theme." + theme.Name + ", " + theme.Owner + ".Theme." + theme.Name + ".Client.Oqtane";
} }
ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme); ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, rootFolder.Name, templatePath, theme);
@ -180,8 +225,8 @@ namespace Oqtane.Controllers
if (theme.Version == "local") if (theme.Version == "local")
{ {
text = text.Replace("[FrameworkVersion]", Constants.Version); text = text.Replace("[FrameworkVersion]", Constants.Version);
text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Client.dll</HintPath></Reference>"); text = text.Replace("[ClientReference]", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Client.dll</HintPath></Reference>");
text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net6.0\\Oqtane.Shared.dll</HintPath></Reference>"); text = text.Replace("[SharedReference]", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net7.0\\Oqtane.Shared.dll</HintPath></Reference>");
} }
else else
{ {

View File

@ -61,6 +61,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<ILoggerProvider, FileLoggerProvider>(); services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
services.AddSingleton<AutoValidateAntiforgeryTokenFilter>(); services.AddSingleton<AutoValidateAntiforgeryTokenFilter>();
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>(); services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
services.AddSingleton<ServerStateManager>();
return services; return services;
} }
@ -200,9 +201,12 @@ namespace Microsoft.Extensions.DependencyInjection
// set the cookies to allow HttpClient API calls to be authenticated // set the cookies to allow HttpClient API calls to be authenticated
var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>(); var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies) if (httpContextAccessor.HttpContext != null)
{ {
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value); foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value);
}
} }
return client; return client;

View File

@ -199,10 +199,6 @@ namespace Oqtane.Infrastructure
if (result.Success) if (result.Success)
{ {
result = CreateSite(install); result = CreateSite(install);
if (result.Success)
{
result = MigrateSites();
}
} }
} }
} }
@ -685,77 +681,6 @@ namespace Oqtane.Infrastructure
return result; return result;
} }
private Installation MigrateSites()
{
var result = new Installation { Success = false, Message = string.Empty };
// get site upgrades
Dictionary<string, Type> siteupgrades = new Dictionary<string, Type>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
foreach (var type in assembly.GetTypes(typeof(ISiteMigration)))
{
if (Attribute.IsDefined(type, typeof(SiteMigrationAttribute)))
{
var attribute = (SiteMigrationAttribute)Attribute.GetCustomAttribute(type, typeof(SiteMigrationAttribute));
siteupgrades.Add(attribute.AliasName + " " + attribute.Version, type);
}
}
}
// execute site upgrades
if (siteupgrades.Count > 0)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var aliases = scope.ServiceProvider.GetRequiredService<IAliasRepository>();
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
var logger = scope.ServiceProvider.GetRequiredService<ILogManager>();
foreach (var alias in aliases.GetAliases().ToList().Where(item => item.IsDefault))
{
foreach (var upgrade in siteupgrades)
{
var aliasname = upgrade.Key.Split(' ').First();
// in the future this equality condition could use RegEx to allow for more flexible matching
if (string.Equals(alias.Name, aliasname, StringComparison.OrdinalIgnoreCase))
{
tenantManager.SetTenant(alias.TenantId);
var site = sites.GetSites().FirstOrDefault(item => item.SiteId == alias.SiteId);
if (site != null)
{
var version = upgrade.Key.Split(' ').Last();
if (string.IsNullOrEmpty(site.Version) || Version.Parse(version) > Version.Parse(site.Version))
{
try
{
var obj = ActivatorUtilities.CreateInstance(scope.ServiceProvider, upgrade.Value) as ISiteMigration;
if (obj != null)
{
obj.Up(site, alias);
site.Version = version;
sites.UpdateSite(site);
logger.Log(alias.SiteId, Shared.LogLevel.Information, "Site Migration", LogFunction.Other, "Site Migrated Successfully To Version {version} For {Alias}", version, alias.Name);
}
}
catch (Exception ex)
{
logger.Log(alias.SiteId, Shared.LogLevel.Error, "Site Migration", LogFunction.Other, ex, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version}", upgrade.Value, alias.Name, version);
}
}
}
}
}
}
}
}
result.Success = true;
return result;
}
private string DenormalizeConnectionString(string connectionString) private string DenormalizeConnectionString(string connectionString)
{ {
var dataDirectory = AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString(); var dataDirectory = AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString();

View File

@ -0,0 +1,24 @@
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Infrastructure.EventSubscribers
{
public class CacheInvalidationEventSubscriber : IEventSubscriber
{
private readonly IMemoryCache _cache;
public CacheInvalidationEventSubscriber(IMemoryCache cache)
{
_cache = cache;
}
public void EntityChanged(SyncEvent syncEvent)
{
if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Refresh)
{
_cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}");
}
}
}
}

View File

@ -1,45 +0,0 @@
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Extensions.Hosting;
using Oqtane.Models;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class CacheInvalidationHostedService : IHostedService
{
private readonly ISyncManager _syncManager;
private readonly IMemoryCache _cache;
public CacheInvalidationHostedService(ISyncManager syncManager, IMemoryCache cache)
{
_syncManager = syncManager;
_cache = cache;
}
void EntityChanged(object sender, SyncEvent e)
{
if (e.EntityName == "Site" && e.Action == SyncEventActions.Refresh)
{
_cache.Remove($"site:{e.TenantId}:{e.EntityId}");
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_syncManager.EntityChanged += EntityChanged;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
_syncManager.EntityChanged -= EntityChanged;
}
}
}

View File

@ -0,0 +1,80 @@
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Extensions.Hosting;
using Oqtane.Models;
using System;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class EventDistributorHostedService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
private readonly ISyncManager _syncManager;
private readonly IMemoryCache _cache;
private readonly ILogger<EventDistributorHostedService> _filelogger;
public EventDistributorHostedService(IServiceProvider serviceProvider, ISyncManager syncManager, IMemoryCache cache, ILogger<EventDistributorHostedService> filelogger)
{
_serviceProvider = serviceProvider;
_syncManager = syncManager;
_cache = cache;
_filelogger = filelogger;
}
void EntityChanged(object sender, SyncEvent syncEvent)
{
List<Type> eventSubscribers = _cache.GetOrCreate($"eventsubscribers", entry =>
{
eventSubscribers = new List<Type>();
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
foreach (var type in assembly.GetTypes(typeof(IEventSubscriber)))
{
eventSubscribers.Add(type);
}
}
entry.Priority = CacheItemPriority.NeverRemove;
return eventSubscribers;
});
foreach (var type in eventSubscribers)
{
try
{
var obj = ActivatorUtilities.CreateInstance(_serviceProvider, type) as IEventSubscriber;
if (obj != null)
{
obj.EntityChanged(syncEvent);
}
}
catch (Exception ex)
{
_filelogger.LogError(Utilities.LogMessage(this, $"Error In EventSubscriber {type.AssemblyQualifiedName} - {ex.Message}"));
}
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_syncManager.EntityChanged += EntityChanged;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public void Dispose()
{
_syncManager.EntityChanged -= EntityChanged;
}
}
}

View File

@ -0,0 +1,9 @@
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public interface IEventSubscriber
{
void EntityChanged(SyncEvent syncEvent);
}
}

View File

@ -1,11 +1,12 @@
using Oqtane.Models; using Oqtane.Models;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Oqtane.Infrastructure namespace Oqtane.Infrastructure
{ {
// this interface is for declaring global resources and is useful for scenarios where you want to declare resources in a single location for the entire application
public interface IHostResources public interface IHostResources
{ {
[Obsolete("IHostResources is deprecated. Use module or theme scoped Resources in conjunction with ResourceLevel.Site instead.", false)]
List<Resource> Resources { get; } // identifies global resources for an application List<Resource> Resources { get; } // identifies global resources for an application
} }
} }

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
@ -38,17 +39,23 @@ namespace Oqtane.Infrastructure
{ {
await Task.Yield(); // required so that this method does not block startup await Task.Yield(); // required so that this method does not block startup
try while (!stoppingToken.IsCancellationRequested)
{ {
while (!stoppingToken.IsCancellationRequested) using (var scope = _serviceScopeFactory.CreateScope())
{ {
using (var scope = _serviceScopeFactory.CreateScope()) ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{ {
var jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
var jobLogs = scope.ServiceProvider.GetRequiredService<IJobLogRepository>();
var tenantRepository = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
// get name of job // get name of job
string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
// load jobs and find current job // load jobs and find current job
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault(); Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault();
if (job != null && job.IsEnabled && !job.IsExecuting) if (job != null && job.IsEnabled && !job.IsExecuting)
{ {
@ -73,7 +80,9 @@ namespace Oqtane.Infrastructure
// determine if the job should be run // determine if the job should be run
if (NextExecution <= DateTime.UtcNow && (job.EndDate == null || job.EndDate >= DateTime.UtcNow)) if (NextExecution <= DateTime.UtcNow && (job.EndDate == null || job.EndDate >= DateTime.UtcNow))
{ {
IJobLogRepository jobLogs = scope.ServiceProvider.GetRequiredService<IJobLogRepository>(); // update the job to indicate it is running
job.IsExecuting = true;
jobs.UpdateJob(job);
// create a job log entry // create a job log entry
JobLog log = new JobLog(); JobLog log = new JobLog();
@ -84,16 +93,10 @@ namespace Oqtane.Infrastructure
log.Notes = ""; log.Notes = "";
log = jobLogs.AddJobLog(log); log = jobLogs.AddJobLog(log);
// update the job to indicate it is running
job.IsExecuting = true;
jobs.UpdateJob(job);
// execute the job // execute the job
try try
{ {
var notes = ""; var notes = "";
var tenantRepository = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
var tenantManager = scope.ServiceProvider.GetRequiredService<ITenantManager>();
foreach (var tenant in tenantRepository.GetTenants()) foreach (var tenant in tenantRepository.GetTenants())
{ {
// set tenant and execute job // set tenant and execute job
@ -133,16 +136,19 @@ namespace Oqtane.Infrastructure
} }
} }
} }
catch (Exception ex)
// wait 1 minute {
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); // can occur during the initial installation because the database has not yet been created
if (!ex.Message.Contains("No database provider has been configured for this DbContext"))
{
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Executing Scheduled Job: {Name} - {ex}"));
}
}
} }
}
catch
{
// can occur during the initial installation as there is no DBContext
}
// wait 1 minute
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
} }
private DateTime CalculateNextExecution(DateTime nextExecution, Job job) private DateTime CalculateNextExecution(DateTime nextExecution, Job job)
@ -191,9 +197,11 @@ namespace Oqtane.Infrastructure
public Task StartAsync(CancellationToken cancellationToken) public Task StartAsync(CancellationToken cancellationToken)
{ {
try using (var scope = _serviceScopeFactory.CreateScope())
{ {
using (var scope = _serviceScopeFactory.CreateScope()) ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{ {
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>(); IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
@ -207,7 +215,7 @@ namespace Oqtane.Infrastructure
} }
else else
{ {
// auto registration - job will not run on initial installation but will run after restart // auto registration - job will not run on initial installation due to no DBContext but will run after restart
job = new Job { JobType = jobTypeName }; job = new Job { JobType = jobTypeName };
// optional HostedServiceBase properties // optional HostedServiceBase properties
@ -233,17 +241,21 @@ namespace Oqtane.Infrastructure
jobs.AddJob(job); jobs.AddJob(job);
} }
} }
catch (Exception ex)
_executingTask = ExecuteAsync(_cancellationTokenSource.Token);
if (_executingTask.IsCompleted)
{ {
return _executingTask; // can occur during the initial installation because the database has not yet been created
if (!ex.Message.Contains("No database provider has been configured for this DbContext"))
{
_filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Starting Scheduled Job: {Name} - {ex}"));
}
} }
} }
catch
_executingTask = ExecuteAsync(_cancellationTokenSource.Token);
if (_executingTask.IsCompleted)
{ {
// can occur during the initial installation because this method is called during startup and the database has not yet been created return _executingTask;
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -251,9 +263,11 @@ namespace Oqtane.Infrastructure
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)
{ {
try using (var scope = _serviceScopeFactory.CreateScope())
{ {
using (var scope = _serviceScopeFactory.CreateScope()) ILogger<HostedServiceBase> _filelogger = scope.ServiceProvider.GetRequiredService<ILogger<HostedServiceBase>>();
try
{ {
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>(); IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
@ -266,10 +280,11 @@ namespace Oqtane.Infrastructure
jobs.UpdateJob(job); jobs.UpdateJob(job);
} }
} }
} catch (Exception ex)
catch {
{ // error updating the job
// error updating the job _filelogger.LogError(Utilities.LogMessage(this, $"An Error Occurred Stopping Scheduled Job: {Name} - {ex}"));
}
} }
// stop called without start // stop called without start

View File

@ -42,119 +42,127 @@ namespace Oqtane.Infrastructure
// get site settings // get site settings
List<Setting> sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); List<Setting> sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList();
Dictionary<string, string> settings = GetSettings(sitesettings); Dictionary<string, string> settings = GetSettings(sitesettings);
if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && if (!settings.ContainsKey("SMTPEnabled") || settings["SMTPEnabled"] == "True")
settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" &&
settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" &&
settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "")
{ {
// construct SMTP Client if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" &&
var client = new SmtpClient() settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" &&
settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" &&
settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "")
{ {
DeliveryMethod = SmtpDeliveryMethod.Network, // construct SMTP Client
UseDefaultCredentials = false, var client = new SmtpClient()
Host = settings["SMTPHost"],
Port = int.Parse(settings["SMTPPort"]),
EnableSsl = bool.Parse(settings["SMTPSSL"])
};
if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
{
client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
}
// iterate through undelivered notifications
int sent = 0;
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications)
{
// get sender and receiver information from user object if not provided
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
{ {
var user = userRepository.GetUser(notification.FromUserId.Value); DeliveryMethod = SmtpDeliveryMethod.Network,
if (user != null) UseDefaultCredentials = false,
{ Host = settings["SMTPHost"],
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; Port = int.Parse(settings["SMTPPort"]),
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; EnableSsl = bool.Parse(settings["SMTPSSL"])
} };
} if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
{ {
var user = userRepository.GetUser(notification.ToUserId.Value); client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
if (user != null)
{
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
}
} }
// validate recipient // iterate through undelivered notifications
if (string.IsNullOrEmpty(notification.ToEmail)) int sent = 0;
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications)
{ {
log += "Recipient Missing For NotificationId: " + notification.NotificationId + "<br />"; // get sender and receiver information from user object if not provided
notification.IsDeleted = true; if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
notificationRepository.UpdateNotification(notification);
}
else
{
MailMessage mailMessage = new MailMessage();
// sender
if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail))
{ {
if (!string.IsNullOrEmpty(notification.FromDisplayName)) var user = userRepository.GetUser(notification.FromUserId.Value);
if (user != null)
{ {
mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName); notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
}
}
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
{
var user = userRepository.GetUser(notification.ToUserId.Value);
if (user != null)
{
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
}
}
// validate recipient
if (string.IsNullOrEmpty(notification.ToEmail))
{
log += "Recipient Missing For NotificationId: " + notification.NotificationId + "<br />";
notification.IsDeleted = true;
notificationRepository.UpdateNotification(notification);
}
else
{
MailMessage mailMessage = new MailMessage();
// sender
if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail))
{
if (!string.IsNullOrEmpty(notification.FromDisplayName))
{
mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName);
}
else
{
mailMessage.From = new MailAddress(notification.FromEmail);
}
} }
else else
{ {
mailMessage.From = new MailAddress(notification.FromEmail); mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name);
}
// recipient
if (!string.IsNullOrEmpty(notification.ToDisplayName))
{
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
}
else
{
mailMessage.To.Add(new MailAddress(notification.ToEmail));
}
// subject
mailMessage.Subject = notification.Subject;
//body
mailMessage.Body = notification.Body;
// encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
mailMessage.IsBodyHtml = true;
// send mail
try
{
client.Send(mailMessage);
sent++;
notification.IsDelivered = true;
notification.DeliveredOn = DateTime.UtcNow;
notificationRepository.UpdateNotification(notification);
}
catch (Exception ex)
{
// error
log += ex.Message + "<br />";
} }
} }
else
{
mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name);
}
// recipient
if (!string.IsNullOrEmpty(notification.ToDisplayName))
{
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
}
else
{
mailMessage.To.Add(new MailAddress(notification.ToEmail));
}
// subject
mailMessage.Subject = notification.Subject;
//body
mailMessage.Body = notification.Body;
// encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
// send mail
try
{
client.Send(mailMessage);
sent++;
notification.IsDelivered = true;
notification.DeliveredOn = DateTime.UtcNow;
notificationRepository.UpdateNotification(notification);
}
catch (Exception ex)
{
// error
log += ex.Message + "<br />";
}
} }
log += "Notifications Delivered: " + sent + "<br />";
}
else
{
log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />";
} }
log += "Notifications Delivered: " + sent + "<br />";
} }
else else
{ {
log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />"; log += "SMTP Disabled In Site Settings" + "<br />";
} }
} }

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public class ServerState
{
public int SiteId { get; set; }
public List<string> Assemblies { get; set; } = new List<string>();
public List<Resource>Scripts { get; set; } = new List<Resource>();
public bool IsMigrated { get; set; } = false;
}
}

View File

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
// singleton
public class ServerStateManager
{
private List<ServerState> _serverStates { get; set; }
public ServerStateManager()
{
_serverStates = new List<ServerState>();
}
public ServerState GetServerState(int siteId)
{
var serverState = _serverStates.FirstOrDefault(item => item.SiteId == siteId);
if (serverState == null)
{
serverState = new ServerState();
serverState.SiteId = siteId;
serverState.Assemblies = new List<string>();
serverState.Scripts = new List<Resource>();
return serverState;
}
else
{
return serverState;
}
}
public void SetServerState(int siteId, ServerState serverState)
{
var serverstate = _serverStates.FirstOrDefault(item => item.SiteId == siteId);
if (serverstate == null)
{
serverState.SiteId = siteId;
_serverStates.Add(serverState);
}
else
{
serverstate.Assemblies = serverState.Assemblies;
serverstate.Scripts = serverState.Scripts;
}
}
}
}

View File

@ -57,10 +57,10 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}, },
Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>modular application framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET Core. It leverages the Blazor component model to compose a <b>fully dynamic</b> web development experience which can be hosted either client-side or server-side. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles.</p>" + Content = "<p><a href=\"https://www.oqtane.org\" target=\"_new\">Oqtane</a> is an open source <b>CMS</b> and <b>Application Framework</b> that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a <b>fully dynamic</b> digital experience. Whether you are looking for a platform to <b>accelerate your web development</b> efforts, or simply interested in exploring the anatomy of a large-scale Blazor application, Oqtane provides a solid foundation based on proven enterprise architectural principles and patterns.</p>" +
"<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org/Community\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" + "<p align=\"center\"><a href=\"https://www.oqtane.org\" target=\"_new\"><img class=\"img-fluid\" src=\"oqtane-glow.png\"></a></p><p align=\"center\"><a class=\"btn btn-primary\" href=\"https://www.oqtane.org\" target=\"_new\">Join Our Community</a>&nbsp;&nbsp;<a class=\"btn btn-primary\" href=\"https://github.com/oqtane/oqtane.framework\" target=\"_new\">Clone Our Repo</a></p>" +
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page applications using .NET and C#. Blazor applications can be hosted in a variety of ways. Blazor Server uses SignalR (WebSockets) to host your application on a web server and provide a responsive and robust development experience. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins in order for applications to run natively in a web browser. Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on mobile and desktop devices. Razor components can be used with all of the hosting models without any modification.</p>" + "<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is an open source and cross-platform web UI framework for building single-page applications using .NET and C#. Blazor applications can be hosted in a variety of ways. Blazor Server uses SignalR (WebSockets) to host your application on a web server and provide a responsive and robust development experience. Blazor WebAssembly relies on Wasm, an open web standard that does not require plugins in order for applications to run natively in a web browser. Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on mobile and desktop devices. Razor components can be shared across all of the hosting models without any modification.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">.NET Core</a>, the popular cross platform web development framework from Microsoft that extends the <a href=\"https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet\" target=\"_new\" >.NET developer platform</a> with tools and libraries for building web apps.</p>" "<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET</a>, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.</p>"
}, },
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default, new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default,
PermissionList = new List<Permission> { PermissionList = new List<Permission> {
@ -141,7 +141,7 @@ namespace Oqtane.SiteTemplates
Path = "develop", Path = "develop",
Icon = "oi oi-wrench", Icon = "oi oi-wrench",
IsNavigation = true, IsNavigation = true,
IsPersonalizable = true, IsPersonalizable = false,
PermissionList = new List<Permission> { PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Host, true), new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true) new Permission(PermissionNames.Edit, RoleNames.Host, true)

View File

@ -192,7 +192,7 @@ namespace Oqtane.Infrastructure
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>(); var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in sites.GetSites().ToList()) foreach (Site site in sites.GetSites().ToList())
{ {
sites.CreatePages(site, pageTemplates); sites.CreatePages(site, pageTemplates, null);
} }
} }
@ -243,7 +243,7 @@ namespace Oqtane.Infrastructure
{ {
if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "404").Any()) if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "404").Any())
{ {
sites.CreatePages(site, pageTemplates); sites.CreatePages(site, pageTemplates, null);
} }
} }
} }

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

@ -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.02")]
public class AddThemeName : MultiDatabaseMigration
{
public AddThemeName(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
themeEntityBuilder.AddStringColumn("Name", 200, true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Components.Web;
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.04.00.00.01")]
public class AddHeaderContent : MultiDatabaseMigration
{
public AddHeaderContent(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.AddStringColumn("HeadContent", 4000, true);
var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase);
pageEntityBuilder.AddStringColumn("HeadContent", 4000, true);
pageEntityBuilder.UpdateColumn("HeadContent", "Meta");
pageEntityBuilder.DropColumn("Meta");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

Some files were not shown because too many files have changed in this diff Show More