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)
{ {
@ -54,12 +56,24 @@
private PageState PageState { get; set; } private PageState PageState { get; set; }
private IHttpContextAccessor accessor;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
SiteState.RemoteIPAddress = RemoteIPAddress; SiteState.RemoteIPAddress = RemoteIPAddress;
SiteState.AntiForgeryToken = AntiForgeryToken; SiteState.AntiForgeryToken = AntiForgeryToken;
SiteState.AuthorizationToken = AuthorizationToken; 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" />
@ -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>
@ -131,12 +153,18 @@
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 List<SiteTemplate> _templates;
private string _template = Constants.DefaultSiteTemplate;
private bool _register = true; 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()
{ {
// 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"]; _togglePassword = SharedLocalizer["ShowPassword"];
_toggleConfirmPassword = SharedLocalizer["ShowPassword"]; _toggleConfirmPassword = SharedLocalizer["ShowPassword"];
@ -150,6 +178,8 @@
_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,6 +33,15 @@
<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 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> </div>
</form> </form>
<Section Name="Information" ResourceKey="Information"> <Section Name="Information" ResourceKey="Information">
@ -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,13 +44,21 @@
<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)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{ {
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option> <option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
} }
} }
}
</select> </select>
</div> </div>
</div> </div>
@ -95,7 +103,6 @@
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;
@ -116,11 +123,10 @@
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> {
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null)
{
<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,15 +18,20 @@
<input id="name" class="form-control" @bind="@_name" required /> <input id="name" class="form-control" @bind="@_name" required />
</div> </div>
</div> </div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row mb-1 align-items-center"> <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> <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="col-sm-9">
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required> <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages) @foreach (Page page in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
} }
}
</select> </select>
</div> </div>
</div> </div>
@ -55,6 +59,26 @@
} }
</div> </div>
</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,45 +109,6 @@
<input id="url" class="form-control" @bind="@_url" /> <input id="url" class="form-control" @bind="@_url" />
</div> </div>
</div> </div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<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>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" />
</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">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<select id="theme" 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="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" 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"> <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="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"> <div class="col-sm-9">
@ -140,8 +125,55 @@
</div> </div>
</div> </div>
</div> </div>
</Section>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<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>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" />
</div>
</div>
<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>
<div class="col-sm-9">
<select id="theme" class="form-select" @bind="@_themetype" 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="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
@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>
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions">
<div class="container"> <div class="container">
@ -160,54 +192,77 @@
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>
}
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Theme> _themeList; private bool _initialized = false;
private ElementReference form;
private bool validated = false;
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 int _pageId;
private string _name; private string _name;
private string _title;
private string _meta;
private string _path = string.Empty;
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 _path = string.Empty;
private string _url; private string _url;
private string _ispersonalizable = "False"; private string _ispersonalizable = "False";
private string _title;
private string _icon = string.Empty;
private string _themetype = string.Empty; private string _themetype = string.Empty;
private string _containertype = string.Empty; private string _containertype = string.Empty;
private string _icon = string.Empty; private string _headcontent;
private string _bodycontent;
private string _permissions = null; private string _permissions = null;
private PermissionGrid _permissionGrid; private PermissionGrid _permissionGrid;
private Type _themeSettingsType; private Type _themeSettingsType;
private object _themeSettings; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; } private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
private ElementReference form; protected Page _parent = null;
private bool validated = false;
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); {
_pageId = Int32.Parse(PageState.QueryString["id"]);
_parent = await PageService.GetPageAsync(_pageId);
if (_parent != null)
{
_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; _themetype = PageState.Site.DefaultThemeType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _themes = ThemeService.GetThemeControls(PageState.Site.Themes, _themetype);
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = PageState.Site.DefaultContainerType; _containertype = PageState.Site.DefaultContainerType;
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = PageState.Pages.Where(item => item.ParentId == null).ToList();
ThemeSettings(); 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))
{ {
@ -366,33 +403,41 @@
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
{ {

View File

@ -8,11 +8,13 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> @if (_initialized)
{
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
@if (_page.UserId == null)
{
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]> <TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
@if (_themeList != null)
{
<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>
@ -20,6 +22,8 @@
<input id="name" class="form-control" @bind="@_name" maxlength="50" required /> <input id="name" class="form-control" @bind="@_name" maxlength="50" required />
</div> </div>
</div> </div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row mb-1 align-items-center"> <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> <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="col-sm-9">
@ -27,7 +31,7 @@
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in PageState.Pages) @foreach (Page page in PageState.Pages)
{ {
if (page.PageId != _pageId) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId)
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
} }
@ -63,6 +67,30 @@
} }
</div> </div>
</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" value="@_parentid" @onchange="(e => ParentChanged(e))" disabled>
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@if (_parent != null)
{
<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="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
<div class="col-sm-9">
<select id="move" class="form-select" @bind="@_insert" disabled>
<option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</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">
@ -84,57 +112,19 @@
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label> <Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="path" class="form-control" @bind="@_path" maxlength="256"/> <input id="path" class="form-control" @bind="@_path" maxlength="256" />
</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="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label> <Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" maxlength="500"/> <input id="url" class="form-control" @bind="@_url" maxlength="500" />
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<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>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200"/>
</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">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<select id="theme" 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="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" 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> </div>
<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="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"> <div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" maxlength="50"/> <input id="icon" class="form-control" @bind="@_icon" maxlength="50" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -147,25 +137,66 @@
</div> </div>
</div> </div>
</div> </div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<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>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
</div>
</div>
<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>
<div class="col-sm-9">
<select id="theme" class="form-select" @bind="@_themetype" 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="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
@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>
<br /> <br />
<br /> <br />
<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>
}
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" PermissionList="@_permissions" @ref="_permissionGrid" />
</div> </div>
</div> </div>
}
</TabPanel> </TabPanel>
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules"> <TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
@if(_pageModules != null)
{
<Pager Items="_pageModules"> <Pager Items="_pageModules">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -180,7 +211,6 @@
<td>@context.ModuleDefinition?.Name</td> <td>@context.ModuleDefinition?.Name</td>
</Row> </Row>
</Pager> </Pager>
}
</TabPanel> </TabPanel>
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
@ -190,25 +220,67 @@
<br /> <br />
} }
</TabStrip> </TabStrip>
}
else
{
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings" Heading=@Localizer["Settings.Heading"]>
<div class="container">
<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>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
</div>
</div>
<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>
<div class="col-sm-9">
<select id="theme" class="form-select" @bind="@_themetype" 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="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
<br />
}
</TabStrip>
}
<br /> <br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>
}
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private bool _initialized = false;
private ElementReference form; private ElementReference form;
private bool validated = false; private bool validated = 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 List<Module> _pageModules;
private int _pageId; private int _pageId;
private string _name; private string _name;
private string _title;
private string _meta;
private string _path;
private string _currentparentid; private string _currentparentid;
private string _parentid = "-1"; private string _parentid = "-1";
private string _insert = "="; private string _insert = "=";
@ -216,44 +288,55 @@
private int _childid = -1; private int _childid = -1;
private string _isnavigation; private string _isnavigation;
private string _isclickable; private string _isclickable;
private string _path;
private string _url; private string _url;
private string _ispersonalizable; private string _ispersonalizable;
private string _title;
private string _icon;
private string _themetype; private string _themetype;
private string _containertype = "-"; private string _containertype = "-";
private string _icon; private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private string _headcontent;
private string _bodycontent;
private List<Permission> _permissions = null; private List<Permission> _permissions = null;
private PermissionGrid _permissionGrid;
private List<Module> _pageModules;
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;
private PermissionGrid _permissionGrid;
private Type _themeSettingsType;
private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
protected Page page; protected Page _page = null;
protected Page _parent = null;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
_children = PageState.Pages.Where(item => item.ParentId == null).ToList(); _children = PageState.Pages.Where(item => item.ParentId == null).ToList();
_themeList = await ThemeService.GetThemesAsync();
_themes = ThemeService.GetThemeControls(_themeList);
_pageId = Int32.Parse(PageState.QueryString["id"]); _pageId = Int32.Parse(PageState.QueryString["id"]);
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); _page = await PageService.GetPageAsync(_pageId);
if (page != null) if (_page != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _page.PermissionList))
{ {
_name = page.Name; _name = _page.Name;
_title = page.Title; if (_page.ParentId == null)
_meta = page.Meta; {
_path = page.Path; _parentid = "-1";
_pageModules = PageState.Modules.Where(m => m.PageId == page.PageId).ToList(); }
else
{
_parentid = _page.ParentId.ToString();
_parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
}
_currentparentid = _parentid;
_isnavigation = _page.IsNavigation.ToString();
_isclickable = _page.IsClickable.ToString();
_path = _page.Path;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
_path = "/"; _path = "/";
@ -265,42 +348,50 @@
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
} }
_url = _page.Url;
_icon = _page.Icon;
_ispersonalizable = _page.IsPersonalizable.ToString();
if (page.ParentId == null) // appearance
{ _title = _page.Title;
_parentid = "-1"; _themetype = _page.ThemeType;
} if (string.IsNullOrEmpty(_themetype) || ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
else
{
_parentid = page.ParentId.ToString();
}
_currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString();
_url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType;
if (string.IsNullOrEmpty(_themetype))
{ {
_themetype = PageState.Site.DefaultThemeType; _themetype = PageState.Site.DefaultThemeType;
} }
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _themes = ThemeService.GetThemeControls(PageState.Site.Themes, _themetype);
_containertype = page.DefaultContainerType; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = _page.DefaultContainerType;
if (string.IsNullOrEmpty(_containertype)) if (string.IsNullOrEmpty(_containertype))
{ {
_containertype = PageState.Site.DefaultContainerType; _containertype = PageState.Site.DefaultContainerType;
} }
_icon = page.Icon;
_permissions = page.PermissionList; // page content
_createdby = page.CreatedBy; _headcontent = _page.HeadContent;
_createdon = page.CreatedOn; _bodycontent = _page.BodyContent;
_modifiedby = page.ModifiedBy;
_modifiedon = page.ModifiedOn; // permissions
_deletedby = page.DeletedBy; _permissions = _page.PermissionList;
_deletedon = page.DeletedOn;
// page modules
_pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList();
// audit
_createdby = _page.CreatedBy;
_createdon = _page.CreatedOn;
_modifiedby = _page.ModifiedBy;
_modifiedon = _page.ModifiedOn;
_deletedby = _page.DeletedBy;
_deletedon = _page.DeletedOn;
ThemeSettings(); ThemeSettings();
_initialized = true;
}
else
{
await logger.LogWarning("Error Loading Page {PageId}", _pageId);
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -314,7 +405,7 @@
{ {
try try
{ {
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(page.PageId, module.ModuleId); PageModule pagemodule = await PageModuleService.GetPageModuleAsync(_page.PageId, module.ModuleId);
pagemodule.IsDeleted = true; pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await logger.LogInformation(LogFunction.Update,"Module Deleted {Title}", module.Title); await logger.LogInformation(LogFunction.Update,"Module Deleted {Title}", module.Title);
@ -372,29 +463,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;
if (PageState.QueryString.ContainsKey("cp")) // can only be displayed if invoked from Control Panel var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
{
var theme = _themeList.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);
@ -410,7 +482,6 @@
_refresh = true; _refresh = true;
} }
} }
}
private async Task SavePage() private async Task SavePage()
{ {
@ -418,16 +489,13 @@
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
{ {
Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{ {
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); string currentPath = _page.Path;
string currentPath = page.Path;
page.Name = _name; _page.Name = _name;
page.Title = _title;
if (string.IsNullOrEmpty(_path)) if (string.IsNullOrEmpty(_path))
{ {
@ -444,33 +512,33 @@
if (_parentid == "-1") if (_parentid == "-1")
{ {
page.ParentId = null; _page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path); _page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.ParentId = Int32.Parse(_parentid); _page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId); Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
if (parent.Path == string.Empty) if (parent.Path == string.Empty)
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); _page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
} }
} }
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
if (_pages.Any(item => item.Path == page.Path && item.PageId != page.PageId)) if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{ {
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return; return;
} }
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower())) if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
return; return;
} }
@ -480,49 +548,59 @@
switch (_insert) switch (_insert)
{ {
case "<<": case "<<":
page.Order = 0; _page.Order = 0;
break; break;
case "<": case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1; if (child != null) _page.Order = child.Order - 1;
break; break;
case ">": case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid); child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1; if (child != null) _page.Order = child.Order + 1;
break; break;
case ">>": case ">>":
page.Order = int.MaxValue; _page.Order = int.MaxValue;
break; break;
} }
} }
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation)); _page.IsNavigation = (_isnavigation == null || 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.Icon = _icon ?? string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) _page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.PermissionList = _permissionGrid.GetPermissionList();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null;
page.Meta = _meta;
page = await PageService.UpdatePageAsync(page); // appearance
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); _page.Title = _title;
_page.ThemeType = _themetype;
if (!string.IsNullOrEmpty(_page.ThemeType) && _page.ThemeType == PageState.Site.DefaultThemeType)
{
_page.ThemeType = string.Empty;
}
_page.DefaultContainerType = _containertype;
if (!string.IsNullOrEmpty(_page.DefaultContainerType) && _page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
_page.DefaultContainerType = string.Empty;
}
// page content
_page.HeadContent = _headcontent;
_page.BodyContent = _bodycontent;
// permissions
if (_page.UserId == null)
{
_page.PermissionList = _permissionGrid.GetPermissionList();
}
_page = await PageService.UpdatePageAsync(_page);
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
if (_currentparentid == string.Empty) if (_currentparentid == string.Empty)
{ {
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, null); await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, null);
} }
else else
{ {
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, int.Parse(_currentparentid)); await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
} }
// update child paths // update child paths
@ -530,7 +608,7 @@
{ {
foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath))) foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath)))
{ {
p.Path = p.Path.Replace(currentPath, page.Path); p.Path = p.Path.Replace(currentPath, _page.Path);
await PageService.UpdatePageAsync(p); await PageService.UpdatePageAsync(p);
} }
} }
@ -540,14 +618,14 @@
await themeSettingsControl.UpdateSettings(); await themeSettingsControl.UpdateSettings();
} }
await logger.LogInformation("Page Saved {Page}", page); await logger.LogInformation("Page Saved {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(PageState.ReturnUrl);
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl(page.Path)); NavigationManager.NavigateTo(NavigateUrl());
} }
} }
else else
@ -557,7 +635,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
} }
} }
@ -569,9 +647,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
{ {

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

@ -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 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>
</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,6 +186,15 @@
</select> </select>
</div> </div>
</div> </div>
<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"> <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">
@ -309,25 +342,22 @@
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 List<Alias> _aliases; private string _homepageid = "-";
private int _aliasid = -1; private string _isdeleted;
private string _aliasname; private string _sitemap = "";
private string _defaultalias; private string _version = "";
private string _runtime = "";
private string _prerender = "";
private int _logofileid = -1; private int _logofileid = -1;
private FileManager _logofilemanager; private FileManager _logofilemanager;
private int _faviconfileid = -1; private int _faviconfileid = -1;
private FileManager _faviconfilemanager; private FileManager _faviconfilemanager;
private string _themetype = "-"; private string _themetype = "";
private string _containertype = "-"; private string _containertype = "";
private string _admincontainertype = "-"; private string _admincontainertype = "";
private string _homepageid = "-"; private string _headcontent = string.Empty;
private string _sitemap = ""; private string _bodycontent = string.Empty;
private string _smtphost = string.Empty; private string _smtphost = string.Empty;
private string _smtpport = string.Empty; private string _smtpport = string.Empty;
private string _smtpssl = "False"; private string _smtpssl = "False";
@ -337,12 +367,19 @@
private string _togglesmtppassword = string.Empty; private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty; private string _smtpsender = string.Empty;
private string _smtprelay = "False"; private string _smtprelay = "False";
private string _smtpenabled = "True";
private string _retention = string.Empty; private string _retention = string.Empty;
private string _pwaisenabled; private string _pwaisenabled;
private int _pwaappiconfileid = -1; private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager; private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1; private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager; private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _runtime = "";
private string _prerender = "";
private string _tenant = string.Empty; private string _tenant = string.Empty;
private string _database = string.Empty; private string _database = string.Empty;
private string _connectionstring = string.Empty; private string _connectionstring = string.Empty;
@ -352,7 +389,6 @@
private DateTime _modifiedon; private DateTime _modifiedon;
private string _deletedby; private string _deletedby;
private DateTime? _deletedon; private DateTime? _deletedon;
private string _isdeleted;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -360,18 +396,19 @@
{ {
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;
_runtime = site.Runtime; if (site.HomePageId != null)
_prerender = site.RenderMode.Replace(_runtime, ""); {
_homepageid = site.HomePageId.Value.ToString();
}
_isdeleted = site.IsDeleted.ToString(); _isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml"; _sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/pages/sitemap.xml";
_version = site.Version;
await GetAliases(); // appearance
if (site.LogoFileId != null) if (site.LogoFileId != null)
{ {
_logofileid = site.LogoFileId.Value; _logofileid = site.LogoFileId.Value;
@ -381,18 +418,17 @@
{ {
_faviconfileid = site.FaviconFileId.Value; _faviconfileid = site.FaviconFileId.Value;
} }
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
_themes = ThemeService.GetThemeControls(_themeList);
_themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme; _themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
if (site.HomePageId != null) // page content
{ _headcontent = site.HeadContent;
_homepageid = site.HomePageId.Value.ToString(); _bodycontent = site.BodyContent;
}
// PWA
_pwaisenabled = site.PwaIsEnabled.ToString(); _pwaisenabled = site.PwaIsEnabled.ToString();
if (site.PwaAppIconFileId != null) if (site.PwaAppIconFileId != null)
{ {
@ -403,6 +439,7 @@
_pwasplashiconfileid = site.PwaSplashIconFileId.Value; _pwasplashiconfileid = site.PwaSplashIconFileId.Value;
} }
// SMTP
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty); _smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
@ -412,8 +449,17 @@
_togglesmtppassword = SharedLocalizer["ShowPassword"]; _togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False"); _smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30"); _retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
// aliases
await GetAliases();
// hosting model
_runtime = site.Runtime;
_prerender = site.RenderMode.Replace(_runtime, "");
// database
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
var tenants = await TenantService.GetTenantsAsync(); var tenants = await TenantService.GetTenantsAsync();
@ -427,6 +473,7 @@
} }
} }
// audit
_createdby = site.CreatedBy; _createdby = site.CreatedBy;
_createdon = site.CreatedOn; _createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy; _modifiedby = site.ModifiedBy;
@ -449,15 +496,8 @@
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);
}
else
{
_containers = new List<ThemeControl>();
}
_containertype = "-";
_admincontainertype = Constants.DefaultAdminContainer; _admincontainertype = Constants.DefaultAdminContainer;
StateHasChanged(); StateHasChanged();
} }
@ -485,17 +525,10 @@
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);
{
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.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
// appearance
site.LogoFileId = null; site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId(); var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1) if (logofileid != -1)
@ -520,8 +553,20 @@
refresh = true; // needs to be refreshed on client refresh = true; // needs to be refreshed on client
} }
site.AdminContainerType = _admincontainertype; site.AdminContainerType = _admincontainertype;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
// 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,6 +609,7 @@
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, "SMTPEnabled", _smtpenabled, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
@ -564,7 +622,7 @@
else else
{ {
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
await interop.ScrollTo(0, 0, "smooth"); await ScrollToPageTop();
} }
} }
} }
@ -633,8 +691,7 @@
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)
{ {

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
@ -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
@ -195,10 +196,18 @@
{ {
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))
{
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
@ -293,10 +294,18 @@ 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))
{
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

@ -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">
@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 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()))
{ {
@ -478,15 +482,19 @@
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,6 +52,7 @@
</div> </div>
@code { @code {
private int pageId = -1;
private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization private string resourceType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client"; // for localization
private string _scope = "page"; private string _scope = "page";
private string _login = "-"; private string _login = "-";
@ -60,6 +61,11 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
if (PageState.QueryString.ContainsKey("id"))
{
pageId = int.Parse(PageState.QueryString["id"]);
}
try try
{ {
await LoadSettings(); await LoadSettings();
@ -82,7 +88,8 @@
} }
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", "-");
@ -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,6 +4,7 @@
@if (_visible) @if (_visible)
{ {
<a id="@ModuleState.PageModuleId.ToString()"></a>
<CascadingValue Value="@ModuleState"> <CascadingValue Value="@ModuleState">
@if (_useadminborder) @if (_useadminborder)
{ {
@ -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

@ -96,7 +96,7 @@ namespace Oqtane.UI
{ {
_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

@ -43,10 +43,11 @@ else
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))
{
var pane = module.Pane;
if (module.ModuleId == PageState.ModuleId && PageState.Action != Constants.DefaultAction)
{ {
// action route needs to inject module control into specific pane
string pane = "";
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;
@ -54,16 +55,13 @@ else
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)
{
module = PageState.Modules.FirstOrDefault(item => item.ModuleId == PageState.ModuleId);
}
if (module != null)
{ {
var moduleType = Type.GetType(module.ModuleType); var moduleType = Type.GetType(module.ModuleType);
if (moduleType != null) if (moduleType != null)
@ -100,23 +98,10 @@ else
CreateComponent(builder, module); CreateComponent(builder, module);
} }
} }
else
{
// module type does not exist
}
}
}
} }
else else
{ {
if (PageState.ModuleId != -1) if (PageState.ModuleId == -1 || PageState.ModuleId == module.ModuleId)
{
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 // check if user is authorized to view module
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList))
@ -125,16 +110,6 @@ else
} }
} }
} }
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

