Compare commits

..

90 Commits

Author SHA1 Message Date
1f0347682e Merge pull request #4553 from oqtane/master
5.2.1 Release
2024-08-22 12:15:38 -04:00
1aee385679 Merge pull request #4552 from oqtane/dev
5.2.1 Release
2024-08-22 12:15:15 -04:00
5dd8191692 Merge pull request #4551 from sbwalker/dev
fixed required field validation in Search Results Settings
2024-08-22 12:10:34 -04:00
b6422f9b80 fixed required field validation in Search Results Settings 2024-08-22 12:10:15 -04:00
a6c2c9c92f Merge pull request #4550 from sbwalker/dev
use localized Yes/No values when displaying Site Urls Default? option
2024-08-22 10:53:43 -04:00
247c573a6e use localized Yes/No values when displaying Site Urls Default? option 2024-08-22 10:53:27 -04:00
aa435d6e94 Merge pull request #4548 from sbwalker/dev
fix #4546 - handle cache invalidation for site deletion
2024-08-22 08:26:44 -04:00
f6858c221b fix #4546 - handle cache invalidation for site deletion 2024-08-22 08:26:29 -04:00
437aa4510b Merge pull request #4547 from sbwalker/dev
fix #4545 - Site Settings - UI Component Settings changes not refreshed after saving
2024-08-22 08:09:18 -04:00
430572fb32 fix #4545 - Site Settings - UI Component Settings changes not refreshed after saving 2024-08-22 08:08:55 -04:00
9f0b755d6f Merge pull request #4543 from sbwalker/dev
fix login redirect issue in sub-site where user has navigated directly to login page
2024-08-21 15:17:47 -04:00
66acb55a57 fix login redirect issue in sub-site where user has navigated directly to login page 2024-08-21 15:17:29 -04:00
f936d4c36e Merge pull request #4542 from sbwalker/dev
fix #4536 - deleted modules appearing in Page Management - Modules panel
2024-08-20 16:18:45 -04:00
c3ddb8df56 fix #4536 - deleted modules appearing in Page Management - Modules panel 2024-08-20 16:18:31 -04:00
81ce920e69 Merge pull request #4541 from sbwalker/dev
fix issues in default template for Interactive Client (WebAssembly) scenarios
2024-08-20 15:34:05 -04:00
3cb875d139 fix issues in default template for Interactive Client (WebAssembly) scenarios 2024-08-20 15:33:46 -04:00
fdb217d5c6 Merge pull request #4540 from sbwalker/dev
set BaseAddress for IHttpClientFactory
2024-08-20 15:22:39 -04:00
085cae3b5f set BaseAddress for IHttpClientFactory 2024-08-20 15:22:27 -04:00
e2f99a1554 Merge pull request #4538 from sbwalker/dev
fix #4498 build ServerState Assemblies collection in a more thread safe manner
2024-08-20 14:03:02 -04:00
aee0c27da7 fix #4498 build ServerState Assemblies collection in a more thread safe manner 2024-08-20 14:02:37 -04:00
accbf4ad8b Merge pull request #4535 from sbwalker/dev
use existing SiteKey
2024-08-20 08:35:36 -04:00
0ac1901f6b use existing SiteKey 2024-08-20 08:35:23 -04:00
c0a0deea78 Merge pull request #4532 from sbwalker/dev
ensure form name is unique in ActionDialog
2024-08-19 16:58:48 -04:00
e3f099441c ensure form name is unique in ActionDialog 2024-08-19 16:58:33 -04:00
840dd25cd1 Merge pull request #4530 from sbwalker/dev
fix issues with ActionDialog in static rendering
2024-08-19 09:34:23 -04:00
a493969f9b fix issues with ActionDialog in static rendering 2024-08-19 09:34:08 -04:00
cca42a10a1 Merge pull request #4529 from sbwalker/dev
fix CSS
2024-08-19 09:05:00 -04:00
2f4aa98c3c fix CSS 2024-08-19 09:04:46 -04:00
175cb9588c Merge pull request #4524 from sbwalker/dev
prevent scroll position from resetting to top of page when querystring or hash changes
2024-08-16 15:01:43 -04:00
a8976e7559 prevent scroll position from resetting to top of page when querystring or hash changes 2024-08-16 15:01:25 -04:00
b663528fb0 Merge pull request #4521 from sbwalker/dev
add ability to extract zip file contents in File Management
2024-08-14 15:54:30 -04:00
1a2ad55677 add ability to extract zip file contents in File Management 2024-08-14 15:54:13 -04:00
513d2a88c0 Merge pull request #4520 from sbwalker/dev
move HtmlText caching from repository to service layer
2024-08-14 10:01:11 -04:00
57ef4c0396 move HtmlText caching from repository to service layer 2024-08-14 10:00:56 -04:00
e9599ca2f4 Merge pull request #4519 from sbwalker/dev
fix #4517 - index error due to duplicate records on upgrade
2024-08-14 08:19:28 -04:00
2fc6dbc222 fix #4517 - index error due to duplicate records on upgrade 2024-08-14 08:19:14 -04:00
225933c442 Merge pull request #4518 from sbwalker/dev
update nuspec files for 5.2.1
2024-08-14 08:12:46 -04:00
36e2f048d7 update nuspec files for 5.2.1 2024-08-14 08:12:31 -04:00
0e158bce59 Merge pull request #4515 from thabaum/update-version-5.2.1-and-dependencies
Fixes #4514 - Update version 5.2.1 and dependencies
2024-08-14 07:58:55 -04:00
202201fd31 Merge pull request #4516 from ijaz-saeed/dev
search settings translation entry
2024-08-14 07:55:11 -04:00
4f8c928f44 search settings translation entry 2024-08-14 11:57:06 +05:00
ada062cf00 Update version to 5.2.1 and dependencies 2024-08-13 15:53:45 -07:00
32dc12912a Update dependencies 2024-08-13 15:51:49 -07:00
671d21adf4 Update dependencies 2024-08-13 15:50:56 -07:00
4e3f8e4b67 Update dependencies 2024-08-13 15:49:15 -07:00
586bb62073 Update version to 5.2.1 and dependencies 2024-08-13 15:48:32 -07:00
151bf83ab1 Update version to 5.2.1 and dependencies 2024-08-13 15:46:45 -07:00
8c618edb5a Update version to 5.2.1 2024-08-13 15:45:23 -07:00
6d6b0cf8c9 Update version to 5.2.1 and dependencies 2024-08-13 15:44:45 -07:00
c610608890 Update version to 5.2.1 and dependencies 2024-08-13 15:43:27 -07:00
28da61daab Update version 5.2.1 and dependencies 2024-08-13 15:39:15 -07:00
cbfc90f60b Update Version To 5.2.1 and Dependencies To Latest 2024-08-13 15:36:20 -07:00
fec92959d4 Merge pull request #4513 from sbwalker/dev
move folder permissions grid to dedicated tab for consistency
2024-08-13 16:48:34 -04:00
bb00d81eba move folder permissions grid to dedicated tab for consistency 2024-08-13 16:48:22 -04:00
bb55644c06 Merge pull request #4512 from sbwalker/dev
improve file name and extension validation
2024-08-12 17:02:22 -04:00
16215847cd improve file name and extension validation 2024-08-12 17:02:07 -04:00
8ee9aed817 Merge pull request #4509 from sbwalker/dev
improve SettingService
2024-08-12 10:20:56 -04:00
515c6402b9 improve SettingService 2024-08-12 10:20:44 -04:00
d1b94ec203 Merge pull request #4507 from leigh-pointer/Permissions-4503
Fix for #4503 Module Custom Permissions not being shown
2024-08-10 17:54:51 -04:00
a037d9167e Fix for #4503 Module Custom Permissions not being shown 2024-08-10 21:25:32 +02:00
3054d33e62 Merge pull request #4493 from thabaum/set-samesite-lax-visitor-culture-cookies
Fix #4492: Updates Culture and Visitor cookies to use "Lax" SameSite and Secure Cookie Options
2024-08-10 14:08:01 -04:00
6651e641e1 Merge pull request #4505 from sbwalker/dev
add search reindex capability
2024-08-10 10:02:08 -04:00
35f873a342 add search reindex capability 2024-08-10 10:01:52 -04:00
2d03ff38a1 Merge pull request #4504 from sbwalker/dev
replace dynamic query with linq
2024-08-09 17:16:30 -04:00
44a3db417b replace dynamic query with linq 2024-08-09 17:16:17 -04:00
f9ca702a12 Merge pull request #4502 from sbwalker/dev
fix #4499 - page modules not loaded properly
2024-08-09 13:11:31 -04:00
4073ff38eb fix #4499 - page modules not loaded properly 2024-08-09 13:11:19 -04:00
f0e2c9f1b6 Merge pull request #4501 from sbwalker/dev
eliminate database call for authenticated users
2024-08-09 13:00:35 -04:00
cf040f51b5 eliminate database call for authenticated users 2024-08-09 13:00:20 -04:00
dcf919fb36 Adds AntiForgery Cookie setting options.Cookie.HttpOnly = true; 2024-08-08 12:24:42 -07:00
aa19b81a68 Merge pull request #4487 from leigh-pointer/TemplateUpdate
Update Theme Template to Bootstrap 5.3.3
2024-08-08 14:44:27 -04:00
db8d77365c Merge pull request #4479 from pollux/patch-1
Fix admin/pages not showing 404 for unauthorized users
2024-08-08 14:42:08 -04:00
280eaea84a Merge pull request #4497 from sbwalker/dev
improve search result performance and relevancy
2024-08-08 14:11:43 -04:00
340ef46469 improve search result performance and relevancy 2024-08-08 14:11:27 -04:00
a5f8651941 Revert previous cookie HttpOnly option 2024-08-07 16:24:18 -07:00
8a18ee548e Merge pull request #4494 from sbwalker/dev
add missing indexes
2024-08-07 16:55:48 -04:00
ef791aa22a add missing indexes 2024-08-07 16:55:35 -04:00
4bdf2e1cc0 Update AntiForgery Token Cookie Option to HTTPOnly = true; 2024-08-07 13:21:18 -07:00
ffa0ca9379 Updates Culture and Visitor cookies to use "Lax" SameSite and Secure cookie options 2024-08-07 11:52:53 -07:00
d3b3d46fc1 Return to standard Bootstrap 2024-08-07 20:01:41 +02:00
7e7dd8efa9 Update Theme Template to Bootstrap 5.3.3 2024-08-07 10:34:40 +02:00
b4506f1133 Merge pull request #4483 from sbwalker/dev
include "://" on default Alias Protocol for consistency
2024-08-05 07:51:07 -04:00
7350c79113 include "://" on default Alias Protocol for consistency 2024-08-05 07:50:54 -04:00
5f7b60d3f4 Merge pull request #4480 from leigh-pointer/Log2xHttp
Removed the extra "://" from the Log Manager
2024-08-05 07:50:06 -04:00
266495a611 Removed the extra ";//" from the Log Manager
{alias.Protocol} return with ";//"
2024-08-03 12:26:33 +02:00
f5b4a7e77b Fix admin/pages not showing 404 for unauthorized users 2024-08-02 11:49:55 +02:00
51ed0f6487 Merge pull request #4472 from sbwalker/dev
fix #4471 - search pages not being added on upgrade
2024-07-27 09:51:22 -04:00
bd70def18a fix #4471 - search pages not being added on upgrade 2024-07-27 09:51:02 -04:00
93a9cf3b31 Update README.md 2024-07-25 13:22:02 -04:00
751287999f Update README.md 2024-07-25 11:57:24 -04:00
82 changed files with 885 additions and 683 deletions

