commit
1aee385679
|
@ -40,10 +40,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
@if (_name.ToLower().EndsWith(".zip"))
|
||||
{
|
||||
<button type="button" class="btn btn-primary mx-1" @onclick="UnzipFile">Unzip</button>
|
||||
}
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
|
@ -126,4 +130,18 @@
|
|||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnzipFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
await FileService.UnzipFileAsync(_fileId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Unzipping File {FileId} {Error}", _fileId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.File.Unzip"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,67 +8,77 @@
|
|||
|
||||
@if (_folders != null)
|
||||
{
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-12">
|
||||
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
}
|
||||
</form>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br /><br />
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
<br />
|
||||
@if (!_isSystem)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||
|
@ -80,11 +90,6 @@
|
|||
@((MarkupString)" ")
|
||||
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
||||
}
|
||||
<br /><br />
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
|
|
|
@ -152,7 +152,8 @@ else
|
|||
|
||||
private async Task Refresh()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await GetJobs();
|
||||
StateHasChanged();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,8 @@ else
|
|||
|
||||
private async Task Refresh()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await GetJobLogs();
|
||||
StateHasChanged();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,17 +208,20 @@
|
|||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
||||
|
||||
// return url is not specified if user navigated directly to login page
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||
|
||||
if (hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.ReturnUrl, true));
|
||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back to the Login page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(PageState.ReturnUrl) };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject IPageService PageService
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISystemService SystemService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
@ -417,7 +418,8 @@
|
|||
_permissions = _page.PermissionList;
|
||||
|
||||
// page modules
|
||||
_pageModules = PageState.Modules;
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_pageModules = modules.Where(item => item.PageId == _page.PageId && !item.IsDeleted).ToList();
|
||||
|
||||
// audit
|
||||
_createdby = _page.CreatedBy;
|
||||
|
|
|
@ -53,35 +53,36 @@
|
|||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Reindex" Message="Are You Sure You Wish To Reindex Search Content?" Action="Reindex" Class="btn btn-danger" OnClick="@(async () => await Reindex())" ResourceKey="Reindex" />
|
||||
<br /><br />
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
private string _searchProvider;
|
||||
private string _enabled;
|
||||
private string _lastIndexedOn;
|
||||
private string _ignorePages;
|
||||
private string _ignoreEntities;
|
||||
private string _minimumWordLength;
|
||||
private string _ignoreWords;
|
||||
private string _enabled = "True";
|
||||
private string _lastIndexedOn = "";
|
||||
private string _ignorePages = "";
|
||||
private string _ignoreEntities = "";
|
||||
private string _minimumWordLength = "3";
|
||||
private string _ignoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName);
|
||||
_enabled = SettingService.GetSetting(settings, "Search_Enabled", "True");
|
||||
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", "");
|
||||
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", "");
|
||||
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", "");
|
||||
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3");
|
||||
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", "");
|
||||
_enabled = SettingService.GetSetting(settings, "Search_Enabled", _enabled);
|
||||
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn);
|
||||
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", _ignorePages);
|
||||
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", _ignoreEntities);
|
||||
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", _minimumWordLength);
|
||||
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", _ignoreWords);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
|
||||
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true);
|
||||
|
@ -93,10 +94,28 @@
|
|||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Reindex()
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastIndexedOn = DateTime.MinValue.ToString();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Message.Reindex"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
private void Search()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}"));
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={WebUtility.UrlEncode(_keywords)}"));
|
||||
}
|
||||
|
||||
private async Task PerformSearch()
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="includeentities" ResourceKey="IncludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to include in the search results. By default all entities will be included.">Include Entities: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" required />
|
||||
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="excludeentities" ResourceKey="ExcludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to exclude from search results. By default no entities will be excluded.">Exclude Entities: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" required></input>
|
||||
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
|
|
@ -309,7 +309,7 @@
|
|||
}
|
||||
</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.IsDefault</td>
|
||||
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -51,25 +51,25 @@ else
|
|||
<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<form class="app-form-inline" method="post" @formname="@($"ActionDialogCloseForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-body">
|
||||
<p>@Message</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
||||
</form>
|
||||
}
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
||||
</form>
|
||||
|
@ -87,7 +87,7 @@ else
|
|||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
</form>
|
||||
|
@ -199,6 +199,8 @@ else
|
|||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||
_authorized = IsAuthorized();
|
||||
|
||||
if (string.IsNullOrEmpty(Id)) Id = "1";
|
||||
|
||||
if (PageState.QueryString.ContainsKey("dialog"))
|
||||
{
|
||||
_visible = (PageState.QueryString["dialog"] == Id);
|
||||
|
|
|
@ -314,7 +314,6 @@
|
|||
await SetImage();
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
|
||||
private async Task SetImage()
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -22,9 +22,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -150,14 +150,11 @@
|
|||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the folder name</value>
|
||||
</data>
|
||||
<data name="Permissions.HelpText" xml:space="preserve">
|
||||
<value>Select the permissions you want for the folder</value>
|
||||
</data>
|
||||
<data name="Parent.Text" xml:space="preserve">
|
||||
<value>Parent: </value>
|
||||
</data>
|
||||
<data name="Permissions.Text" xml:space="preserve">
|
||||
<value>Permissions: </value>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="DeleteFolder.Header" xml:space="preserve">
|
||||
<value>Delete Folder</value>
|
||||
|
@ -198,4 +195,7 @@
|
|||
<data name="Message.Folder.Duplicate" xml:space="preserve">
|
||||
<value>Folder Name Specified Already Exists In Parent</value>
|
||||
</data>
|
||||
<data name="Settings.Heading" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
</root>
|
|
@ -165,4 +165,16 @@
|
|||
<data name="SearchProvider.Text" xml:space="preserve">
|
||||
<value>Search Provider:</value>
|
||||
</data>
|
||||
<data name="Message.Reindex" xml:space="preserve">
|
||||
<value>The search index will be rebuilt for this site. Please be patient during the reindexing process.</value>
|
||||
</data>
|
||||
<data name="Reindex.Text" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Header" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Reindex Search Content?</value>
|
||||
</data>
|
||||
</root>
|
|
@ -213,6 +213,9 @@
|
|||
<data name="Reset" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="Search Settings" xml:space="preserve">
|
||||
<value>Search Settings</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
|
|
|
@ -75,5 +75,10 @@ namespace Oqtane.Services
|
|||
{
|
||||
return await GetByteArrayAsync($"{Apiurl}/download/{fileId}");
|
||||
}
|
||||
|
||||
public async Task UnzipFileAsync(int fileId)
|
||||
{
|
||||
await PutAsync($"{Apiurl}/unzip/{fileId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,5 +92,13 @@ namespace Oqtane.Services
|
|||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task<List<File>> GetFilesAsync(int siteId, string folderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Unzips the contents of a zip file
|
||||
/// </summary>
|
||||
/// <param name="fileId">Reference to the <see cref="File"/></param>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task UnzipFileAsync(int fileId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,6 +179,17 @@ namespace Oqtane.Services
|
|||
/// <returns></returns>
|
||||
Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates setting for a given entityName and Id
|
||||
/// </summary>
|
||||
/// <param name="entityName"></param>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="settingName"></param>
|
||||
/// <param name="settingValue"></param>
|
||||
/// <param name="isPrivate"></param>
|
||||
/// <returns></returns>
|
||||
Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific setting
|
||||
/// </summary>
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace Oqtane.Services
|
|||
if (_factory != null)
|
||||
{
|
||||
var client = _factory.CreateClient("oqtane");
|
||||
client.BaseAddress = new Uri(_siteState.Alias.Protocol + _siteState.Alias.Name);
|
||||
if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace Oqtane.Services
|
|||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SettingService : ServiceBase, ISettingService
|
||||
{
|
||||
public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) {}
|
||||
|
||||
private string Apiurl => CreateApiUrl("Setting");
|
||||
|
||||
|
@ -134,7 +134,7 @@ namespace Oqtane.Services
|
|||
public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
|
||||
{
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var settings = await GetSettingsAsync(entityName, entityId, "");
|
||||
if (settings != null)
|
||||
{
|
||||
foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList())
|
||||
|
@ -147,7 +147,7 @@ namespace Oqtane.Services
|
|||
|
||||
public async Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId)
|
||||
{
|
||||
var settingsList = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var settingsList = await GetSettingsAsync(entityName, entityId, "");
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in settings)
|
||||
{
|
||||
|
@ -192,14 +192,14 @@ namespace Oqtane.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate)
|
||||
{
|
||||
await PutAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}");
|
||||
}
|
||||
|
||||
public async Task DeleteSettingAsync(string entityName, int entityId, string settingName)
|
||||
{
|
||||
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
|
||||
if (setting != null)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{setting.SettingId}/{entityName}");
|
||||
}
|
||||
await DeleteAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}");
|
||||
}
|
||||
|
||||
public async Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName)
|
||||
|
|
|
@ -134,8 +134,7 @@
|
|||
if (PageState.User != null)
|
||||
{
|
||||
// preserve edit mode for authenticated users
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
|
||||
await SettingService.AddOrUpdateSettingAsync(EntityNames.User, PageState.User.UserId, "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1", false);
|
||||
}
|
||||
|
||||
// preserve other querystring parameters
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{
|
||||
if (_searchResultsPage != null)
|
||||
{
|
||||
var url = NavigateUrl(_searchResultsPage.Path, $"q={_keywords}");
|
||||
var url = NavigateUrl(_searchResultsPage.Path, $"q={WebUtility.UrlEncode(_keywords)}");
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,17 +261,17 @@
|
|||
// edit mode
|
||||
if (user != null)
|
||||
{
|
||||
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
|
||||
var editpageid = user.Settings.ContainsKey("CP-editmode") ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1;
|
||||
if ((querystring.ContainsKey("edit") && querystring["edit"] == "true") || page.PageId == editpageid)
|
||||
{
|
||||
editmode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1));
|
||||
if (!editmode)
|
||||
if (editpageid != -1)
|
||||
{
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
|
||||
// reset edit mode page
|
||||
await SettingService.AddOrUpdateSettingAsync(EntityNames.User, user.UserId, "CP-editmode", "-1", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -34,7 +34,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -33,7 +33,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
@ -33,7 +33,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
|
@ -31,7 +31,7 @@
|
|||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>5.2.0</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>5.2.1</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
|
@ -65,15 +65,15 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.80" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.80" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.80" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
|
@ -11,8 +11,8 @@
|
|||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.0/Oqtane.Framework.5.2.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.1/Oqtane.Framework.5.2.1.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
</metadata>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>5.2.0</version>
|
||||
<version>5.2.1</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</releaseNotes>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.1.Install.zip" -Force
|
||||
|
|
|
@ -1 +1 @@
|
|||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.1.Upgrade.zip" -Force
|
||||
|
|
|
@ -429,7 +429,10 @@
|
|||
new CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(10),
|
||||
IsEssential = true
|
||||
IsEssential = true,
|
||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
|
||||
Secure = true, // Ensure the cookie is only sent over HTTPS
|
||||
HttpOnly = true // Optional: Helps mitigate XSS attacks
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -516,9 +519,9 @@
|
|||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
|
||||
" window.interceptNavigation = () => {" + Environment.NewLine +
|
||||
" let currentUrl = window.location.href;" + Environment.NewLine +
|
||||
" let currentUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
|
||||
" let newUrl = window.location.href;" + Environment.NewLine +
|
||||
" let newUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" if (currentUrl != newUrl) {" + Environment.NewLine +
|
||||
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
|
@ -601,9 +604,19 @@
|
|||
|
||||
private void SetLocalizationCookie(string culture)
|
||||
{
|
||||
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(1),
|
||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
|
||||
Secure = true, // Ensure the cookie is only sent over HTTPS
|
||||
HttpOnly = true // Optional: Helps mitigate XSS attacks
|
||||
};
|
||||
|
||||
Context.Response.Cookies.Append(
|
||||
CookieRequestCultureProvider.DefaultCookieName,
|
||||
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
|
||||
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
|
||||
cookieOptions
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<List<Resource>> GetPageResources(Alias alias, Site site, Page page, List<Module> modules, int moduleid, string action)
|
||||
|
|
|
@ -21,6 +21,7 @@ using SixLabors.ImageSharp.Processing;
|
|||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using System.IO.Compression;
|
||||
|
||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||
|
||||
|
@ -171,18 +172,30 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
if (_userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit))
|
||||
{
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
if (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
|
||||
{
|
||||
file = CreateFile(file.Name, folder.FolderId, filepath);
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file);
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
file = CreateFile(file.Name, folder.FolderId, filepath);
|
||||
if (file != null)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Does Not Exist At Path {FilePath}", filepath);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Does Not Exist At Path {FilePath}", filepath);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", file.Name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
|
@ -213,29 +226,38 @@ namespace Oqtane.Controllers
|
|||
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, File.FolderId, PermissionNames.Edit) // ensure user had edit rights to original folder
|
||||
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit)) // ensure user has edit rights to new folder
|
||||
{
|
||||
if (File.Name != file.Name || File.FolderId != file.FolderId)
|
||||
if (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
|
||||
{
|
||||
file.Folder = _folders.GetFolder(file.FolderId, false);
|
||||
string folderpath = _folders.GetFolderPath(file.Folder);
|
||||
if (!Directory.Exists(folderpath))
|
||||
if (File.Name != file.Name || File.FolderId != file.FolderId)
|
||||
{
|
||||
Directory.CreateDirectory(folderpath);
|
||||
file.Folder = _folders.GetFolder(file.FolderId, false);
|
||||
string folderpath = _folders.GetFolderPath(file.Folder);
|
||||
if (!Directory.Exists(folderpath))
|
||||
{
|
||||
Directory.CreateDirectory(folderpath);
|
||||
}
|
||||
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
|
||||
}
|
||||
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
|
||||
}
|
||||
|
||||
var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file));
|
||||
if (newfile != null)
|
||||
var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file));
|
||||
if (newfile != null)
|
||||
{
|
||||
file.Extension = newfile.Extension;
|
||||
file.Size = newfile.Size;
|
||||
file.ImageWidth = newfile.ImageWidth;
|
||||
file.ImageHeight = newfile.ImageHeight;
|
||||
}
|
||||
|
||||
file = _files.UpdateFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
|
||||
}
|
||||
else
|
||||
{
|
||||
file.Extension = newfile.Extension;
|
||||
file.Size = newfile.Size;
|
||||
file.ImageWidth = newfile.ImageWidth;
|
||||
file.ImageHeight = newfile.ImageHeight;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", file.Name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
|
||||
file = _files.UpdateFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -247,6 +269,57 @@ namespace Oqtane.Controllers
|
|||
return file;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/unzip/5
|
||||
[HttpPut("unzip/{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Unzip(int id)
|
||||
{
|
||||
var zipfile = _files.GetFile(id, false);
|
||||
if (zipfile != null && zipfile.Folder.SiteId == _alias.SiteId && zipfile.Extension.ToLower() == "zip")
|
||||
{
|
||||
// extract files
|
||||
string folderpath = _folders.GetFolderPath(zipfile.Folder);
|
||||
using (ZipArchive archive = ZipFile.OpenRead(Path.Combine(folderpath, zipfile.Name)))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (HasValidFileExtension(entry.Name) && entry.Name.IsPathOrFileValid())
|
||||
{
|
||||
entry.ExtractToFile(Path.Combine(folderpath, entry.Name), true);
|
||||
var file = CreateFile(entry.Name, zipfile.Folder.FolderId, Path.Combine(folderpath, entry.Name));
|
||||
if (file != null)
|
||||
{
|
||||
if (file.FileId == 0)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = _files.UpdateFile(file);
|
||||
}
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Extracted {File}", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete zip file
|
||||
_files.DeleteFile(zipfile.FileId);
|
||||
System.IO.File.Delete(Path.Combine(folderpath, zipfile.Name));
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Zip File Removed {File}", zipfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Unzip Attempt {FileId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Registered)]
|
||||
|
@ -289,64 +362,55 @@ namespace Oqtane.Controllers
|
|||
folder = _folders.GetFolder(FolderId);
|
||||
}
|
||||
|
||||
var _UploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
|
||||
|
||||
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.PermissionList))
|
||||
{
|
||||
string folderPath = _folders.GetFolderPath(folder);
|
||||
CreateDirectory(folderPath);
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
||||
}
|
||||
// check for allowable file extensions
|
||||
if (!_UploadableFiles.Split(',').Contains(Path.GetExtension(name).ToLower().Replace(".", "")))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url Due To Its File Extension {Url}", url);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
|
||||
return file;
|
||||
}
|
||||
|
||||
if (!name.IsPathOrFileValid())
|
||||
if (HasValidFileExtension(name) && name.IsPathOrFileValid())
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, $"File Could Not Be Downloaded From Url Due To Its File Name Not Allowed {url}");
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
|
||||
return file;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string targetPath = Path.Combine(folderPath, name);
|
||||
|
||||
// remove file if it already exists
|
||||
if (System.IO.File.Exists(targetPath))
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(targetPath);
|
||||
}
|
||||
string folderPath = _folders.GetFolderPath(folder);
|
||||
CreateDirectory(folderPath);
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
using (var stream = await client.GetStreamAsync(url))
|
||||
string targetPath = Path.Combine(folderPath, name);
|
||||
if (System.IO.File.Exists(targetPath))
|
||||
{
|
||||
using (var fileStream = new FileStream(targetPath, FileMode.CreateNew))
|
||||
System.IO.File.Delete(targetPath);
|
||||
}
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
using (var stream = await client.GetStreamAsync(url))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
using (var fileStream = new FileStream(targetPath, FileMode.CreateNew))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file = CreateFile(name, folder.FolderId, targetPath);
|
||||
if (file != null)
|
||||
file = CreateFile(name, folder.FolderId, targetPath);
|
||||
if (file != null)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Downloaded {File}", file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -368,21 +432,11 @@ namespace Oqtane.Controllers
|
|||
return;
|
||||
}
|
||||
|
||||
// Get the UploadableFiles extensions
|
||||
string _UploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
|
||||
|
||||
// ensure filename is valid
|
||||
string token = ".part_";
|
||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check for allowable file extensions (ignore token)
|
||||
var extension = Path.GetExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))).Replace(".", "");
|
||||
if (!_UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -428,7 +482,7 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
file = _files.UpdateFile(file);
|
||||
}
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Upload Succeeded {File}", Path.Combine(folderPath, upload));
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload));
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
|
@ -838,10 +892,18 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
_files.DeleteFile(file.FileId);
|
||||
}
|
||||
file = null;
|
||||
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "File Exceeds Folder Capacity And Has Been Removed {Folder} {File}", folder, filepath);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private bool HasValidFileExtension(string fileName)
|
||||
{
|
||||
var _uploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||
return _uploadableFiles.Split(',').Contains(Path.GetExtension(fileName).ToLower().Replace(".", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace Oqtane.Controllers
|
|||
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Create);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting);
|
||||
}
|
||||
else
|
||||
|
@ -131,7 +131,7 @@ namespace Oqtane.Controllers
|
|||
if (ModelState.IsValid && setting.SettingId == id && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Update);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
}
|
||||
else
|
||||
|
@ -146,15 +146,53 @@ namespace Oqtane.Controllers
|
|||
return setting;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5/xxx
|
||||
[HttpDelete("{id}/{entityName}")]
|
||||
public void Delete(string entityName, int id)
|
||||
// PUT api/<controller>/site/1/settingname/x/false
|
||||
[HttpPut("{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}")]
|
||||
public void Put(string entityName, int entityId, string settingName, string settingValue, bool isPrivate)
|
||||
{
|
||||
Setting setting = _settings.GetSetting(entityName, id);
|
||||
if (IsAuthorized(entityName, entityId, PermissionNames.Edit))
|
||||
{
|
||||
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new Setting();
|
||||
setting.EntityName = entityName;
|
||||
setting.EntityId = entityId;
|
||||
setting.SettingName = settingName;
|
||||
setting.SettingValue = settingValue;
|
||||
setting.IsPrivate = isPrivate;
|
||||
setting = _settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting.SettingValue != settingValue || setting.IsPrivate != isPrivate)
|
||||
{
|
||||
setting.SettingValue = settingValue;
|
||||
setting.IsPrivate = isPrivate;
|
||||
setting = _settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Add Or Update Setting {EntityName} {EntityId} {SettingName}", entityName, entityId, settingName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/site/1/settingname
|
||||
[HttpDelete("{entityName}/{entityId}/{settingName}")]
|
||||
public void Delete(string entityName, int entityId, string settingName)
|
||||
{
|
||||
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
|
||||
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
_settings.DeleteSetting(setting.EntityName, id);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Delete);
|
||||
_settings.DeleteSetting(setting.EntityName, setting.SettingId);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
|
||||
}
|
||||
else
|
||||
|
@ -300,7 +338,7 @@ namespace Oqtane.Controllers
|
|||
return filter;
|
||||
}
|
||||
|
||||
private void AddSyncEvent(string EntityName, int SettingId, string Action)
|
||||
private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action);
|
||||
|
||||
|
@ -311,6 +349,9 @@ namespace Oqtane.Controllers
|
|||
case EntityNames.Site:
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
break;
|
||||
case EntityNames.User:
|
||||
_syncManager.AddSyncEvent(_alias, EntityName, EntityId, SyncEventActions.Update);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,19 +26,17 @@ namespace Oqtane.Controllers
|
|||
private readonly IUserManager _userManager;
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly IJwtManager _jwtManager;
|
||||
private readonly IFileRepository _files;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, IFileRepository files, ILogManager logger)
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_tenantManager = tenantManager;
|
||||
_userManager = userManager;
|
||||
_sites = sites;
|
||||
_userPermissions = userPermissions;
|
||||
_settings = settings;
|
||||
_jwtManager = jwtManager;
|
||||
_files = files;
|
||||
_logger = logger;
|
||||
|
@ -56,12 +54,6 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
|
@ -83,12 +75,6 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
|
@ -112,12 +98,6 @@ namespace Oqtane.Controllers
|
|||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
|
|
71
Oqtane.Server/Extensions/QueryableExtensions.cs
Normal file
71
Oqtane.Server/Extensions/QueryableExtensions.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Extensions
|
||||
{
|
||||
public static class QueryableExtensions
|
||||
{
|
||||
public static IQueryable<T> FilterByItems<T, TItem>(this IQueryable<T> query, IEnumerable<TItem> items,
|
||||
Expression<Func<T, TItem, bool>> filterPattern, bool isOr)
|
||||
{
|
||||
Expression predicate = null;
|
||||
foreach (var item in items)
|
||||
{
|
||||
var itemExpr = Expression.Constant(item);
|
||||
var itemCondition = ExpressionReplacer.Replace(filterPattern.Body, filterPattern.Parameters[1], itemExpr);
|
||||
if (predicate == null)
|
||||
predicate = itemCondition;
|
||||
else
|
||||
{
|
||||
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
|
||||
itemCondition);
|
||||
}
|
||||
}
|
||||
|
||||
predicate ??= Expression.Constant(false);
|
||||
var filterLambda = Expression.Lambda<Func<T, bool>>(predicate, filterPattern.Parameters[0]);
|
||||
|
||||
return query.Where(filterLambda);
|
||||
}
|
||||
|
||||
class ExpressionReplacer : ExpressionVisitor
|
||||
{
|
||||
readonly IDictionary<Expression, Expression> _replaceMap;
|
||||
|
||||
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
|
||||
{
|
||||
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(node))]
|
||||
public override Expression Visit(Expression node)
|
||||
{
|
||||
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
|
||||
return replacement;
|
||||
return base.Visit(node);
|
||||
}
|
||||
|
||||
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
|
||||
{
|
||||
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
|
||||
}
|
||||
|
||||
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
|
||||
{
|
||||
return new ExpressionReplacer(replaceMap).Visit(expr);
|
||||
}
|
||||
|
||||
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
|
||||
{
|
||||
if (lambda.Parameters.Count != toReplace.Length)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
|
||||
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,18 +17,24 @@ namespace Oqtane.Infrastructure.EventSubscribers
|
|||
public void EntityChanged(SyncEvent syncEvent)
|
||||
{
|
||||
// when site entities change (ie. site, pages, modules, etc...) a site refresh event is raised and the site cache item needs to be refreshed
|
||||
if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Refresh)
|
||||
if (syncEvent.EntityName == EntityNames.Site && (syncEvent.Action == SyncEventActions.Refresh || syncEvent.Action == SyncEventActions.Reload))
|
||||
{
|
||||
_cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||
_cache.Remove($"modules:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||
}
|
||||
|
||||
// when a site entity is updated, the hosting model may have changed so the client assemblies cache items need to be refreshed
|
||||
if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Update)
|
||||
if (syncEvent.EntityName == EntityNames.Site && (syncEvent.Action == SyncEventActions.Update || syncEvent.Action == SyncEventActions.Delete))
|
||||
{
|
||||
_cache.Remove($"assemblieslist:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||
_cache.Remove($"assemblies:{syncEvent.TenantId}:{syncEvent.EntityId}");
|
||||
}
|
||||
|
||||
// when a users settings are changed, the user cache item needs to be refreshed
|
||||
if (syncEvent.EntityName == EntityNames.User && syncEvent.Action == SyncEventActions.Update)
|
||||
{
|
||||
_cache.Remove($"user:{syncEvent.EntityId}:{syncEvent.TenantId}:{syncEvent.SiteId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,18 +186,21 @@ namespace Oqtane.Infrastructure
|
|||
|
||||
if (string.IsNullOrEmpty(searchContent.Title))
|
||||
{
|
||||
searchContent.Title = string.Empty;
|
||||
if (!string.IsNullOrEmpty(pageModule.Title))
|
||||
{
|
||||
searchContent.Title = pageModule.Title;
|
||||
}
|
||||
else if (pageModule.Page != null)
|
||||
if (pageModule.Page != null)
|
||||
{
|
||||
searchContent.Title = !string.IsNullOrEmpty(pageModule.Page.Title) ? pageModule.Page.Title : pageModule.Page.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchContent.Title = pageModule.Title;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchContent.Description == null)
|
||||
{
|
||||
searchContent.Description = (searchContent.Title != pageModule.Title) ? pageModule.Title : string.Empty;
|
||||
}
|
||||
|
||||
if (searchContent.Description == null) { searchContent.Description = string.Empty;}
|
||||
if (searchContent.Body == null) { searchContent.Body = string.Empty; }
|
||||
|
||||
if (string.IsNullOrEmpty(searchContent.Url))
|
||||
|
|
|
@ -210,7 +210,7 @@ namespace Oqtane.Infrastructure
|
|||
if (alias != null)
|
||||
{
|
||||
subject = $"{alias.Name} Site {log.Level} Notification";
|
||||
body = $"Log Message: {log.Message}<br /><br />Please visit {alias.Protocol}://{alias.Name}/admin/log?id={log.LogId} for more information";
|
||||
body = $"Log Message: {log.Message}<br /><br />Please visit {alias.Protocol}{alias.Name}/admin/log?id={log.LogId} for more information";
|
||||
}
|
||||
|
||||
foreach (var userrole in _userRoles.GetUserRoles(RoleNames.Host, log.SiteId.Value))
|
||||
|
|
|
@ -266,7 +266,6 @@ namespace Oqtane.SiteTemplates
|
|||
PermissionList = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.View, RoleNames.Registered, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
},
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
|
|
|
@ -66,8 +66,8 @@ namespace Oqtane.Infrastructure
|
|||
case "5.1.0":
|
||||
Upgrade_5_1_0(tenant, scope);
|
||||
break;
|
||||
case "5.2.0":
|
||||
Upgrade_5_2_0(tenant, scope);
|
||||
case "5.2.1":
|
||||
Upgrade_5_2_1(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ namespace Oqtane.Infrastructure
|
|||
}
|
||||
};
|
||||
|
||||
AddPagesToSites(scope, pageTemplates);
|
||||
AddPagesToSites(scope, tenant, pageTemplates);
|
||||
}
|
||||
|
||||
private void Upgrade_3_1_3(Tenant tenant, IServiceScope scope)
|
||||
|
@ -386,7 +386,7 @@ namespace Oqtane.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
private void Upgrade_5_2_0(Tenant tenant, IServiceScope scope)
|
||||
private void Upgrade_5_2_1(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
var pageTemplates = new List<PageTemplate>
|
||||
{
|
||||
|
@ -438,14 +438,16 @@ namespace Oqtane.Infrastructure
|
|||
}
|
||||
};
|
||||
|
||||
AddPagesToSites(scope, pageTemplates);
|
||||
AddPagesToSites(scope, tenant, pageTemplates);
|
||||
}
|
||||
|
||||
private void AddPagesToSites(IServiceScope scope, List<PageTemplate> pageTemplates)
|
||||
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
|
||||
{
|
||||
var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>();
|
||||
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
foreach (var site in sites.GetSites().ToList())
|
||||
{
|
||||
tenants.SetAlias(tenant.TenantId, site.SiteId);
|
||||
sites.CreatePages(site, pageTemplates, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ namespace Oqtane.Managers
|
|||
{
|
||||
user.SiteId = siteid;
|
||||
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || user.UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
|
@ -74,8 +77,7 @@ namespace Oqtane.Managers
|
|||
User user = _users.GetUser(username);
|
||||
if (user != null)
|
||||
{
|
||||
user.SiteId = siteid;
|
||||
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||
user = GetUser(user.UserId, siteid);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
@ -85,8 +87,7 @@ namespace Oqtane.Managers
|
|||
User user = _users.GetUser(username, email);
|
||||
if (user != null)
|
||||
{
|
||||
user.SiteId = siteid;
|
||||
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||
user = GetUser(user.UserId, siteid);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
|
32
Oqtane.Server/Migrations/Master/05020101_AddIndexes.cs
Normal file
32
Oqtane.Server/Migrations/Master/05020101_AddIndexes.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
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.05.02.01.01")]
|
||||
public class AddIndexes : MultiDatabaseMigration
|
||||
{
|
||||
public AddIndexes(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var aliasEntityBuilder = new AliasEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
aliasEntityBuilder.DropIndex("IX_Alias");
|
||||
aliasEntityBuilder.AddIndex("IX_Alias", "Name", true);
|
||||
|
||||
var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
themeEntityBuilder.AddIndex("IX_Theme", "ThemeName", true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
|
|
41
Oqtane.Server/Migrations/Tenant/05020101_AddIndexes.cs
Normal file
41
Oqtane.Server/Migrations/Tenant/05020101_AddIndexes.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
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.05.02.01.01")]
|
||||
public class AddIndexes : MultiDatabaseMigration
|
||||
{
|
||||
public AddIndexes(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
languageEntityBuilder.AddIndex("IX_Language", new string[] { "SiteId", "Code" }, true);
|
||||
|
||||
var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
pageModuleEntityBuilder.AddIndex("IX_PageModule_Module", "ModuleId");
|
||||
pageModuleEntityBuilder.AddIndex("IX_PageModule_Page", "PageId");
|
||||
|
||||
var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
searchContentEntityBuilder.AddIndex("IX_SearchContent", new string[] { "SiteId", "EntityName", "EntityId" }, true);
|
||||
|
||||
var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
searchContentPropertyEntityBuilder.AddIndex("IX_SearchContentProperty", new string[] { "SearchContentId", "Name" }, true);
|
||||
|
||||
var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
searchContentWordEntityBuilder.AddIndex("IX_SearchContentWord", new string[] { "SearchContentId", "SearchWordId" }, true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ namespace Oqtane.Modules.Admin.Files.Manager
|
|||
EntityName = EntityNames.File,
|
||||
EntityId = file.FileId.ToString(),
|
||||
Title = path,
|
||||
Description = "",
|
||||
Body = body,
|
||||
Url = $"{Constants.FileUrl}{folder.Path}{file.Name}",
|
||||
Permissions = $"{EntityNames.Folder}:{folder.FolderId}",
|
||||
|
|
|
@ -20,18 +20,15 @@ namespace Oqtane.Modules.HtmlText.Manager
|
|||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IHtmlTextRepository _htmlText;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
private readonly ISqlRepository _sqlRepository;
|
||||
|
||||
public HtmlTextManager(
|
||||
IServiceProvider serviceProvider,
|
||||
IHtmlTextRepository htmlText,
|
||||
IDBContextDependencies DBContextDependencies,
|
||||
ISqlRepository sqlRepository)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_htmlText = htmlText;
|
||||
_DBContextDependencies = DBContextDependencies;
|
||||
_sqlRepository = sqlRepository;
|
||||
|
@ -53,19 +50,15 @@ namespace Oqtane.Modules.HtmlText.Manager
|
|||
{
|
||||
var searchContents = new List<SearchContent>();
|
||||
|
||||
var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId);
|
||||
if (htmltexts != null && htmltexts.Any())
|
||||
var htmltext = _htmlText.GetHtmlTexts(pageModule.ModuleId)?.OrderByDescending(item => item.CreatedOn).FirstOrDefault();
|
||||
if (htmltext != null && htmltext.CreatedOn >= lastIndexedOn)
|
||||
{
|
||||
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
|
||||
if (htmltext.CreatedOn >= lastIndexedOn)
|
||||
searchContents.Add(new SearchContent
|
||||
{
|
||||
searchContents.Add(new SearchContent
|
||||
{
|
||||
Body = htmltext.Content,
|
||||
ContentModifiedBy = htmltext.CreatedBy,
|
||||
ContentModifiedOn = htmltext.CreatedOn
|
||||
});
|
||||
}
|
||||
Body = htmltext.Content,
|
||||
ContentModifiedBy = htmltext.CreatedBy,
|
||||
ContentModifiedOn = htmltext.CreatedOn
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult(searchContents);
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
using System.Linq;
|
||||
using Oqtane.Documentation;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Repository
|
||||
{
|
||||
|
@ -13,24 +9,16 @@ namespace Oqtane.Modules.HtmlText.Repository
|
|||
public class HtmlTextRepository : IHtmlTextRepository, ITransientService
|
||||
{
|
||||
private readonly IDbContextFactory<HtmlTextContext> _factory;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly SiteState _siteState;
|
||||
|
||||
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory, IMemoryCache cache, SiteState siteState)
|
||||
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_cache = cache;
|
||||
_siteState = siteState;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId)
|
||||
{
|
||||
return _cache.GetOrCreate($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.HtmlText.Where(item => item.ModuleId == moduleId).ToList();
|
||||
});
|
||||
using var db = _factory.CreateDbContext();
|
||||
return db.HtmlText.Where(item => item.ModuleId == moduleId).ToList();
|
||||
}
|
||||
|
||||
public Models.HtmlText GetHtmlText(int htmlTextId)
|
||||
|
@ -44,7 +32,6 @@ namespace Oqtane.Modules.HtmlText.Repository
|
|||
using var db = _factory.CreateDbContext();
|
||||
db.HtmlText.Add(htmlText);
|
||||
db.SaveChanges();
|
||||
ClearCache(htmlText.ModuleId);
|
||||
return htmlText;
|
||||
}
|
||||
|
||||
|
@ -53,47 +40,7 @@ namespace Oqtane.Modules.HtmlText.Repository
|
|||
using var db = _factory.CreateDbContext();
|
||||
Models.HtmlText htmlText = db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
|
||||
if (htmlText != null) db.HtmlText.Remove(htmlText);
|
||||
ClearCache(htmlText.ModuleId);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Models.HtmlText>> GetHtmlTextsAsync(int moduleId)
|
||||
{
|
||||
return await _cache.GetOrCreateAsync($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}", async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
using var db = _factory.CreateDbContext();
|
||||
return await db.HtmlText.Where(item => item.ModuleId == moduleId).ToListAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return await db.HtmlText.FindAsync(htmlTextId);
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.HtmlText.Add(htmlText);
|
||||
await db.SaveChangesAsync();
|
||||
ClearCache(htmlText.ModuleId);
|
||||
return htmlText;
|
||||
}
|
||||
|
||||
public async Task DeleteHtmlTextAsync(int htmlTextId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.HtmlText htmlText = db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
|
||||
db.HtmlText.Remove(htmlText);
|
||||
ClearCache(htmlText.ModuleId);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private void ClearCache(int moduleId)
|
||||
{
|
||||
_cache.Remove($"HtmlText:{_siteState.Alias.SiteKey}:{moduleId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Repository
|
||||
|
@ -11,10 +10,5 @@ namespace Oqtane.Modules.HtmlText.Repository
|
|||
Models.HtmlText GetHtmlText(int htmlTextId);
|
||||
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
|
||||
void DeleteHtmlText(int htmlTextId);
|
||||
|
||||
Task<IEnumerable<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId);
|
||||
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText);
|
||||
Task DeleteHtmlTextAsync(int htmlTextId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
|
@ -17,24 +19,26 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||
{
|
||||
private readonly IHtmlTextRepository _htmlText;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public ServerHtmlTextService(IHtmlTextRepository htmlText, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||
public ServerHtmlTextService(IHtmlTextRepository htmlText, IUserPermissions userPermissions, IMemoryCache cache, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
||||
{
|
||||
_htmlText = htmlText;
|
||||
_userPermissions = userPermissions;
|
||||
_cache = cache;
|
||||
_logger = logger;
|
||||
_accessor = accessor;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
public async Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId)
|
||||
public Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId)
|
||||
{
|
||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Registered))
|
||||
{
|
||||
return (await _htmlText.GetHtmlTextsAsync(moduleId)).ToList();
|
||||
return Task.FromResult(GetCachedHtmlTexts(moduleId));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -43,19 +47,11 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> GetHtmlTextAsync(int moduleId)
|
||||
public Task<Models.HtmlText> GetHtmlTextAsync(int moduleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, moduleId, PermissionNames.View))
|
||||
{
|
||||
var htmltexts = await _htmlText.GetHtmlTextsAsync(moduleId);
|
||||
if (htmltexts != null && htmltexts.Any())
|
||||
{
|
||||
return htmltexts.OrderByDescending(item => item.CreatedOn).First();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Task.FromResult(GetCachedHtmlTexts(moduleId)?.OrderByDescending(item => item.CreatedOn).FirstOrDefault());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -64,11 +60,11 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
public Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, moduleId, PermissionNames.View))
|
||||
{
|
||||
return await _htmlText.GetHtmlTextAsync(htmlTextId);
|
||||
return Task.FromResult(GetCachedHtmlTexts(moduleId)?.FirstOrDefault(item => item.HtmlTextId == htmlTextId));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -77,11 +73,12 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||
public Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, htmlText.ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
htmlText = await _htmlText.AddHtmlTextAsync(htmlText);
|
||||
htmlText = _htmlText.AddHtmlText(htmlText);
|
||||
ClearCache(htmlText.ModuleId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Html/Text Added {HtmlText}", htmlText);
|
||||
}
|
||||
else
|
||||
|
@ -89,20 +86,36 @@ namespace Oqtane.Modules.HtmlText.Services
|
|||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Add Attempt {HtmlText}", htmlText);
|
||||
htmlText = null;
|
||||
}
|
||||
return htmlText;
|
||||
return Task.FromResult(htmlText);
|
||||
}
|
||||
|
||||
public async Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
public Task DeleteHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, moduleId, PermissionNames.Edit))
|
||||
{
|
||||
await _htmlText.DeleteHtmlTextAsync(htmlTextId);
|
||||
_htmlText.DeleteHtmlText(htmlTextId);
|
||||
ClearCache(moduleId);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Html/Text Deleted {HtmlTextId}", htmlTextId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Html/Text Delete Attempt {HtmlTextId} {ModuleId}", htmlTextId, moduleId);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private List<Models.HtmlText> GetCachedHtmlTexts(int moduleId)
|
||||
{
|
||||
return _cache.GetOrCreate($"HtmlText:{_alias.SiteKey}:{moduleId}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
return _htmlText.GetHtmlTexts(moduleId).ToList();
|
||||
});
|
||||
}
|
||||
|
||||
private void ClearCache(int moduleId)
|
||||
{
|
||||
_cache.Remove($"HtmlText:{_alias.SiteKey}:{moduleId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -11,7 +11,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -33,21 +33,21 @@
|
|||
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.62" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.7" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.8" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Oqtane.Providers
|
|||
ContentModifiedOn = searchContent.ContentModifiedOn,
|
||||
SearchContentProperties = searchContent.SearchContentProperties,
|
||||
Snippet = BuildSnippet(searchContent, searchQuery),
|
||||
Score = CalculateScore(searchContent, searchQuery)
|
||||
Score = (searchContent.Count / 100f)
|
||||
};
|
||||
|
||||
return searchResult;
|
||||
|
@ -99,17 +99,6 @@ namespace Oqtane.Providers
|
|||
return snippet;
|
||||
}
|
||||
|
||||
private float CalculateScore(SearchContent searchContent, SearchQuery searchQuery)
|
||||
{
|
||||
var score = 0f;
|
||||
foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords))
|
||||
{
|
||||
score += searchContent.SearchContentWords.Where(i => i.SearchWord.Word.StartsWith(keyword)).Sum(i => i.Count);
|
||||
}
|
||||
|
||||
return score / 100;
|
||||
}
|
||||
|
||||
public Task SaveSearchContent(SearchContent searchContent, Dictionary<string, string> siteSettings)
|
||||
{
|
||||
// remove existing search content
|
||||
|
|
|
@ -6,15 +6,6 @@ namespace Oqtane.Repository
|
|||
{
|
||||
public interface ISiteRepository
|
||||
{
|
||||
// asynchronous methods
|
||||
Task<IEnumerable<Site>> GetSitesAsync();
|
||||
Task<Site> AddSiteAsync(Site site);
|
||||
Task<Site> UpdateSiteAsync(Site site);
|
||||
Task<Site> GetSiteAsync(int siteId);
|
||||
Task<Site> GetSiteAsync(int siteId, bool tracking);
|
||||
Task DeleteSiteAsync(int siteId);
|
||||
|
||||
// synchronous methods
|
||||
IEnumerable<Site> GetSites();
|
||||
Site AddSite(Site site);
|
||||
Site UpdateSite(Site site);
|
||||
|
|
|
@ -185,6 +185,7 @@ namespace Oqtane.Repository
|
|||
if (siteId != -1)
|
||||
{
|
||||
var siteKey = _tenants.GetAlias().SiteKey;
|
||||
var assemblies = new List<string>();
|
||||
|
||||
// get all module definition permissions for site
|
||||
List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
|
||||
|
@ -193,12 +194,11 @@ namespace Oqtane.Repository
|
|||
var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList();
|
||||
|
||||
// populate module definition site settings and permissions
|
||||
var serverState = _serverState.GetServerState(siteKey);
|
||||
foreach (ModuleDefinition moduledefinition in ModuleDefinitions)
|
||||
{
|
||||
moduledefinition.SiteId = siteId;
|
||||
|
||||
var setting = settings.FirstOrDefault(item => item.EntityId == moduledefinition.ModuleDefinitionId && item.SettingName == $"{settingprefix}{_tenants.GetAlias().SiteKey}");
|
||||
var setting = settings.FirstOrDefault(item => item.EntityId == moduledefinition.ModuleDefinitionId && item.SettingName == $"{settingprefix}{siteKey}");
|
||||
if (setting != null)
|
||||
{
|
||||
moduledefinition.IsEnabled = bool.Parse(setting.SettingValue);
|
||||
|
@ -211,17 +211,17 @@ namespace Oqtane.Repository
|
|||
if (moduledefinition.IsEnabled)
|
||||
{
|
||||
// build list of assemblies for site
|
||||
if (!serverState.Assemblies.Contains(moduledefinition.AssemblyName))
|
||||
if (!assemblies.Contains(moduledefinition.AssemblyName))
|
||||
{
|
||||
serverState.Assemblies.Add(moduledefinition.AssemblyName);
|
||||
assemblies.Add(moduledefinition.AssemblyName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(moduledefinition.Dependencies))
|
||||
{
|
||||
foreach (var assembly in moduledefinition.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse())
|
||||
{
|
||||
if (!serverState.Assemblies.Contains(assembly.Trim()))
|
||||
if (!assemblies.Contains(assembly.Trim()))
|
||||
{
|
||||
serverState.Assemblies.Insert(0, assembly.Trim());
|
||||
assemblies.Insert(0, assembly.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,13 @@ namespace Oqtane.Repository
|
|||
}
|
||||
}
|
||||
|
||||
// cache site assemblies
|
||||
var serverState = _serverState.GetServerState(siteKey);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
if (!serverState.Assemblies.Contains(assembly)) serverState.Assemblies.Add(assembly);
|
||||
}
|
||||
|
||||
// clean up any orphaned permissions
|
||||
var ids = new HashSet<int>(ModuleDefinitions.Select(item => item.ModuleDefinitionId));
|
||||
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
|
||||
|
@ -20,50 +24,83 @@ namespace Oqtane.Repository
|
|||
public async Task<IEnumerable<SearchContent>> GetSearchContentsAsync(SearchQuery searchQuery)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
var searchContents = db.SearchContent.AsNoTracking()
|
||||
.Include(i => i.SearchContentProperties)
|
||||
.Include(i => i.SearchContentWords)
|
||||
.ThenInclude(w => w.SearchWord)
|
||||
.Where(i => i.SiteId == searchQuery.SiteId);
|
||||
|
||||
var keywords = SearchUtils.GetKeywords(searchQuery.Keywords);
|
||||
|
||||
var searchContents = db.SearchContentWord
|
||||
.AsNoTracking()
|
||||
.Include(item => item.SearchContent)
|
||||
.Include(item => item.SearchWord)
|
||||
.Where(item => item.SearchContent.SiteId == searchQuery.SiteId)
|
||||
.FilterByItems(keywords, (item, keyword) => item.SearchWord.Word.StartsWith(keyword), true)
|
||||
.GroupBy(item => new
|
||||
{
|
||||
item.SearchContent.SearchContentId,
|
||||
item.SearchContent.SiteId,
|
||||
item.SearchContent.EntityName,
|
||||
item.SearchContent.EntityId,
|
||||
item.SearchContent.Title,
|
||||
item.SearchContent.Description,
|
||||
item.SearchContent.Body,
|
||||
item.SearchContent.Url,
|
||||
item.SearchContent.Permissions,
|
||||
item.SearchContent.ContentModifiedBy,
|
||||
item.SearchContent.ContentModifiedOn,
|
||||
item.SearchContent.AdditionalContent,
|
||||
item.SearchContent.CreatedOn
|
||||
})
|
||||
.Select(result => new SearchContent
|
||||
{
|
||||
SearchContentId = result.Key.SearchContentId,
|
||||
SiteId = result.Key.SiteId,
|
||||
EntityName = result.Key.EntityName,
|
||||
EntityId = result.Key.EntityId,
|
||||
Title = result.Key.Title,
|
||||
Description = result.Key.Description,
|
||||
Body = result.Key.Body,
|
||||
Url = result.Key.Url,
|
||||
Permissions = result.Key.Permissions,
|
||||
ContentModifiedBy = result.Key.ContentModifiedBy,
|
||||
ContentModifiedOn = result.Key.ContentModifiedOn,
|
||||
AdditionalContent = result.Key.AdditionalContent,
|
||||
CreatedOn = result.Key.CreatedOn,
|
||||
Count = result.Sum(group => group.Count)
|
||||
});
|
||||
|
||||
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
||||
{
|
||||
searchContents = searchContents.Include(item => item.SearchContentProperties);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(searchQuery.IncludeEntities))
|
||||
{
|
||||
searchContents = searchContents.Where(i => searchQuery.IncludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(i.EntityName));
|
||||
searchContents = searchContents.Where(item => searchQuery.IncludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(item.EntityName));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(searchQuery.ExcludeEntities))
|
||||
{
|
||||
searchContents = searchContents.Where(i => !searchQuery.ExcludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(i.EntityName));
|
||||
searchContents = searchContents.Where(item => !searchQuery.ExcludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(item.EntityName));
|
||||
}
|
||||
|
||||
if (searchQuery.FromDate != DateTime.MinValue)
|
||||
if (searchQuery.FromDate.Date != DateTime.MinValue.Date)
|
||||
{
|
||||
searchContents = searchContents.Where(i => i.ContentModifiedOn >= searchQuery.FromDate);
|
||||
searchContents = searchContents.Where(item => item.ContentModifiedOn >= searchQuery.FromDate);
|
||||
}
|
||||
|
||||
if (searchQuery.ToDate != DateTime.MaxValue)
|
||||
if (searchQuery.ToDate.Date != DateTime.MaxValue.Date)
|
||||
{
|
||||
searchContents = searchContents.Where(i => i.ContentModifiedOn <= searchQuery.ToDate);
|
||||
searchContents = searchContents.Where(item => item.ContentModifiedOn <= searchQuery.ToDate);
|
||||
}
|
||||
|
||||
if (searchQuery.Properties != null && searchQuery.Properties.Any())
|
||||
{
|
||||
foreach (var property in searchQuery.Properties)
|
||||
{
|
||||
searchContents = searchContents.Where(i => i.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value));
|
||||
searchContents = searchContents.Where(item => item.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value));
|
||||
}
|
||||
}
|
||||
|
||||
var filteredContentList = new List<SearchContent>();
|
||||
if (!string.IsNullOrEmpty(searchQuery.Keywords))
|
||||
{
|
||||
foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords))
|
||||
{
|
||||
filteredContentList.AddRange(await searchContents.Where(i => i.SearchContentWords.Any(w => w.SearchWord.Word.StartsWith(keyword))).ToListAsync());
|
||||
}
|
||||
}
|
||||
|
||||
return filteredContentList.DistinctBy(i => i.UniqueKey);
|
||||
return await searchContents.ToListAsync();
|
||||
}
|
||||
|
||||
public SearchContent AddSearchContent(SearchContent searchContent)
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -51,58 +50,6 @@ namespace Oqtane.Repository
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
// asynchronous methods
|
||||
public async Task<IEnumerable<Site>> GetSitesAsync()
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return await db.Site.OrderBy(item => item.Name).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Site> AddSiteAsync(Site site)
|
||||
{
|
||||
site.SiteGuid = Guid.NewGuid().ToString();
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Site.Add(site);
|
||||
await db.SaveChangesAsync();
|
||||
CreateSite(site);
|
||||
return site;
|
||||
}
|
||||
|
||||
public async Task<Site> UpdateSiteAsync(Site site)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry(site).State = EntityState.Modified;
|
||||
await db.SaveChangesAsync();
|
||||
return site;
|
||||
}
|
||||
|
||||
public async Task<Site> GetSiteAsync(int siteId)
|
||||
{
|
||||
return await GetSiteAsync(siteId, true);
|
||||
}
|
||||
|
||||
public async Task<Site> GetSiteAsync(int siteId, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return await db.Site.FindAsync(siteId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await db.Site.AsNoTracking().FirstOrDefaultAsync(item => item.SiteId == siteId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteSiteAsync(int siteId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
var site = db.Site.Find(siteId);
|
||||
db.Site.Remove(site);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// synchronous methods
|
||||
public IEnumerable<Site> GetSites()
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
|
|
|
@ -161,12 +161,12 @@ namespace Oqtane.Repository
|
|||
if (siteId != -1)
|
||||
{
|
||||
var siteKey = _tenants.GetAlias().SiteKey;
|
||||
var assemblies = new List<string>();
|
||||
|
||||
// get settings for site
|
||||
var settings = _settings.GetSettings(EntityNames.Theme).ToList();
|
||||
|
||||
// populate theme site settings
|
||||
var serverState = _serverState.GetServerState(siteKey);
|
||||
foreach (Theme theme in Themes)
|
||||
{
|
||||
theme.SiteId = siteId;
|
||||
|
@ -184,22 +184,29 @@ namespace Oqtane.Repository
|
|||
if (theme.IsEnabled)
|
||||
{
|
||||
// build list of assemblies for site
|
||||
if (!serverState.Assemblies.Contains(theme.AssemblyName))
|
||||
if (!assemblies.Contains(theme.AssemblyName))
|
||||
{
|
||||
serverState.Assemblies.Add(theme.AssemblyName);
|
||||
assemblies.Add(theme.AssemblyName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(theme.Dependencies))
|
||||
{
|
||||
foreach (var assembly in theme.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse())
|
||||
{
|
||||
if (!serverState.Assemblies.Contains(assembly.Trim()))
|
||||
if (!assemblies.Contains(assembly.Trim()))
|
||||
{
|
||||
serverState.Assemblies.Insert(0, assembly.Trim());
|
||||
assemblies.Insert(0, assembly.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cache site assemblies
|
||||
var serverState = _serverState.GetServerState(siteKey);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
if (!serverState.Assemblies.Contains(assembly)) serverState.Assemblies.Add(assembly);
|
||||
}
|
||||
}
|
||||
|
||||
return Themes;
|
||||
|
|
|
@ -37,12 +37,27 @@ namespace Oqtane.Services
|
|||
var searchProvider = GetSearchProvider(searchQuery.SiteId);
|
||||
var searchResults = await searchProvider.GetSearchResultsAsync(searchQuery);
|
||||
|
||||
var totalResults = 0;
|
||||
|
||||
// trim results
|
||||
var results = searchResults.Where(i => HasViewPermission(i, searchQuery))
|
||||
.OrderBy(i => i.Url).ThenByDescending(i => i.Score)
|
||||
.DistinctBy(i => i.Url);
|
||||
// security trim results and aggregate by Url
|
||||
var results = searchResults.Where(item => HasViewPermission(item, searchQuery))
|
||||
.OrderBy(item => item.Url).ThenByDescending(item => item.Score)
|
||||
.GroupBy(group => group.Url)
|
||||
.Select(result => new SearchResult
|
||||
{
|
||||
SearchContentId = result.First().SearchContentId,
|
||||
SiteId = result.First().SiteId,
|
||||
EntityName = result.First().EntityName,
|
||||
EntityId = result.First().EntityId,
|
||||
Title = result.First().Title,
|
||||
Description = result.First().Description,
|
||||
Body = result.First().Body,
|
||||
Url = result.First().Url,
|
||||
Permissions = result.First().Permissions,
|
||||
ContentModifiedBy = result.First().ContentModifiedBy,
|
||||
ContentModifiedOn = result.First().ContentModifiedOn,
|
||||
SearchContentProperties = result.First().SearchContentProperties,
|
||||
Snippet = result.First().Snippet,
|
||||
Score = result.Sum(group => group.Score) // recalculate score
|
||||
});
|
||||
|
||||
// sort results
|
||||
if (searchQuery.SortOrder == SearchSortOrder.Descending)
|
||||
|
@ -76,12 +91,10 @@ namespace Oqtane.Services
|
|||
}
|
||||
}
|
||||
|
||||
totalResults = results.Count();
|
||||
|
||||
return new SearchResults
|
||||
{
|
||||
Results = results.Skip(searchQuery.PageIndex * searchQuery.PageSize).Take(searchQuery.PageSize).ToList(),
|
||||
TotalResults = totalResults
|
||||
TotalResults = results.Count()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -50,23 +50,23 @@ namespace Oqtane.Services
|
|||
_accessor = accessor;
|
||||
}
|
||||
|
||||
public async Task<List<Site>> GetSitesAsync()
|
||||
public Task<List<Site>> GetSitesAsync()
|
||||
{
|
||||
List<Site> sites = new List<Site>();
|
||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
|
||||
{
|
||||
sites = (await _sites.GetSitesAsync()).ToList();
|
||||
sites = _sites.GetSites().ToList();
|
||||
}
|
||||
return sites;
|
||||
return Task.FromResult(sites);
|
||||
}
|
||||
|
||||
public async Task<Site> GetSiteAsync(int siteId)
|
||||
public Task<Site> GetSiteAsync(int siteId)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var site = await _cache.GetOrCreateAsync($"site:{alias.SiteKey}", async entry =>
|
||||
var site = _cache.GetOrCreate($"site:{alias.SiteKey}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
return await GetSite(siteId);
|
||||
return GetSite(siteId);
|
||||
});
|
||||
|
||||
// trim pages based on user permissions
|
||||
|
@ -83,13 +83,13 @@ namespace Oqtane.Services
|
|||
site = site.Clone(site);
|
||||
site.Pages = pages;
|
||||
|
||||
return site;
|
||||
return Task.FromResult(site);
|
||||
}
|
||||
|
||||
private async Task<Site> GetSite(int siteid)
|
||||
private Site GetSite(int siteid)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var site = await _sites.GetSiteAsync(siteid);
|
||||
var site = _sites.GetSite(siteid);
|
||||
if (site != null && site.SiteId == alias.SiteId)
|
||||
{
|
||||
// site settings
|
||||
|
@ -116,7 +116,7 @@ namespace Oqtane.Services
|
|||
site.Pages = GetPagesHierarchy(site.Pages);
|
||||
|
||||
// framework modules
|
||||
var modules = await GetModulesAsync(site.SiteId);
|
||||
var modules = GetModules(site.SiteId);
|
||||
site.Settings.Add(Constants.AdminDashboardModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule).ModuleId.ToString());
|
||||
site.Settings.Add(Constants.PageManagementModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule).ModuleId.ToString());
|
||||
|
||||
|
@ -179,11 +179,11 @@ namespace Oqtane.Services
|
|||
return hierarchy;
|
||||
}
|
||||
|
||||
public async Task<Site> AddSiteAsync(Site site)
|
||||
public Task<Site> AddSiteAsync(Site site)
|
||||
{
|
||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
|
||||
{
|
||||
site = await _sites.AddSiteAsync(site);
|
||||
site = _sites.AddSite(site);
|
||||
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.Site, site.SiteId, SyncEventActions.Create);
|
||||
_logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site);
|
||||
}
|
||||
|
@ -191,18 +191,18 @@ namespace Oqtane.Services
|
|||
{
|
||||
site = null;
|
||||
}
|
||||
return site;
|
||||
return Task.FromResult(site);
|
||||
}
|
||||
|
||||
public async Task<Site> UpdateSiteAsync(Site site)
|
||||
public Task<Site> UpdateSiteAsync(Site site)
|
||||
{
|
||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Admin))
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var current = await _sites.GetSiteAsync(site.SiteId, false);
|
||||
var current = _sites.GetSite(site.SiteId, false);
|
||||
if (site.SiteId == alias.SiteId && site.TenantId == alias.TenantId && current != null)
|
||||
{
|
||||
site = await _sites.UpdateSiteAsync(site);
|
||||
site = _sites.UpdateSite(site);
|
||||
_syncManager.AddSyncEvent(alias, EntityNames.Site, site.SiteId, SyncEventActions.Update);
|
||||
string action = SyncEventActions.Refresh;
|
||||
if (current.RenderMode != site.RenderMode || current.Runtime != site.Runtime)
|
||||
|
@ -222,19 +222,20 @@ namespace Oqtane.Services
|
|||
{
|
||||
site = null;
|
||||
}
|
||||
return site;
|
||||
return Task.FromResult(site);
|
||||
}
|
||||
|
||||
public async Task DeleteSiteAsync(int siteId)
|
||||
public Task DeleteSiteAsync(int siteId)
|
||||
{
|
||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var site = await _sites.GetSiteAsync(siteId);
|
||||
var site = _sites.GetSite(siteId);
|
||||
if (site != null && site.SiteId == alias.SiteId)
|
||||
{
|
||||
await _sites.DeleteSiteAsync(siteId);
|
||||
_sites.DeleteSite(siteId);
|
||||
_syncManager.AddSyncEvent(alias, EntityNames.Site, site.SiteId, SyncEventActions.Delete);
|
||||
_syncManager.AddSyncEvent(alias, EntityNames.Site, site.SiteId, SyncEventActions.Refresh);
|
||||
_logger.Log(siteId, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", siteId);
|
||||
}
|
||||
else
|
||||
|
@ -242,15 +243,16 @@ namespace Oqtane.Services
|
|||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Delete Attempt {SiteId}", siteId);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<List<Module>> GetModulesAsync(int siteId, int pageId)
|
||||
public Task<List<Module>> GetModulesAsync(int siteId, int pageId)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
var sitemodules = await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry =>
|
||||
var sitemodules = _cache.GetOrCreate($"modules:{alias.SiteKey}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
return await GetModulesAsync(siteId);
|
||||
return GetModules(siteId);
|
||||
});
|
||||
|
||||
var modules = new List<Module>();
|
||||
|
@ -261,23 +263,21 @@ namespace Oqtane.Services
|
|||
modules.Add(module);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
return Task.FromResult(modules);
|
||||
}
|
||||
|
||||
public async Task<List<Module>> GetModulesAsync(int siteId)
|
||||
private List<Module> GetModules(int siteId)
|
||||
{
|
||||
var alias = _tenantManager.GetAlias();
|
||||
return await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry =>
|
||||
return _cache.GetOrCreate($"modules:{alias.SiteKey}", entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
|
||||
return await GetModules(siteId);
|
||||
return GetPageModules(siteId);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<List<Module>> GetModules(int siteId)
|
||||
private List<Module> GetPageModules(int siteId)
|
||||
{
|
||||
await Task.Yield(); // force method to async
|
||||
|
||||
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList();
|
||||
var settings = _settings.GetSettings(EntityNames.Module).ToList();
|
||||
var modules = new List<Module>();
|
||||
|
|
|
@ -100,6 +100,7 @@ namespace Oqtane
|
|||
options.Cookie.Name = Constants.AntiForgeryTokenCookieName;
|
||||
options.Cookie.SameSite = SameSiteMode.Strict;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||
options.Cookie.HttpOnly = true;
|
||||
});
|
||||
|
||||
services.AddIdentityCore<IdentityUser>(options => { })
|
||||
|
|
|
@ -2,13 +2,12 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public class [Module]Service : ServiceBase, I[Module]Service, IService
|
||||
public class [Module]Service : ServiceBase, I[Module]Service
|
||||
{
|
||||
public [Module]Service(IHttpClientFactory http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
|
|
|
@ -11,12 +11,5 @@ namespace [Owner].Module.[Module].Repository
|
|||
Models.[Module] Add[Module](Models.[Module] [Module]);
|
||||
Models.[Module] Update[Module](Models.[Module] [Module]);
|
||||
void Delete[Module](int [Module]Id);
|
||||
|
||||
Task<IEnumerable<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id);
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, bool tracking);
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
Task Delete[Module]Async(int [Module]Id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ using Microsoft.EntityFrameworkCore;
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Modules;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace [Owner].Module.[Module].Repository
|
||||
{
|
||||
|
@ -62,54 +61,5 @@ namespace [Owner].Module.[Module].Repository
|
|||
db.[Module].Remove([Module]);
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<Models.[Module]>> Get[Module]sAsync(int ModuleId)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
return await db.[Module].Where(item => item.ModuleId == ModuleId).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id)
|
||||
{
|
||||
return await Get[Module]Async([Module]Id, true);
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, bool tracking)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return await db.[Module].FindAsync([Module]Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await db.[Module].AsNoTracking().FirstOrDefaultAsync(item => item.[Module]Id == [Module]Id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.[Module].Add([Module]);
|
||||
await db.SaveChangesAsync();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.Entry([Module]).State = EntityState.Modified;
|
||||
await db.SaveChangesAsync();
|
||||
return [Module];
|
||||
}
|
||||
|
||||
public async Task Delete[Module]Async(int [Module]Id)
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.[Module] [Module] = db.[Module].Find([Module]Id);
|
||||
db.[Module].Remove([Module]);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,13 @@ using Microsoft.AspNetCore.Http;
|
|||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Security;
|
||||
using Oqtane.Shared;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public class Server[Module]Service : I[Module]Service, ITransientService
|
||||
public class Server[Module]Service : I[Module]Service
|
||||
{
|
||||
private readonly I[Module]Repository _[Module]Repository;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
|
@ -29,11 +28,11 @@ namespace [Owner].Module.[Module].Services
|
|||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
public async Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
|
||||
public Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||
{
|
||||
return (await _[Module]Repository.Get[Module]sAsync(ModuleId)).ToList();
|
||||
return Task.FromResult(_[Module]Repository.Get[Module]s(ModuleId).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -42,11 +41,11 @@ namespace [Owner].Module.[Module].Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
||||
public Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||
{
|
||||
return await _[Module]Repository.Get[Module]Async([Module]Id);
|
||||
return Task.FromResult(_[Module]Repository.Get[Module]([Module]Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -55,11 +54,11 @@ namespace [Owner].Module.[Module].Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||
public Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
[Module] = await _[Module]Repository.Add[Module]Async([Module]);
|
||||
[Module] = _[Module]Repository.Add[Module]([Module]);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "[Module] Added {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
|
@ -67,14 +66,14 @@ namespace [Owner].Module.[Module].Services
|
|||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Add Attempt {[Module]}", [Module]);
|
||||
[Module] = null;
|
||||
}
|
||||
return [Module];
|
||||
return Task.FromResult([Module]);
|
||||
}
|
||||
|
||||
public async Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
|
||||
public Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module])
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, [Module].ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
[Module] = await _[Module]Repository.Update[Module]Async([Module]);
|
||||
[Module] = _[Module]Repository.Update[Module]([Module]);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
|
@ -82,20 +81,21 @@ namespace [Owner].Module.[Module].Services
|
|||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Update Attempt {[Module]}", [Module]);
|
||||
[Module] = null;
|
||||
}
|
||||
return [Module];
|
||||
return Task.FromResult([Module]);
|
||||
}
|
||||
|
||||
public async Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
||||
public Task Delete[Module]Async(int [Module]Id, int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||
{
|
||||
await _[Module]Repository.Delete[Module]Async([Module]Id);
|
||||
_[Module]Repository.Delete[Module]([Module]Id);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "[Module] Deleted {[Module]Id}", [Module]Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized [Module] Delete Attempt {[Module]Id} {ModuleId}", [Module]Id, ModuleId);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Infrastructure;
|
||||
using [Owner].Module.[Module].Repository;
|
||||
using [Owner].Module.[Module].Services;
|
||||
|
||||
namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
public class [Module]ServerStartup : IServerStartup
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
public void ConfigureMvc(IMvcBuilder mvcBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<I[Module]Service, Server[Module]Service>();
|
||||
services.AddDbContextFactory<[Module]Context>(opt => { }, ServiceLifetime.Transient);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,10 +19,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -17,9 +17,10 @@ namespace [Owner].Theme.[Theme]
|
|||
Resources = new List<Resource>()
|
||||
{
|
||||
// obtained from https://cdnjs.com/libraries
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css", Integrity = "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==", CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", 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.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous" }
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js", Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==", CrossOrigin = "anonymous" }
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -233,7 +233,7 @@ app {
|
|||
}
|
||||
|
||||
.app-form-inline {
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
}
|
||||
.app-search{
|
||||
display: inline-block;
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace Oqtane.Models
|
|||
/// Protocol for the request from which the alias was resolved (ie. http or https )
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string Protocol { get; set; } = "https"; // default value
|
||||
public string Protocol { get; set; } = "https://"; // default value
|
||||
|
||||
/// <summary>
|
||||
/// Base Url for static resources (note that this will only be set for remote clients)
|
||||
|
|
|
@ -31,21 +31,22 @@ namespace Oqtane.Models
|
|||
|
||||
public string AdditionalContent { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
public List<SearchContentProperty> SearchContentProperties { get; set; }
|
||||
|
||||
public DateTime CreatedOn { get; set; }
|
||||
|
||||
public List<SearchContentProperty> SearchContentProperties { get; set; } // only used during updates
|
||||
|
||||
[NotMapped]
|
||||
public int TenantId { get; set; }
|
||||
public int Count { get; set; } // only populated for queries
|
||||
|
||||
[NotMapped]
|
||||
public bool IsDeleted { get; set; } // only used during updates
|
||||
|
||||
[NotMapped]
|
||||
public int TenantId { get; set; } // only used during updates
|
||||
|
||||
[NotMapped]
|
||||
public string UniqueKey => $"{TenantId}:{SiteId}:{EntityName}:{EntityId}";
|
||||
|
||||
public List<SearchContentWord> SearchContentWords { get; set; }
|
||||
|
||||
// constructors
|
||||
public SearchContent() { }
|
||||
|
||||
|
|
|
@ -20,5 +20,7 @@ namespace Oqtane.Models
|
|||
public DateTime ModifiedOn { get; set; }
|
||||
|
||||
public SearchWord SearchWord { get; set; }
|
||||
|
||||
public SearchContent SearchContent { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -11,7 +11,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
@ -19,8 +19,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
||||
|
|
|
@ -4,8 +4,8 @@ namespace Oqtane.Shared
|
|||
{
|
||||
public class Constants
|
||||
{
|
||||
public static readonly string Version = "5.2.0";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0";
|
||||
public static readonly string Version = "5.2.1";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1";
|
||||
public const string PackageId = "Oqtane.Framework";
|
||||
public const string ClientId = "Oqtane.Client";
|
||||
public const string UpdaterPackageId = "Oqtane.Updater";
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>5.2.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
|
@ -11,7 +11,7 @@
|
|||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Latest Release
|
||||
|
||||
[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) was released on May 28, 2024 and is primarily a stabilization release. This release includes 68 pull requests by 9 different contributors, pushing the total number of project commits all-time to over 5300. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
[5.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0) was released on July 25, 2024 and is a major release including 109 pull requests by 8 different contributors, pushing the total number of project commits all-time to over 5600. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||
|
||||
[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json)
|
||||
|
||||
|
@ -18,7 +18,7 @@ Please note that this project is owned by the .NET Foundation and is governed by
|
|||
|
||||
**Using Version 5:**
|
||||
|
||||
- Install **[.NET 8.0.5 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**.
|
||||
- Install **[.NET 8.0.7 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**.
|
||||
|
||||
- Install the latest edition (v17.9 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
|
||||
|
||||
|
@ -63,6 +63,11 @@ Backlog (TBD)
|
|||
- [ ] Folder Providers
|
||||
- [ ] Generative AI Integration
|
||||
|
||||
[5.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0) (Jul 25, 2024)
|
||||
- [x] Site Content Search
|
||||
- [x] RichTextEditor extensibility
|
||||
- [x] Scalability and performance improvements
|
||||
|
||||
[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) (May 28, 2024)
|
||||
- [x] Stabilization improvements
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user