@ -23,6 +23,7 @@
@code { @code {
private string _absoluteUri; private string _absoluteUri;
private bool _isInternalNavigation = false;
private bool _navigationInterceptionEnabled; private bool _navigationInterceptionEnabled;
private PageState _pagestate; private PageState _pagestate;
private string _error = ""; private string _error = "";
@ -72,6 +73,23 @@
} }
} }
private async void LocationChanged(object sender, LocationChangedEventArgs args)
{
_absoluteUri = args.Location;
_isInternalNavigation = true;
await Refresh();
}
Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
return NavigationInterception.EnableNavigationInterceptionAsync();
}
return Task.CompletedTask;
}
[SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")]
private async Task Refresh() private async Task Refresh()
{ {
@ -87,7 +105,7 @@
Route route = new Route(_absoluteUri, SiteState.Alias.Path); Route route = new Route(_absoluteUri, SiteState.Alias.Path);
int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1; int moduleid = (int.TryParse(route.ModuleId, out moduleid)) ? moduleid : -1;
var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction; var action = (!string.IsNullOrEmpty(route.Action)) ? route.Action : Constants.DefaultAction;
var querystring = ParseQueryString(route.Query); var querystring = Utilities.ParseQueryString(route.Query);
var returnurl = ""; var returnurl = "";
if (querystring.ContainsKey("returnurl")) if (querystring.ContainsKey("returnurl"))
{ {
@ -203,6 +221,23 @@
page = site.Pages.FirstOrDefault(); 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);
}
}
}
if (page != null) if (page != null)
{ {
@ -210,10 +245,10 @@
if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList)) if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList))
{ {
// load additional metadata for current page // load additional metadata for current page
page = await ProcessPage(page, site, user); page = ProcessPage(page, site, user, SiteState.Alias);
// load additional metadata for modules // load additional metadata for modules
(page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); (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) // populate page state (which acts as a client-side cache for subsequent requests)
_pagestate = new PageState _pagestate = new PageState
@ -223,6 +258,7 @@
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,7 +268,8 @@
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);
@ -277,84 +314,34 @@
} }
} }
private async void LocationChanged(object sender, LocationChangedEventArgs args) private Page ProcessPage(Page page, Site site, User user, Alias alias)
{
_absoluteUri = args.Location;
await Refresh();
}
Task IHandleAfterRender.OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
return NavigationInterception.EnableNavigationInterceptionAsync();
}
return Task.CompletedTask;
}
private Dictionary<string, string> ParseQueryString(string query)
{
Dictionary<string, string> querystring = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // case insensistive keys
if (!string.IsNullOrEmpty(query))
{
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)
{ {
try try
{ {
if (page.IsPersonalizable && user != null) page.Panes = new List<string>();
{ page.Resources = new List<Resource>();
// load the personalized page
page = await PageService.GetPageAsync(page.PageId, user.UserId);
}
// validate theme
if (string.IsNullOrEmpty(page.ThemeType)) if (string.IsNullOrEmpty(page.ThemeType))
{ {
page.ThemeType = site.DefaultThemeType; page.ThemeType = site.DefaultThemeType;
} }
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
page.Panes = new List<string>();
page.Resources = new List<Resource>();
string panes = "";
Type themetype = Type.GetType(page.ThemeType); Type themetype = Type.GetType(page.ThemeType);
if (themetype == null) if (themetype == null || theme == null)
{ {
// fallback // fallback to default Oqtane theme
page.ThemeType = Constants.DefaultTheme; page.ThemeType = Constants.DefaultTheme;
themetype = Type.GetType(Constants.DefaultTheme); themetype = Type.GetType(Constants.DefaultTheme);
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
} }
if (themetype != null)
string panes = "";
if (themetype != null && theme != null)
{ {
// get resources for theme (ITheme)
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
var themeobject = Activator.CreateInstance(themetype) as IThemeControl; var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
if (themeobject != null) if (themeobject != null)
{ {
@ -362,9 +349,11 @@
{ {
panes = themeobject.Panes; panes = themeobject.Panes;
} }
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page); // get resources for theme control
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
} }
} }
if (!string.IsNullOrEmpty(panes)) if (!string.IsNullOrEmpty(panes))
{ {
page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); page.Panes = panes.Replace(";", ",").Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
@ -387,7 +376,7 @@
return page; return page;
} }
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype) private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
{ {
var paneindex = new Dictionary<string, int>(); var paneindex = new Dictionary<string, int>();
foreach (Module module in modules) foreach (Module module in modules)
@ -403,6 +392,7 @@
if ((module.PageId == page.PageId || module.ModuleId == moduleid)) if ((module.PageId == page.PageId || module.ModuleId == moduleid))
{ {
var typename = Constants.ErrorModule; var typename = Constants.ErrorModule;
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime))) if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
{ {
typename = module.ModuleDefinition.ControlTypeTemplate; typename = module.ModuleDefinition.ControlTypeTemplate;
@ -424,6 +414,9 @@
} }
} }
} }
// get module resources
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
} }
// ensure component exists and implements IModuleControl // ensure component exists and implements IModuleControl
@ -447,7 +440,7 @@
{ {
// 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
@ -455,7 +448,7 @@
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);
} }
} }
@ -514,20 +507,33 @@
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)
{ {
if (resource.Level != ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
{
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 // ensure resource does not exist already
if (pageresources.Find(item => item.Url == resource.Url) == null) if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{ {
resource.Level = level; resource.Level = level;
resource.Namespace = name;
pageresources.Add(resource); pageresources.Add(resource);
} }
} }
} }
}
return pageresources; return pageresources;
} }