View File

@ -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);
}
}
}

View File

@ -8,6 +8,8 @@
@if (_folders != null)
{
<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">
@ -59,16 +61,24 @@
<input id="capacity" class="form-control" @bind="@_capacity" required />
</div>
</div>
</div>
@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">
<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>
<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)"&nbsp;")
<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 {

View File

@ -152,7 +152,8 @@ else
private async Task Refresh()
{
ShowProgressIndicator();
await GetJobs();
StateHasChanged();
HideProgressIndicator();
}
}

View File

@ -78,7 +78,8 @@ else
private async Task Refresh()
{
ShowProgressIndicator();
await GetJobLogs();
StateHasChanged();
HideProgressIndicator();
}
}

View File

@ -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);
}

View File

@ -5,6 +5,7 @@
@inject NavigationManager NavigationManager
@inject IPageService PageService
@inject IPageModuleService PageModuleService
@inject IModuleService ModuleService
@inject IThemeService ThemeService
@inject ISystemService SystemService
@inject IStringLocalizer<Edit> Localizer
@ -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;

View File

@ -53,29 +53,30 @@
</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()
{
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()
@ -99,4 +100,22 @@
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);
}
}
}

View File

@ -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()

View File

@ -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">

View File

@ -309,7 +309,7 @@
}
</td>
<td>@context.Name</td>
<td>@context.IsDefault</td>
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
}
else
{

View File

@ -51,25 +51,25 @@ else
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<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>
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
<button type="submit" class="btn-close" aria-label="Close"></button>
</form>
</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);