View File

@ -1,6 +1,7 @@
@namespace Oqtane.UI @namespace Oqtane.UI
@inject IJSRuntime JsRuntime @inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject SiteState SiteState
@DynamicComponent @DynamicComponent
@ -18,6 +19,41 @@
return; return;
} }
// set page title
if (!string.IsNullOrEmpty(PageState.Page.Title))
{
SiteState.Properties.PageTitle = PageState.Page.Title;
}
else
{
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 => DynamicComponent = builder =>
{ {
var themeType = Type.GetType(PageState.Page.ThemeType); var themeType = Type.GetType(PageState.Page.ThemeType);
@ -26,11 +62,44 @@
}; };
} }
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) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
var interop = new Interop(JsRuntime); 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);
}
}
// manage stylesheets for this page // 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"); string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
var links = new List<object>(); var links = new List<object>();
foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet))
@ -45,15 +114,73 @@
} }
await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00"); await interop.RemoveElementsById("app-stylesheet-page-", "", "app-stylesheet-page-" + batch + "-00");
await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00"); await interop.RemoveElementsById("app-stylesheet-module-", "", "app-stylesheet-module-" + batch + "-00");
}
}
// set page title private async Task InjectScripts(string content, ResourceLocation location)
if (!string.IsNullOrEmpty(PageState.Page.Title))
{ {
await interop.UpdateTitle(PageState.Page.Title); // 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 else
{ {
await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); // 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>();
@ -127,30 +133,37 @@ namespace Oqtane.Controllers
hashfilename = false; hashfilename = false;
} }
// get list of assemblies which should be downloaded to client // get site assemblies which should be downloaded to client
var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); var assemblies = _serverState.GetServerState(siteId).Assemblies;
var list = assemblies.Select(a => a.GetName().Name).ToList();
// populate assemblies // populate assembly list
for (int i = 0; i < list.Count; i++) 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)) foreach (var assembly in assemblies)
{ {
assemblyList.Insert(0, new ClientAssembly(resourceFile, hashfilename)); var filepath = Path.Combine(assembliesFolderPath, assembly) + ".resources.dll";
if (System.IO.File.Exists(filepath))
{
assemblyList.Insert(0, new ClientAssembly(Path.Combine(assembliesFolderPath, assembly + ".resources.dll"), hashfilename));
}
} }
} }
else else
@ -158,48 +171,6 @@ namespace Oqtane.Controllers
_filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); _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))
{
assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename));
}
}
else
{
_filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"));
}
}
}
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");
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"));
}
}
}
} }
return assemblyList; return assemblyList;
@ -239,6 +210,8 @@ namespace Oqtane.Controllers
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{ {
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{
if (Path.GetFileNameWithoutExtension(assembly.FilePath) != Constants.ClientId)
{ {
if (System.IO.File.Exists(assembly.FilePath)) if (System.IO.File.Exists(assembly.FilePath))
{ {
@ -259,6 +232,7 @@ namespace Oqtane.Controllers
} }
} }
} }
}
return memoryStream.ToArray(); return memoryStream.ToArray();
} }

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,10 +201,13 @@ 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>();
if (httpContextAccessor.HttpContext != null)
{
foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies) foreach (var cookie in httpContextAccessor.HttpContext.Request.Cookies)
{ {
client.DefaultRequestHeaders.Add("Cookie", cookie.Key + "=" + cookie.Value); 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,17 +136,20 @@ namespace Oqtane.Infrastructure
} }
} }
} }
catch (Exception ex)
{
// 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}"));
}
}
}
// wait 1 minute // wait 1 minute
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
} }
} }
catch
{
// can occur during the initial installation as there is no DBContext
}
}
private DateTime CalculateNextExecution(DateTime nextExecution, Job job) private DateTime CalculateNextExecution(DateTime nextExecution, Job job)
{ {
@ -190,10 +196,12 @@ 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,6 +241,15 @@ namespace Oqtane.Infrastructure
jobs.AddJob(job); jobs.AddJob(job);
} }
} }
catch (Exception ex)
{
// 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}"));
}
}
}
_executingTask = ExecuteAsync(_cancellationTokenSource.Token); _executingTask = ExecuteAsync(_cancellationTokenSource.Token);
@ -240,20 +257,17 @@ namespace Oqtane.Infrastructure
{ {
return _executingTask; return _executingTask;
} }
}
catch
{
// can occur during the initial installation because this method is called during startup and the database has not yet been created
}
return Task.CompletedTask; return Task.CompletedTask;
} }
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,6 +42,8 @@ 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("SMTPEnabled") || settings["SMTPEnabled"] == "True")
{
if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" &&
settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" &&
settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" &&
@ -133,6 +135,7 @@ namespace Oqtane.Infrastructure
// encoding // encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8; mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
mailMessage.IsBodyHtml = true;
// send mail // send mail
try try
@ -157,6 +160,11 @@ namespace Oqtane.Infrastructure
log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />"; log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />";
} }
} }
else
{
log += "SMTP Disabled In Site Settings" + "<br />";
}
}
return log; return log;
} }

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