View File

@ -314,7 +314,6 @@
await SetImage();
await OnSelect.InvokeAsync(FileId);
StateHasChanged();
}
private async Task SetImage()

View File

@ -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))
{

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -75,5 +75,10 @@ namespace Oqtane.Services
{
return await GetByteArrayAsync($"{Apiurl}/download/{fileId}");
}
public async Task UnzipFileAsync(int fileId)
{
await PutAsync($"{Apiurl}/unzip/{fileId}");
}
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
@ -170,15 +171,20 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && folder != null && folder.SiteId == _alias.SiteId)
{
if (_userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit))
{
if (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
{
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);
@ -187,6 +193,13 @@ namespace Oqtane.Controllers
}
}
else
{
_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;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Post Attempt {File}", file);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
@ -212,6 +225,8 @@ namespace Oqtane.Controllers
if (ModelState.IsValid && file.Folder.SiteId == _alias.SiteId && file.FileId == id && File != null // ensure file exists
&& _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 (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
{
if (File.Name != file.Name || File.FolderId != file.FolderId)
{
@ -238,6 +253,13 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
}
else
{
_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;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Put Attempt {File}", file);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
@ -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,38 +362,21 @@ 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);
string folderPath = _folders.GetFolderPath(folder);
CreateDirectory(folderPath);
// remove file if it already exists
string targetPath = Path.Combine(folderPath, name);
if (System.IO.File.Exists(targetPath))
{
System.IO.File.Delete(targetPath);
@ -341,6 +397,7 @@ namespace Oqtane.Controllers
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);
}
}
@ -350,6 +407,13 @@ namespace Oqtane.Controllers
}
}
else
{
_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
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Download Attempt {FolderId} {Url}", folderid, url);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
@ -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(".", ""));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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

View 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);
}
}
}
}

View File

@ -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}");
}
}
}
}

View File

@ -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))

View File

@ -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))

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}

View 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
}
}
}

View File

@ -1,4 +1,3 @@
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;

View 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
}
}
}

View File

@ -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}",

View File

@ -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,11 +50,8 @@ namespace Oqtane.Modules.HtmlText.Manager
{
var searchContents = new List<SearchContent>();
var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId);
if (htmltexts != null && htmltexts.Any())
{
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
if (htmltext.CreatedOn >= lastIndexedOn)
var htmltext = _htmlText.GetHtmlTexts(pageModule.ModuleId)?.OrderByDescending(item => item.CreatedOn).FirstOrDefault();
if (htmltext != null && htmltext.CreatedOn >= lastIndexedOn)
{
searchContents.Add(new SearchContent
{
@ -66,7 +60,6 @@ namespace Oqtane.Modules.HtmlText.Manager
ContentModifiedOn = htmltext.CreatedOn
});
}
}
return Task.FromResult(searchContents);
}

View File

@ -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();
});
}
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}");
}
}
}

View File

@ -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);
}
}

View File

@ -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}");
}
}
}

View File

@ -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" />

View File

@ -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

View File

@ -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);

View File

@ -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)))

View File

@ -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)

View File

@ -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();

View File

@ -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;

View File

@ -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()
};
}

View File

@ -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>();

View File

@ -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 => { })

View File

@ -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) { }

View File

@ -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" />

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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" }
}
};

View File

@ -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>

View File

@ -233,7 +233,7 @@ app {
}
.app-form-inline {
display: inline-block;
display: inline;
}
.app-search{
display: inline-block;

View File

@ -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)

View File

@ -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() { }

View File

@ -20,5 +20,7 @@ namespace Oqtane.Models
public DateTime ModifiedOn { get; set; }
public SearchWord SearchWord { get; set; }
public SearchContent SearchContent { get; set; }
}
}

View File

@ -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" />

View File

@ -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";

View File

@ -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>

View File

@ -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.
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](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