Compare commits

...

134 Commits

Author SHA1 Message Date
8d7845a44d Merge pull request #4944 from oqtane/master
6.0.1 Release
2024-12-20 16:52:31 -05:00
3a15e6e5e9 Merge pull request #4943 from oqtane/dev
6.0.1 Release
2024-12-20 16:52:10 -05:00
3b8a51e855 Merge pull request #4942 from sbwalker/dev
fix reload script to use static array rather than a live HtmlCollection
2024-12-20 15:39:50 -05:00
f2cb34cc35 fix reload script to use static array rather than a live HtmlCollection 2024-12-20 15:39:35 -05:00
723ce62a34 Merge pull request #4940 from sbwalker/dev
write upgrade errors to log rather than console
2024-12-20 14:38:34 -05:00
2c9a2ea021 write upgrade errors to log rather than console 2024-12-20 14:38:21 -05:00
2be008d6d1 Merge pull request #4939 from sbwalker/dev
fix compilation issues in PR #4782
2024-12-20 12:36:24 -05:00
7fb51bdd0a fix compilation issues in PR #4782 2024-12-20 12:36:10 -05:00
abdbe3694f Merge pull request #4782 from thabaum/edit-add-page-scrolltotoppage-error
Fixes #4781: Adds Edit + Add Page ScrollToTopPage() On Settings Tab Form Error Messages
2024-12-20 12:26:55 -05:00
bd87e5012f Merge pull request #4938 from sbwalker/dev
fix documentation
2024-12-20 11:55:50 -05:00
55e18f2364 fix documentation 2024-12-20 11:55:35 -05:00
655e84072d Merge pull request #4937 from sbwalker/dev
use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd
2024-12-20 11:46:52 -05:00
ab5409d5b6 use CompressionEnabled switch to disable static asset compression during publish - eliminates need to cleanup files manually in release.cmd 2024-12-20 11:46:30 -05:00
5a5da6486c Merge pull request #4933 from sbwalker/dev
remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled)
2024-12-19 19:56:36 -05:00
7e99252429 remove *.br, *.gz files from wwwroot content folders in published release (as MapStaticAssets is not enabled) 2024-12-19 19:56:13 -05:00
10e0dcef8b Merge pull request #4931 from sbwalker/dev
ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository
2024-12-19 14:45:34 -05:00
80c8433aad ensure Pages collection is always returned in the same order by moving GetPagesHierarchy method to the repository. 2024-12-19 14:45:12 -05:00
5b0ae372f8 Merge pull request #4930 from sbwalker/dev
add support for data-reload=false attribute in Interactive rendering
2024-12-19 13:04:59 -05:00
b5a1b529ab add support for data-reload=false attribute in Interactive rendering 2024-12-19 13:04:43 -05:00
af821dcd9a Merge pull request #4914 from leigh-pointer/userSettings
User Profile Update: Utilizing User Settings Object
2024-12-18 15:27:34 -05:00
10d3c81520 Merge pull request #4918 from leigh-pointer/ProjectNameTemplates
Implement Dynamic ProjectName Parameter Across Build Process
2024-12-18 15:27:24 -05:00
e3811b453a Merge pull request #4927 from sbwalker/dev
Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations.
2024-12-18 15:16:16 -05:00
ca0fb05baa Improvements to add support for script type and data-* attributes. Also added Script and Stylesheet classes to simplify Resource declarations. 2024-12-18 15:15:54 -05:00
2b4b01bf6e Merge pull request #4926 from sbwalker/dev
always render page-script elements in body
2024-12-18 11:56:18 -05:00
3a1244bddc always render page-script elements in body 2024-12-18 11:56:03 -05:00
b8fd922b19 Merge pull request #4925 from sbwalker/dev
improve reload script to replicate all attributes
2024-12-18 10:32:12 -05:00
03f856025e improve reload script to replicate all attributes 2024-12-18 10:31:59 -05:00
45f04d24c3 Merge pull request #4924 from sbwalker/dev
refactor Static Blazor script processing
2024-12-18 10:09:20 -05:00
2435d610c7 refactor Static Blazor script processing 2024-12-18 10:09:02 -05:00
06572bcd14 Merge pull request #4922 from sbwalker/dev
page-script optimization
2024-12-17 14:57:17 -05:00
3fab79afc0 page-script optimization 2024-12-17 14:57:01 -05:00
2b7dd3fed5 Merge pull request #4921 from sbwalker/dev
render page-script elements at end of head content
2024-12-17 12:31:40 -05:00
d65efed032 render page-script elements at end of head content 2024-12-17 12:31:26 -05:00
c6896ea936 Merge pull request #4920 from sbwalker/dev
fix page-script to support type attribute on inline scripts
2024-12-17 09:13:42 -05:00
b7a41bddec fix page-script to support type attribute on inline scripts 2024-12-17 09:13:26 -05:00
6fd80c3737 Merge pull request #4919 from sbwalker/dev
page-script improvements
2024-12-17 08:34:55 -05:00
0aa690b3b1 page-script improvements 2024-12-17 08:34:39 -05:00
6d5bcfc6ed Implement Dynamic ProjectName Parameter Across Build Process
This pull request introduces a dynamic ProjectName parameter across our build process, enhancing flexibility and reducing hardcoding in our module and theme development workflow. The changes affect the project file, NuSpec file, command scripts.
2024-12-17 10:35:14 +01:00
b60de69fa5 Merge pull request #4917 from sbwalker/dev
improvements to page-script
2024-12-16 12:44:23 -05:00
d991b57d08 improvements to page-script 2024-12-16 12:44:07 -05:00
1133d7fcba User Profile Update: Utilizing User Settings Object
This pull request updates the User Profile implementation to utilize the existing User Settings object on the user object. While the current implementation is functional, this change offers several benefits:
Improved code consistency
Better guidance for developers accessing User Settings
Alignment with the framework's best practices
2024-12-14 11:09:54 +01:00
6fbf0383bb Merge pull request #4911 from leigh-pointer/FixGlow
This is a cosmetic fix to the Oqtane Glow image
2024-12-13 16:46:49 -05:00
0296230219 Merge pull request #4913 from sbwalker/dev
page-script enhancements
2024-12-13 16:46:23 -05:00
dedfbba27a page-script enhancements 2024-12-13 16:46:08 -05:00
dc5441da07 This is a cosmetic fix to the Oqtane Glow image
The glow on the original is cropped at the left by a few pixcels.  This update fixes that and sizes the image to a 600 x 150
2024-12-13 11:45:13 +01:00
585648b7f3 Merge pull request #4907 from leigh-pointer/ProfileMaxLength
FIX for #4906 Profile Field does not allow max length
2024-12-10 09:12:52 -05:00
cd0ee1c26d FIX for #4906 Profile Field does not allow max length
This update applies the maxlength attribute fix to all input and textarea elements. The maxlength attribute will only be added if p.MaxLength is greater than 0, allowing unlimited characters when it's 0 or less.
2024-12-10 09:25:01 +01:00
d7a7be5af4 Merge pull request #4904 from sbwalker/dev
add sync events for user login/logout
2024-12-09 10:55:55 -05:00
13e4267c11 add sync events for user login/logout 2024-12-09 10:55:40 -05:00
15bc47e3e8 Merge pull request #4898 from leigh-pointer/TemplateParam
Implement Dynamic TargetFramework in Debug Scripts #4897
2024-12-09 07:36:00 -05:00
1a4380dcd7 Variable update 2024-12-07 15:02:27 +01:00
5ace34b5cd Add the $TargetFramework macro to the Release Builds
Add the $TargetFramework macro to the Release Builds
2024-12-06 13:37:37 +01:00
f010c0f1fa Implement Dynamic TargetFramework in Debug Scripts #4897
This PR updates our debug scripts (both .cmd and .sh) to dynamically use the current TargetFramework passed from the build process. This change improves flexibility and future-proofs our build process for different .NET versions.
2024-12-05 12:43:38 +01:00
2c721ad5dd Merge pull request #4895 from tvatavuk/enh-4883-control-panel
Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button
2024-12-04 08:25:17 -05:00
8a7c2ce2c2 Merge pull request #4894 from W6HBR/dev
Fix #4885 - Pass userid as int to GetUser for JWT authentication
2024-12-04 08:24:27 -05:00
b2a7b813de Remove redundant IServiceProvider injection 2024-12-04 09:39:02 +01:00
e85cf04b99 Fix #4883: Add ShowEditMode parameter to ControlPanel to allow hiding the Edit Mode toggle button 2024-12-04 09:37:21 +01:00
ab6fa48172 Fix #4885 - Pass userid as int to GetUser for JWT authentication 2024-12-03 10:34:44 -08:00
c81905882f Merge pull request #4881 from sbwalker/dev
improve security of UserRole API
2024-11-27 14:59:41 -05:00
f0d31c1114 improve security of UserRole API 2024-11-27 14:59:25 -05:00
497b255216 Merge pull request #4880 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:16:04 -05:00
d96286d771 User Settings should only be accessible to individual users or administrators 2024-11-27 13:15:43 -05:00
2441647d75 Merge pull request #4879 from sbwalker/dev
update EFCore.NamingConventions to .NET 9 package version
2024-11-27 13:06:53 -05:00
77b780d631 update EFCore.NamingConventions to .NET 9 package version 2024-11-27 13:06:37 -05:00
cdd03bf3d4 Merge pull request #4878 from sbwalker/dev
User Settings should only be accessible to individual users or administrators
2024-11-27 13:04:27 -05:00
e786c35f7d User Settings should only be accessible to individual users or administrators 2024-11-27 13:04:06 -05:00
e83399acb1 Merge pull request #4876 from sbwalker/dev
prevent notifications from being accessed by other users
2024-11-26 14:30:55 -05:00
ffea9e3210 prevent notifications from being accessed by other users 2024-11-26 14:30:41 -05:00
f71a3a1ce3 Merge pull request #4875 from oqtane/revert-4828-TabChange
Revert "Fix for Tabpanel is not updating the UI. #4778"
2024-11-26 13:36:18 -05:00
a5ccc23604 Revert "Fix for Tabpanel is not updating the UI. #4778" 2024-11-26 13:36:05 -05:00
1ed4c8a094 Merge pull request #4874 from oqtane/revert-4871-HTMLTabError
Revert "Rework for Tabstrip regression issue"
2024-11-26 13:35:37 -05:00
4a74549c1b Revert "Rework for Tabstrip regression issue" 2024-11-26 13:35:21 -05:00
a499cfb98f Merge pull request #4871 from leigh-pointer/HTMLTabError
Rework for Tabstrip regression issue
2024-11-26 11:27:52 -05:00
01038c8296 Merge pull request #4873 from sbwalker/dev
include SECURITY.md
2024-11-26 11:25:35 -05:00
7407f79b3d include SECURITY.md 2024-11-26 11:25:23 -05:00
a845dd1976 Rework for Tabstrip regression issue
Fix for Tabpanel is not updating the UI. #4778 #4828
2024-11-26 15:39:51 +01:00
9d7549da70 Reverted TabStrip and Panel 2024-11-26 10:33:18 +01:00
f5cc61384f Merge pull request #4870 from sbwalker/dev
reference Quill CSS theme using BaseUrl so that it works in .NET MAUI as well as web
2024-11-25 14:20:15 -05:00
844778d36a reference Quill CSS theme using BaseUrl so that it works in .NET MAUI as well as web 2024-11-25 14:19:58 -05:00
871b0a274e Merge pull request #4869 from sbwalker/dev
improve message grammar
2024-11-25 13:48:41 -05:00
737740a3ca improve message grammar 2024-11-25 13:48:28 -05:00
ae8d600600 Update README.md 2024-11-25 09:11:12 -05:00
2f1691bfb0 Update README.md 2024-11-25 09:10:20 -05:00
a3d25f91c8 Update README.md 2024-11-25 09:09:52 -05:00
ff84b50817 Update README.md 2024-11-25 09:09:13 -05:00
0be8242284 Merge pull request #4862 from leigh-pointer/SettingsSetTabOnSave
Updated the Module Settings to use the new ActiveTab parameter
2024-11-25 08:39:45 -05:00
e25a6259ea Merge pull request #4861 from zyhfish/task/fix-4841
Fix #4841: force 2FA validation when it's required in site level.
2024-11-25 08:39:26 -05:00
1578f82efb Updated the Module Settings to use the new ActiveTab parameter 2024-11-23 11:42:22 +01:00
Ben
b5f75f0c5e Fix #4841: force 2FA validation when it's required in site level. 2024-11-23 13:04:27 +08:00
601caab3b6 Merge pull request #4860 from sbwalker/dev
fix #4760 - display update confirmation message in Site Settings
2024-11-22 16:34:46 -05:00
6d3092f440 fix #4760 - display update confirmation message in Site Settings 2024-11-22 16:34:35 -05:00
ef27937c7a Merge pull request #4785 from thabaum/refactored-heading-ifelse
Fixes #4784: Refactor TabPanel Heading Assignment Logic
2024-11-22 15:34:51 -05:00
f4a7b79c4f Merge pull request #4828 from leigh-pointer/TabChange
Fix for Tabpanel is not updating the UI. #4778
2024-11-22 15:30:51 -05:00
2531776a48 Merge pull request #4859 from sbwalker/dev
prepare for 6.0.1
2024-11-22 12:29:56 -05:00
ced80419aa prepare for 6.0.1 2024-11-22 12:29:44 -05:00
ad2816f4e8 Merge pull request #4858 from sbwalker/dev
fix #4848 - remove assemblies from /bin which have been moved to /bin/refs in .NET 9
2024-11-22 12:14:04 -05:00
3528b8c674 fix #4848 - remove assemblies from /bin which have been moved to /bin/refs in .NET 9 2024-11-22 12:13:45 -05:00
80c83c626d Merge pull request #4853 from leigh-pointer/PagerAlignment
Fix for #4852 align the Page numbers container
2024-11-22 11:56:12 -05:00
6a355f2aea Merge pull request #4857 from sbwalker/dev
fix #4855 - dropping required column causes issue on SQLite
2024-11-22 11:55:56 -05:00
7d94e4a53a fix #4855 - dropping required column causes issue on SQLite 2024-11-22 11:55:43 -05:00
823c04742e Merge pull request #4854 from sbwalker/dev
resolve .NET version issue in nuspec files
2024-11-21 10:54:06 -05:00
043fb1abd1 resolve .NET version issue in nuspec files 2024-11-21 10:53:52 -05:00
f01e85b690 Fix for #4852 align the Page numbers container 2024-11-21 16:05:32 +01:00
7eb1298847 Merge pull request #4847 from sbwalker/dev
do not include Oqtane.Server.staticwebassets.endpoints.json in release packages
2024-11-19 13:50:05 -05:00
a5480c9a96 do not include Oqtane.Server.staticwebassets.endpoints.json in release packages 2024-11-19 13:49:45 -05:00
f948600e86 Merge pull request #4845 from sbwalker/dev
fix 2 factor authentication email
2024-11-18 15:04:01 -05:00
420182b9bf fix 2 factor authentication email 2024-11-18 15:03:48 -05:00
8c430ce1a6 Merge pull request #4837 from Trifoia/4803-Add-a-CONTRIBUTING.md
4803 add a contributing.md
2024-11-15 15:02:28 -05:00
d3717dbe19 Remove DefaultDBType value and InstallationId from appsettings.json
The commit removes the value for DefaultDBType and InstallationId from the appsettings.json file.
2024-11-15 10:31:49 -08:00
caa83d769f Create CONTRIBUTING.md 2024-11-15 10:27:53 -08:00
365f87828f Merge remote-tracking branch 'oqtane/dev' into dev 2024-11-15 09:52:50 -08:00
f780887866 Update README.md 2024-11-14 15:46:26 -05:00
db6dd5abee Fix for TabPanel is not updating the UI. #4778
Modified that TabStrip and TabPane, now when the ActiveTab is changed the TabPanel is selected
2024-11-14 10:35:15 +01:00
702eb9e466 Revert "Fix for Page Management tab panel is not updating the UI. #4778"
This reverts commit 3c99006226.
2024-11-14 10:33:10 +01:00
3c99006226 Fix for Page Management tab panel is not updating the UI. #4778 2024-11-14 10:31:53 +01:00
784f3771b3 Remove Blank Page Specific Error Message 2024-10-24 13:22:08 -07:00
80316824f7 Remove Blank Page Specific Error Message 2024-10-24 13:21:40 -07:00
cbaebb7b7c Remove Blank Page Specific Error Message 2024-10-24 13:21:15 -07:00
97d3764b6e Remove Blank Page Specific Error Message 2024-10-24 13:20:52 -07:00
d77e898929 Refactor TabPanel Heading Assignment Logic
- Simplified the logic for setting the Heading property in the TabPanel component.
- Replaced the if-else statement with a ternary operator for improved readability and maintainability.
- Ensured that the functionality remains unchanged and verified correct assignment of headings.
2024-10-24 12:43:20 -07:00
4c937be884 Clarify Page Name Required Message 2024-10-24 12:38:51 -07:00
c25cce4398 Clarify Page Name Required Message 2024-10-24 12:38:16 -07:00
58c422285a Adds Message.Required.PageName 2024-10-24 12:28:17 -07:00
f2f22f35e8 Adds Message.Required.PageName 2024-10-24 12:26:36 -07:00
15867a7807 Adds await ScrollToPageTop(); to error messages + Page Name Error Message 2024-10-24 12:23:29 -07:00
e2c404d8bb Adds await ScrollToPageTop(); to error messages + Page Name Error Message
- Adds await ScrollToPageTop(); to error messages
- Adds Blank Page Name Error Message
2024-10-24 12:18:30 -07:00
bf175984f3 Merge remote-tracking branch 'origin/dev' into dev 2024-09-11 18:23:41 -07:00
4b17847ea5 Merge remote-tracking branch 'oqtane/dev' into dev 2024-07-05 21:53:14 -07:00
2addcc3ab5 Merge branch 'dev' of https://github.com/Trifoia/oqtane.framework into dev 2024-03-28 13:47:43 -07:00
370b39a139 Merge branch 'release/v5.0.1' into dev 2024-01-27 21:18:58 -08:00
7b7e64576f Update appsettings.json 2024-01-27 20:30:20 -08:00
286928d59e Merge tag 'v5.0.1' into dev 2023-12-29 11:54:39 -08:00
cc65555c3d minor fix to routing 2023-12-05 12:17:02 -08:00
63e3923349 Merge remote-tracking branch 'oqtane/dev' into dev 2023-11-17 12:55:40 -08:00
82 changed files with 990 additions and 517 deletions

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
# Contributing to Oqtane
## How to Contribute
We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution.
## Use GitHub-flow process
- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding.
- Fork the repository
- Create a new branch and update your comment on the issue with a llink to the branch
- Make your changes and commit them
- Push to the branch
- Create a pull request
## Reporting Bugs
- Check if the issue has already been reported.
- Open a new issue if it hasnt been reported.
## Requesting Features
- Use the feature request template in the Issues tab.
Thank you for contributing!

View File

@ -12,7 +12,7 @@
@if (_initialized) @if (_initialized)
{ {
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip> <TabStrip ActiveTab="@_activetab">
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
@if (_containers != null) @if (_containers != null)
{ {
@ -162,6 +162,7 @@
private DateTime? _effectivedate = null; private DateTime? _effectivedate = null;
private DateTime? _expirydate = null; private DateTime? _expirydate = null;
private List<Page> _pages; private List<Page> _pages;
private string _activetab = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -241,6 +242,7 @@
private async Task SaveModule() private async Task SaveModule()
{ {
validated = true; validated = true;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
if (await interop.FormValid(form)) if (await interop.FormValid(form))
@ -300,11 +302,13 @@
} }
else else
{ {
_activetab = "Settings";
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
} }
} }
else else
{ {
_activetab = "Settings";
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -316,10 +316,11 @@
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
} }
} }
private void ThemeChanged(ChangeEventArgs e) private async Task ThemeChanged(ChangeEventArgs e)
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -330,6 +331,7 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{ {
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning); AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
} }
} }
@ -345,6 +347,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate)) if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{ {
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype)) if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
@ -395,12 +398,14 @@
if (_pages.Any(item => item.Path == page.Path)) if (_pages.Any(item => item.Path == page.Path))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower())) if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
@ -468,6 +473,7 @@
else else
{ {
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
} }
} }
@ -475,11 +481,13 @@
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
} }
} }
else else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
} }
} }

View File

@ -479,10 +479,11 @@
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
await ScrollToPageTop();
} }
} }
private void ThemeChanged(ChangeEventArgs e) private async Task ThemeChanged(ChangeEventArgs e)
{ {
_themetype = (string)e.Value; _themetype = (string)e.Value;
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
@ -494,6 +495,7 @@
if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName) if (ThemeService.GetTheme(PageState.Site.Themes, _themetype)?.ThemeName != ThemeService.GetTheme(PageState.Site.Themes, PageState.Site.DefaultThemeType)?.ThemeName)
{ {
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning); AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
await ScrollToPageTop();
} }
} }
@ -531,6 +533,7 @@
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate)) if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
{ {
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
@ -581,12 +584,14 @@
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId)) if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
{ {
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower())) if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
await ScrollToPageTop();
return; return;
} }
@ -671,17 +676,20 @@
else else
{ {
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
await ScrollToPageTop();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message); await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
await ScrollToPageTop();
} }
} }
else else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
await ScrollToPageTop();
} }
} }

View File

@ -481,6 +481,11 @@
{ {
try try
{ {
if (PageState.QueryString.ContainsKey("updated"))
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
}
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
@ -736,7 +741,7 @@
await logger.LogInformation("Site Settings Saved {Site}", site); await logger.LogInformation("Site Settings Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl(), true); // reload NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "updated=true"), true); // reload
} }
} }
else else

View File

@ -149,22 +149,26 @@
{ {
@if (p.IsRequired) @if (p.IsRequired)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
else else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
} }
else else
{ {
@if (p.IsRequired) @if (p.IsRequired)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
else else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
} }
} }
@ -174,22 +178,26 @@
{ {
@if (p.IsRequired) @if (p.IsRequired)
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
else else
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
} }
else else
{ {
@if (p.IsRequired) @if (p.IsRequired)
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
else else
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
} }
} }
@ -230,13 +238,13 @@
@if (context.IsRead) @if (context.IsRead)
{ {
<td>@context.FromDisplayName</td> <td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
<td>@context.Subject</td> <td>@context.Subject</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td> <td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
} }
else else
{ {
<td><b>@context.FromDisplayName</b></td> <td><b>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</b></td>
<td><b>@context.Subject</b></td> <td><b>@context.Subject</b></td>
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td> <td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
} }
@ -363,7 +371,7 @@
private File photo = null; private File photo = null;
private string _ImageFiles = string.Empty; private string _ImageFiles = string.Empty;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> userSettings;
private string category = string.Empty; private string category = string.Empty;
private string filter = "to"; private string filter = "to";
@ -411,9 +419,9 @@
photo = null; photo = null;
} }
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); userSettings = PageState.User.Settings;
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); _ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles);
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
await LoadNotificationsAsync(); await LoadNotificationsAsync();
@ -440,7 +448,7 @@
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
@ -483,7 +491,7 @@
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved"); await logger.LogInformation("User Profile Saved");
if (!string.IsNullOrEmpty(PageState.ReturnUrl)) if (!string.IsNullOrEmpty(PageState.ReturnUrl))
@ -554,7 +562,7 @@
var value = GetProfileValue(profile.Name, string.Empty); var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
@ -586,7 +594,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName) private void ProfileChanged(ChangeEventArgs e, string SettingName)
{ {
var value = (string)e.Value; var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value); userSettings = SettingService.SetSetting(userSettings, SettingName, value);
} }
private async Task Delete(Notification Notification) private async Task Delete(Notification Notification)

View File

@ -128,7 +128,7 @@
createdon = notification.CreatedOn.ToString(); createdon = notification.CreatedOn.ToString();
body = notification.Body; body = notification.Body;
if (title == "From") if (title == "From" && !notification.IsRead)
{ {
notification.IsRead = true; notification.IsRead = true;
notification = await NotificationService.UpdateNotificationAsync(notification); notification = await NotificationService.UpdateNotificationAsync(notification);

View File

@ -81,11 +81,11 @@
{ {
@if (p.Rows == 1) @if (p.Rows == 1)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
else else
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
} }
</div> </div>

View File

@ -110,11 +110,11 @@
{ {
@if (p.Rows == 1) @if (p.Rows == 1)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
} }
else else
{ {
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea> <textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
} }
} }
</div> </div>
@ -148,7 +148,7 @@
private string lastipaddress; private string lastipaddress;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> userSettings;
private string category = string.Empty; private string category = string.Empty;
private string createdby; private string createdby;
@ -181,7 +181,7 @@
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress; lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId); userSettings = user.Settings;
createdby = user.CreatedBy; createdby = user.CreatedBy;
createdon = user.CreatedOn; createdon = user.CreatedOn;
modifiedby = user.ModifiedBy; modifiedby = user.ModifiedBy;
@ -202,7 +202,7 @@
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
{ {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue); string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
if (value.Contains("]")) if (value.Contains("]"))
{ {
value = value.Substring(value.IndexOf("]") + 1); value = value.Substring(value.IndexOf("]") + 1);
@ -232,7 +232,7 @@
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
if (user != null) if (user != null)
{ {
await SettingService.UpdateUserSettingsAsync(settings, user.UserId); await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
await logger.LogInformation("User Saved {User}", user); await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
@ -266,7 +266,7 @@
var value = GetProfileValue(profile.Name, string.Empty); var value = GetProfileValue(profile.Name, string.Empty);
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
{ {
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue);
} }
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
@ -293,7 +293,7 @@
private void ProfileChanged(ChangeEventArgs e, string SettingName) private void ProfileChanged(ChangeEventArgs e, string SettingName)
{ {
var value = (string)e.Value; var value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value); userSettings = SettingService.SetSetting(userSettings, SettingName, value);
} }
private void TogglePassword() private void TogglePassword()

View File

@ -21,7 +21,7 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@ -84,7 +84,7 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@ -200,7 +200,7 @@
{ {
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive) @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@ -248,7 +248,7 @@
} }
else else
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@ -368,6 +368,12 @@
[SupplyParameterFromForm(FormName = "PagerForm")] [SupplyParameterFromForm(FormName = "PagerForm")]
public string _Search { get => ""; set => _search = value; } public string _Search { get => ""; set => _search = value; }
/// <summary>
/// Accepted values are Start or Center or End. The default value is Center
/// </summary>
[Parameter]
public string PaginationAlignment { get; set; } = "center"; // Alignment of the Page Numbering start, center, end
private IEnumerable<TableItem> ItemList { get; set; } private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized() protected override void OnInitialized()

View File

@ -277,7 +277,7 @@
{ {
// include CSS theme // include CSS theme
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.IncludeLink("", "stylesheet", $"css/quill/quill.{_theme}.css", "text/css", "", "", ""); await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
} }
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);

View File

@ -36,14 +36,7 @@ else
Parent.AddTabPanel((TabPanel)this); Parent.AddTabPanel((TabPanel)this);
if (string.IsNullOrEmpty(Heading)) Heading = string.IsNullOrEmpty(Heading) ? Localize(nameof(Name), Name) : Localize(nameof(Heading), Heading);
{
Heading = Localize(nameof(Name), Name);
}
else
{
Heading = Localize(nameof(Heading), Heading);
}
} }
public string DisplayHeading() public string DisplayHeading()

View File

@ -98,17 +98,17 @@ namespace Oqtane.Modules
var inline = 0; var inline = 0;
foreach (Resource resource in resources) foreach (Resource resource in resources)
{ {
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
} }
else else
{ {
inline += 1; inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
} }
} }
} }

View File

@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -12,7 +12,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -435,4 +435,7 @@
<data name="Functionality" xml:space="preserve"> <data name="Functionality" xml:space="preserve">
<value>Functionality</value> <value>Functionality</value>
</data> </data>
<data name="System" xml:space="preserve">
<value>System</value>
</data>
</root> </root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -151,6 +151,6 @@
<value>You Cannot Perform A System Update In A Development Environment</value> <value>You Cannot Perform A System Update In A Development Environment</value>
</data> </data>
<data name="Disclaimer.Text" xml:space="preserve"> <data name="Disclaimer.Text" xml:space="preserve">
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To The Limitations Of That Environment. </value> <value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To Environmental Limitations.</value>
</data> </data>
</root> </root>

View File

@ -37,11 +37,8 @@
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", "anonymous"),
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", new Stylesheet(ThemePath() + "Theme.css"),
CrossOrigin = "anonymous" }, new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body },
}; };
} }

View File

@ -9,7 +9,7 @@
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" /> <LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
} }
@if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) @if (ShowEditMode && (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))))
{ {
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance> <form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" /> <input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
@ -59,9 +59,15 @@
[Parameter] [Parameter]
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
/// <summary>
/// Ability to hide the Edit Mode toggle button
/// </summary>
[Parameter]
public bool ShowEditMode { get; set; } = true;
private PageState _pageState; private PageState _pageState;
private bool _canViewAdminDashboard = false; private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false; // internal state (not the same as ShowEditMode parameter)
protected override void OnParametersSet() protected override void OnParametersSet()
{ {

View File

@ -15,7 +15,6 @@
@inject ILogService LoggingService @inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer @inject IStringLocalizer<ControlPanelInteractive> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IServiceProvider ServiceProvider
<button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage"> <button type="button" class="btn @ButtonClass ms-1" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel" @onclick="ClearMessage">
<span class="oi oi-cog"></span> <span class="oi oi-cog"></span>

View File

@ -17,11 +17,9 @@ namespace Oqtane.Themes.OqtaneTheme
Resources = new List<Resource>() Resources = new List<Resource>()
{ {
// obtained from https://cdnjs.com/libraries // obtained from https://cdnjs.com/libraries
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", new Stylesheet("https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", "anonymous"),
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", new Stylesheet("~/Theme.css"),
CrossOrigin = "anonymous" }, new Script(Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous")
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body }
} }
}; };
} }

View File

@ -62,17 +62,17 @@ namespace Oqtane.Themes
var inline = 0; var inline = 0;
foreach (Resource resource in resources) foreach (Resource resource in resources)
{ {
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
{ {
if (!string.IsNullOrEmpty(resource.Url)) if (!string.IsNullOrEmpty(resource.Url))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes });
} }
else else
{ {
inline += 1; inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
} }
} }
} }

View File

@ -41,7 +41,7 @@
} }
break; break;
case "HeadContent": case "HeadContent":
var content = RemoveScripts(SiteState.Properties.HeadContent) + "\n"; var content = FormatScripts(SiteState.Properties.HeadContent) + "\n";
if (content != _content) if (content != _content)
{ {
_content = content; _content = content;
@ -51,16 +51,30 @@
} }
} }
private string RemoveScripts(string headcontent) private string FormatScripts(string headcontent)
{ {
if (!string.IsNullOrEmpty(headcontent) && RenderMode == RenderModes.Interactive) if (!string.IsNullOrEmpty(headcontent))
{ {
var index = headcontent.IndexOf("<script"); var index = headcontent.IndexOf("<script");
while (index >= 0) while (index >= 0)
{ {
headcontent = headcontent.Remove(index, headcontent.IndexOf("</script>") + 9 - index); var script = headcontent.Substring(index, headcontent.IndexOf("</script>", index) - index + 9);
if (RenderMode == RenderModes.Interactive)
{
// remove scripts when interactive rendering
headcontent = headcontent.Remove(index, script.Length);
index = headcontent.IndexOf("<script"); index = headcontent.IndexOf("<script");
} }
else
{
if (!script.Contains("><") && !script.Contains("data-reload"))
{
// add data-reload attribute to inline script
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
}
index = headcontent.IndexOf("<script", index + 1);
}
}
} }
return headcontent; return headcontent;
} }

View File

@ -117,12 +117,17 @@ namespace Oqtane.UI
} }
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location) public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location)
{
return IncludeScript(id, src, integrity, crossorigin, type, content, location, null);
}
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string type, string content, string location, Dictionary<string, string> dataAttributes)
{ {
try try
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeScript", "Oqtane.Interop.includeScript",
id, src, integrity, crossorigin, type, content, location); id, src, integrity, crossorigin, type, content, location, dataAttributes);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch

View File

@ -11,6 +11,8 @@
RenderFragment DynamicComponent { get; set; } RenderFragment DynamicComponent { get; set; }
private string lastPagePath = "";
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
// handle page redirection // handle page redirection
@ -90,8 +92,16 @@
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (!firstRender) if (!firstRender && PageState.Page.Path != lastPagePath)
{ {
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
{
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
}
if (!string.IsNullOrEmpty(PageState.Site.BodyContent) && PageState.Site.BodyContent.Contains("<script"))
{
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
}
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script")) if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
{ {
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head); await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
@ -100,6 +110,7 @@
{ {
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body); await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
} }
lastPagePath = PageState.Page.Path;
} }
// style sheets // style sheets
@ -176,18 +187,20 @@
if (!string.IsNullOrEmpty(src)) if (!string.IsNullOrEmpty(src))
{ {
src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src; src = (src.Contains("://")) ? src : PageState.Alias.BaseUrl + src;
scripts.Add(new { href = src, bundle = "", integrity = integrity, crossorigin = crossorigin, es6module = (type == "module"), location = location.ToString().ToLower(), dataAttributes = dataAttributes }); scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
} }
else else
{ {
// inline script must have an id attribute if (dataAttributes == null || !dataAttributes.ContainsKey("data-reload") || dataAttributes["data-reload"] != "false")
{
if (id == "") if (id == "")
{ {
count += 1; count += 1;
id = $"page{PageState.Page.PageId}-script{count}"; id = $"page{PageState.Page.PageId}-script{count}";
} }
index = script.IndexOf(">") + 1; var pos = script.IndexOf(">") + 1;
await interop.IncludeScript(id, "", "", "", "", script.Substring(index, script.IndexOf("</script>") - index), location.ToString().ToLower()); await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
}
} }
index = content.IndexOf("<script", index + 1); index = content.IndexOf("<script", index + 1);
} }

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -29,14 +29,13 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;EF1001;AD0001;NU1608</NoWarn> <NoWarn>1701;1702;EF1001;AD0001</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" /> <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.2" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -6,7 +6,7 @@
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> --> <!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -14,7 +14,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -30,7 +30,7 @@
<ApplicationId>com.oqtane.maui</ApplicationId> <ApplicationId>com.oqtane.maui</ApplicationId>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>6.0.0</ApplicationDisplayVersion> <ApplicationDisplayVersion>6.0.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged --> <!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>6.0.0</version> <version>6.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.dll" target="lib\net9.0" /> <file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.dll" target="lib\net9.0" />
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net9.0" /> <file src="..\Oqtane.Client\bin\Release\net9.0\Oqtane.Client.pdb" target="lib\net9.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
<file src="readme.md" target="" /> <file src="readme.md" target="" />
</files> </files>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>6.0.0</version> <version>6.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright> <copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.0.0/Oqtane.Framework.6.0.0.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.0.1/Oqtane.Framework.6.0.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>6.0.0</version> <version>6.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.dll" target="lib\net9.0" /> <file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.dll" target="lib\net9.0" />
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net9.0" /> <file src="..\Oqtane.Server\bin\Release\net9.0\Oqtane.Server.pdb" target="lib\net9.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
<file src="readme.md" target="" /> <file src="readme.md" target="" />
</files> </files>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>6.0.0</version> <version>6.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.dll" target="lib\net9.0" /> <file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.dll" target="lib\net9.0" />
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net9.0" /> <file src="..\Oqtane.Shared\bin\Release\net9.0\Oqtane.Shared.pdb" target="lib\net9.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
<file src="readme.md" target="" /> <file src="readme.md" target="" />
</files> </files>

View File

@ -2,7 +2,7 @@
<package> <package>
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>6.0.0</version> <version>6.0.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,13 +12,13 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
<readme>readme.md</readme> <readme>readme.md</readme>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>
<files> <files>
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net9.0" /> <file src="..\Oqtane.Updater\bin\Release\net9.0\publish\*.*" target="lib\net9.0" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
<file src="readme.md" target="" /> <file src="readme.md" target="" />
</files> </files>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Install.zip" -Force

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Upgrade.zip" -Force

View File

@ -76,6 +76,10 @@
@((MarkupString)_scripts) @((MarkupString)_scripts)
@((MarkupString)_bodyResources) @((MarkupString)_bodyResources)
@if (_renderMode == RenderModes.Static)
{
<page-script src="./js/reload.js"></page-script>
}
} }
else else
{ {
@ -188,9 +192,6 @@
_scripts += CreateScrollPositionScript(); _scripts += CreateScrollPositionScript();
} }
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
// set culture if not specified // set culture if not specified
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName]; string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (cultureCookie == null) if (cultureCookie == null)
@ -510,26 +511,10 @@
"</script>" + Environment.NewLine; "</script>" + Environment.NewLine;
} }
private string ParseScripts(string content)
{
var scripts = "";
// in interactive render mode, parse scripts from content and inject into page
if (_renderMode == RenderModes.Interactive && !string.IsNullOrEmpty(content))
{
var index = content.IndexOf("<script");
while (index >= 0)
{
scripts += content.Substring(index, content.IndexOf("</script>", index) + 9 - index);
index = content.IndexOf("<script", index + 1);
}
}
return scripts;
}
private void AddScript(Resource resource, Alias alias) private void AddScript(Resource resource, Alias alias)
{ {
var script = CreateScript(resource, alias); var script = CreateScript(resource, alias);
if (resource.Location == Shared.ResourceLocation.Head) if (resource.Location == Shared.ResourceLocation.Head && !resource.Reload)
{ {
if (!_headResources.Contains(script)) if (!_headResources.Contains(script))
{ {
@ -546,38 +531,31 @@
} }
private string CreateScript(Resource resource, Alias alias) private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
{ {
if (!resource.Reload) if (!resource.Reload)
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script" +
var dataAttributes = "";
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
{
foreach (var attribute in resource.DataAttributes)
{
dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\"";
}
}
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") + ((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") + ((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") + ((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
" src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm "></script>";
} }
else else
{
// use custom element which can execute script on every page transition
@if (string.IsNullOrEmpty(resource.Integrity) && string.IsNullOrEmpty(resource.CrossOrigin))
{ {
return "<page-script src=\"" + resource.Url + "\"></page-script>"; return "<page-script src=\"" + resource.Url + "\"></page-script>";
} }
else
{
// use modulepreload for external resources
return "<link rel=\"modulepreload\" href=\"" + resource.Url + "\" integrity=\"" + resource.Integrity + "\" crossorigin=\"" + resource.CrossOrigin + "\" />\n" +
"<page-script src=\"" + resource.Url + "\"></page-script>";
}
}
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
}
} }
private void SetLocalizationCookie(string cookieValue) private void SetLocalizationCookie(string cookieValue)

View File

@ -183,7 +183,7 @@ namespace Oqtane.Controllers
{ {
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId))) if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
{ {
if (!User.IsInRole(RoleNames.Admin)) if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null)
{ {
// content must be HTML encoded for non-admins to prevent HTML injection // content must be HTML encoded for non-admins to prevent HTML injection
notification.Subject = WebUtility.HtmlEncode(notification.Subject); notification.Subject = WebUtility.HtmlEncode(notification.Subject);
@ -223,7 +223,7 @@ namespace Oqtane.Controllers
private bool IsAuthorized(int? userid) private bool IsAuthorized(int? userid)
{ {
bool authorized = true; bool authorized = false;
if (userid != null) if (userid != null)
{ {
authorized = (_userPermissions.GetUser(User).UserId == userid); authorized = (_userPermissions.GetUser(User).UserId == userid);

View File

@ -9,7 +9,7 @@ using System.Net;
using Oqtane.Enums; using Oqtane.Enums;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Oqtane.Repository; using Oqtane.Repository;
using System.IO; using System;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {

View File

@ -269,11 +269,7 @@ namespace Oqtane.Controllers
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
break; break;
case EntityNames.User: case EntityNames.User:
authorized = true;
if (permissionName == PermissionNames.Edit)
{
authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
}
break; break;
case EntityNames.Visitor: case EntityNames.Visitor:
authorized = User.IsInRole(RoleNames.Admin); authorized = User.IsInRole(RoleNames.Admin);
@ -319,7 +315,7 @@ namespace Oqtane.Controllers
filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit); filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit);
break; break;
case EntityNames.User: case EntityNames.User:
filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId; filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
break; break;
case EntityNames.Visitor: case EntityNames.Visitor:
if (!User.IsInRole(RoleNames.Admin)) if (!User.IsInRole(RoleNames.Admin))

View File

@ -28,9 +28,10 @@ namespace Oqtane.Controllers
private readonly IJwtManager _jwtManager; private readonly IJwtManager _jwtManager;
private readonly IFileRepository _files; private readonly IFileRepository _files;
private readonly ISettingRepository _settings; private readonly ISettingRepository _settings;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ILogManager logger) public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ISyncManager syncManager, ILogManager logger)
{ {
_users = users; _users = users;
_tenantManager = tenantManager; _tenantManager = tenantManager;
@ -40,6 +41,7 @@ namespace Oqtane.Controllers
_jwtManager = jwtManager; _jwtManager = jwtManager;
_files = files; _files = files;
_settings = settings; _settings = settings;
_syncManager = syncManager;
_logger = logger; _logger = logger;
} }
@ -136,7 +138,7 @@ namespace Oqtane.Controllers
filtered.PhotoFileId = user.PhotoFileId; filtered.PhotoFileId = user.PhotoFileId;
filtered.LastLoginOn = user.LastLoginOn; filtered.LastLoginOn = user.LastLoginOn;
filtered.LastIPAddress = user.LastIPAddress; filtered.LastIPAddress = user.LastIPAddress;
filtered.TwoFactorRequired = false; filtered.TwoFactorRequired = user.TwoFactorRequired;
filtered.Roles = user.Roles; filtered.Roles = user.Roles;
filtered.CreatedBy = user.CreatedBy; filtered.CreatedBy = user.CreatedBy;
filtered.CreatedOn = user.CreatedOn; filtered.CreatedOn = user.CreatedOn;
@ -145,20 +147,7 @@ namespace Oqtane.Controllers
filtered.DeletedBy = user.DeletedBy; filtered.DeletedBy = user.DeletedBy;
filtered.DeletedOn = user.DeletedOn; filtered.DeletedOn = user.DeletedOn;
filtered.IsDeleted = user.IsDeleted; filtered.IsDeleted = user.IsDeleted;
} filtered.Settings = user.Settings; // include all settings
// if authenticated user is accessing their own user account
if (_userPermissions.GetUser(User).UserId == user.UserId)
{
// include all settings
filtered.Settings = user.Settings;
}
else
{
// include only public settings
filtered.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
.Where(item => !item.IsPrivate)
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
} }
} }
@ -266,6 +255,7 @@ namespace Oqtane.Controllers
if (_userPermissions.GetUser(User).UserId == user.UserId) if (_userPermissions.GetUser(User).UserId == user.UserId)
{ {
await HttpContext.SignOutAsync(Constants.AuthenticationScheme); await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : ""); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
} }
} }
@ -279,6 +269,7 @@ namespace Oqtane.Controllers
{ {
await _userManager.LogoutUserEverywhere(user); await _userManager.LogoutUserEverywhere(user);
await HttpContext.SignOutAsync(Constants.AuthenticationScheme); await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, "Logout");
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : ""); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : "");
} }
} }

View File

@ -10,7 +10,6 @@ using System.Linq;
using System.Net; using System.Net;
using Oqtane.Security; using Oqtane.Security;
using System; using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -40,12 +39,14 @@ namespace Oqtane.Controllers
public IEnumerable<UserRole> Get(string siteid, string userid = null, string rolename = null) public IEnumerable<UserRole> Get(string siteid, string userid = null, string rolename = null)
{ {
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null || rolename != null)) int UserId = -1;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId && (userid != null && int.TryParse(userid, out UserId) || rolename != null))
{
if (IsAuthorized(UserId, rolename))
{ {
var userroles = _userRoles.GetUserRoles(SiteId).ToList(); var userroles = _userRoles.GetUserRoles(SiteId).ToList();
if (userid != null) if (UserId != -1)
{ {
int UserId = int.TryParse(userid, out UserId) ? UserId : -1;
userroles = userroles.Where(item => item.UserId == UserId).ToList(); userroles = userroles.Where(item => item.UserId == UserId).ToList();
} }
if (rolename != null) if (rolename != null)
@ -58,6 +59,14 @@ namespace Oqtane.Controllers
userroles[i] = Filter(userroles[i], user.UserId); userroles[i] = Filter(userroles[i], user.UserId);
} }
return userroles.OrderBy(u => u.User.DisplayName); return userroles.OrderBy(u => u.User.DisplayName);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UserRole Get Attempt For Site {SiteId} User {UserId} Role {RoleName}", siteid, userid, rolename);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
} }
else else
{ {
@ -73,7 +82,7 @@ namespace Oqtane.Controllers
public UserRole Get(int id) public UserRole Get(int id)
{ {
var userrole = _userRoles.GetUserRole(id); var userrole = _userRoles.GetUserRole(id);
if (userrole != null && SiteValid(userrole.Role.SiteId)) if (userrole != null && SiteValid(userrole.Role.SiteId) && IsAuthorized(userrole.UserId, userrole.Role.Name))
{ {
return Filter(userrole, _userPermissions.GetUser().UserId); return Filter(userrole, _userPermissions.GetUser().UserId);
} }
@ -92,33 +101,57 @@ namespace Oqtane.Controllers
} }
} }
private bool IsAuthorized(int userId, string roleName)
{
bool authorized = true;
if (userId != -1)
{
authorized = _userPermissions.GetUser(User).UserId == userId;
}
if (authorized && !string.IsNullOrEmpty(roleName))
{
authorized = User.IsInRole(roleName);
}
return authorized;
}
private UserRole Filter(UserRole userrole, int userid) private UserRole Filter(UserRole userrole, int userid)
{ {
// clone object to avoid mutating cache
UserRole filtered = null;
if (userrole != null) if (userrole != null)
{ {
userrole.User.Password = ""; filtered = new UserRole();
userrole.User.IsAuthenticated = false;
userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null;
if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId) // public properties
filtered.UserRoleId = userrole.UserRoleId;
filtered.UserId = userrole.UserId;
filtered.RoleId = userrole.RoleId;
filtered.User = new User();
filtered.User.SiteId = userrole.User.SiteId;
filtered.User.UserId = userrole.User.UserId;
filtered.User.Username = userrole.User.Username;
filtered.User.DisplayName = userrole.User.DisplayName;
filtered.Role = new Role();
filtered.Role.SiteId = userrole.Role.SiteId;
filtered.Role.RoleId = userrole.Role.RoleId;
filtered.Role.Name = userrole.Role.Name;
// include private properties if administrator
if (_userPermissions.IsAuthorized(User, filtered.User.SiteId, EntityNames.UserRole, -1, PermissionNames.Write, RoleNames.Admin))
{ {
userrole.User.Email = ""; filtered.User.Email = userrole.User.Email;
userrole.User.PhotoFileId = null; filtered.User.PhotoFileId = userrole.User.PhotoFileId;
userrole.User.LastLoginOn = DateTime.MinValue; filtered.User.LastLoginOn = userrole.User.LastLoginOn;
userrole.User.LastIPAddress = ""; filtered.User.LastIPAddress = userrole.User.LastIPAddress;
userrole.User.Roles = ""; filtered.User.CreatedOn = userrole.User.CreatedOn;
userrole.User.CreatedBy = "";
userrole.User.CreatedOn = DateTime.MinValue;
userrole.User.ModifiedBy = "";
userrole.User.ModifiedOn = DateTime.MinValue;
userrole.User.DeletedBy = "";
userrole.User.DeletedOn = DateTime.MinValue;
userrole.User.IsDeleted = false;
userrole.User.TwoFactorRequired = false;
} }
} }
return userrole;
return filtered;
} }
// POST api/<controller> // POST api/<controller>

View File

@ -645,6 +645,9 @@ namespace Oqtane.Extensions
} }
} }
var _syncManager = httpContext.RequestServices.GetRequiredService<ISyncManager>();
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName); _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
} }
} }

View File

@ -59,7 +59,7 @@ namespace Oqtane.Infrastructure
if (userid != null && username != null) if (userid != null && username != null)
{ {
var _users = context.RequestServices.GetService(typeof(IUserManager)) as IUserManager; var _users = context.RequestServices.GetService(typeof(IUserManager)) as IUserManager;
var user = _users.GetUser(userid, alias.SiteId); // cached var user = _users.GetUser(int.Parse(userid), alias.SiteId); // cached
if (user != null && !user.IsDeleted) if (user != null && !user.IsDeleted)
{ {
var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user); var claimsidentity = UserSecurity.CreateClaimsIdentity(alias, user);

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Repository; using Oqtane.Repository;
using Oqtane.Shared; using Oqtane.Shared;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -17,12 +17,14 @@ namespace Oqtane.Infrastructure
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly IWebHostEnvironment _environment; private readonly IWebHostEnvironment _environment;
private readonly IConfigManager _configManager; private readonly IConfigManager _configManager;
private readonly ILogger<UpgradeManager> _filelogger;
public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager) public UpgradeManager(IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IConfigManager configManager, ILogger<UpgradeManager> filelogger)
{ {
_serviceScopeFactory = serviceScopeFactory; _serviceScopeFactory = serviceScopeFactory;
_environment = environment; _environment = environment;
_configManager = configManager; _configManager = configManager;
_filelogger = filelogger;
} }
public void Upgrade(Tenant tenant, string version) public void Upgrade(Tenant tenant, string version)
@ -69,6 +71,9 @@ namespace Oqtane.Infrastructure
case "5.2.1": case "5.2.1":
Upgrade_5_2_1(tenant, scope); Upgrade_5_2_1(tenant, scope);
break; break;
case "6.0.1":
Upgrade_6_0_1(tenant, scope);
break;
} }
} }
} }
@ -88,7 +93,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex) catch (Exception ex)
{ {
// error deleting directory // error deleting directory
Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"));
} }
} }
} }
@ -106,7 +111,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex) catch (Exception ex)
{ {
// error populating guid // error populating guid
Debug.WriteLine($"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"));
} }
} }
@ -274,7 +279,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.0 Upgrade Logic - {ex}"));
} }
} }
@ -310,7 +315,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}"));
} }
} }
@ -354,7 +359,7 @@ namespace Oqtane.Infrastructure
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}"));
} }
} }
@ -371,7 +376,7 @@ namespace Oqtane.Infrastructure
try try
{ {
// delete legacy Views assemblies which will cause startup errors due to missing HostModel // delete legacy Views assemblies which will cause startup errors due to missing HostModel
// note that the following files will be deleted however the framework has already started up so a restart will be required // note that the following files will be deleted however the framework has already started up so another restart will be required
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var filepath = Path.Combine(binFolder, "Oqtane.Server.Views.dll"); var filepath = Path.Combine(binFolder, "Oqtane.Server.Views.dll");
if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath); if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath);
@ -381,7 +386,7 @@ namespace Oqtane.Infrastructure
catch (Exception ex) catch (Exception ex)
{ {
// error deleting file // error deleting file
Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}"); _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}"));
} }
} }
} }
@ -441,6 +446,54 @@ namespace Oqtane.Infrastructure
AddPagesToSites(scope, tenant, pageTemplates); AddPagesToSites(scope, tenant, pageTemplates);
} }
private void Upgrade_6_0_1(Tenant tenant, IServiceScope scope)
{
// assemblies which have been relocated to the bin/refs folder in .NET 9
string[] assemblies = {
"Microsoft.AspNetCore.Authorization.dll",
"Microsoft.AspNetCore.Components.Authorization.dll",
"Microsoft.AspNetCore.Components.dll",
"Microsoft.AspNetCore.Components.Forms.dll",
"Microsoft.AspNetCore.Components.Web.dll",
"Microsoft.AspNetCore.Cryptography.Internal.dll",
"Microsoft.AspNetCore.Cryptography.KeyDerivation.dll",
"Microsoft.AspNetCore.Metadata.dll",
"Microsoft.Extensions.Caching.Memory.dll",
"Microsoft.Extensions.Configuration.Binder.dll",
"Microsoft.Extensions.Configuration.FileExtensions.dll",
"Microsoft.Extensions.Configuration.Json.dll",
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"Microsoft.Extensions.DependencyInjection.dll",
"Microsoft.Extensions.Diagnostics.Abstractions.dll",
"Microsoft.Extensions.Diagnostics.dll",
"Microsoft.Extensions.Http.dll",
"Microsoft.Extensions.Identity.Core.dll",
"Microsoft.Extensions.Identity.Stores.dll",
"Microsoft.Extensions.Localization.Abstractions.dll",
"Microsoft.Extensions.Localization.dll",
"Microsoft.Extensions.Logging.Abstractions.dll",
"Microsoft.Extensions.Logging.dll",
"Microsoft.Extensions.Options.dll",
"Microsoft.JSInterop.dll",
"System.Text.Json.dll"
};
foreach (var assembly in assemblies)
{
try
{
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var filepath = Path.Combine(binFolder, assembly);
if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath);
}
catch (Exception ex)
{
// error deleting asesmbly
_filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: 6.0.1 Upgrade Error Removing {assembly} - {ex}"));
}
}
}
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates) private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
{ {
var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>(); var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>();

View File

@ -339,20 +339,20 @@ namespace Oqtane.Managers
user = _users.GetUser(user.Username); user = _users.GetUser(user.Username);
if (!user.IsDeleted) if (!user.IsDeleted)
{ {
if (user.TwoFactorRequired) var alias = _tenantManager.GetAlias();
var twoFactorSetting = _settings.GetSetting(EntityNames.Site, alias.SiteId, "LoginOptions:TwoFactor")?.SettingValue ?? "false";
var twoFactorRequired = twoFactorSetting == "required" || user.TwoFactorRequired;
if (twoFactorRequired)
{ {
var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email"); var token = await _identityUserManager.GenerateTwoFactorTokenAsync(identityuser, "Email");
user.TwoFactorCode = token; user.TwoFactorCode = token;
user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10); user.TwoFactorExpiry = DateTime.UtcNow.AddMinutes(10);
_users.UpdateUser(user); _users.UpdateUser(user);
var alias = _tenantManager.GetAlias();
string url = alias.Protocol + alias.Name;
string siteName = _sites.GetSite(alias.SiteId).Name; string siteName = _sites.GetSite(alias.SiteId).Name;
string subject = _localizer["TwoFactorEmailSubject"]; string subject = _localizer["TwoFactorEmailSubject"];
subject = subject.Replace("[SiteName]", siteName); subject = subject.Replace("[SiteName]", siteName);
string body = _localizer["TwoFactorEmailBody"].Value; string body = _localizer["TwoFactorEmailBody"].Value;
body = body.Replace("[UserDisplayName]", user.DisplayName); body = body.Replace("[UserDisplayName]", user.DisplayName);
body = body.Replace("[URL]", url);
body = body.Replace("[SiteName]", siteName); body = body.Replace("[SiteName]", siteName);
body = body.Replace("[Token]", token); body = body.Replace("[Token]", token);
var notification = new Notification(alias.SiteId, user, subject, body); var notification = new Notification(alias.SiteId, user, subject, body);
@ -374,6 +374,8 @@ namespace Oqtane.Managers
_users.UpdateUser(user); _users.UpdateUser(user);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress); _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful For {Username} From IP Address {IPAddress}", user.Username, LastIPAddress);
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
if (setCookie) if (setCookie)
{ {
await _identitySignInManager.SignInAsync(identityuser, isPersistent); await _identitySignInManager.SignInAsync(identityuser, isPersistent);

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.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.06.00.01.01")]
public class AddLanguageName : MultiDatabaseMigration
{
public AddLanguageName(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
// Name column was removed in 5.2.4 however SQLite does not support column removal so it had to be restored
if (ActiveDatabase.Name != "Sqlite")
{
var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase);
languageEntityBuilder.AddStringColumn("Name", 100, true);
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
@ -19,6 +19,7 @@
<DefineConstants>$(DefineConstants);OQTANE;OQTANE3</DefineConstants> <DefineConstants>$(DefineConstants);OQTANE;OQTANE3</DefineConstants>
<PreserveCompilationContext>true</PreserveCompilationContext> <PreserveCompilationContext>true</PreserveCompilationContext>
<SatelliteResourceLanguages>none</SatelliteResourceLanguages> <SatelliteResourceLanguages>none</SatelliteResourceLanguages>
<CompressionEnabled>false</CompressionEnabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="wwwroot\Modules\Templates\**" /> <Compile Remove="wwwroot\Modules\Templates\**" />

View File

@ -35,6 +35,7 @@ namespace Oqtane.Pages
{ {
await _userManager.LogoutUserEverywhere(user); await _userManager.LogoutUserEverywhere(user);
} }
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Logout");
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload); _syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, SyncEventActions.Reload);
} }

View File

@ -31,7 +31,7 @@ namespace Oqtane.Repository
.ToList() .ToList()
.ForEach(l => l.IsDefault = false); .ForEach(l => l.IsDefault = false);
} }
language.Name = ""; // stored in database but not used (SQLite limitation)
db.Language.Add(language); db.Language.Add(language);
db.SaveChanges(); db.SaveChanges();
@ -55,6 +55,7 @@ namespace Oqtane.Repository
.ForEach(l => l.IsDefault = false); .ForEach(l => l.IsDefault = false);
} }
language.Name = ""; // stored in database but not used (SQLite limitation)
db.SaveChanges(); db.SaveChanges();
} }

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -31,7 +32,47 @@ namespace Oqtane.Repository
{ {
page.PermissionList = permissions.Where(item => item.EntityId == page.PageId).ToList(); page.PermissionList = permissions.Where(item => item.EntityId == page.PageId).ToList();
} }
return pages; return GetPagesHierarchy(pages);
}
private static List<Page> GetPagesHierarchy(List<Page> pages)
{
List<Page> hierarchy = new List<Page>();
Action<List<Page>, Page> getPath = null;
getPath = (pageList, page) =>
{
IEnumerable<Page> children;
int level;
if (page == null)
{
level = -1;
children = pages.Where(item => item.ParentId == null);
}
else
{
level = page.Level;
children = pages.Where(item => item.ParentId == page.PageId);
}
foreach (Page child in children)
{
child.Level = level + 1;
child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation);
hierarchy.Add(child);
getPath(pageList, child);
}
};
pages = pages.OrderBy(item => item.Order).ToList();
getPath(pages, null);
// add any non-hierarchical items to the end of the list
foreach (Page page in pages)
{
if (hierarchy.Find(item => item.PageId == page.PageId) == null)
{
hierarchy.Add(page);
}
}
return hierarchy;
} }
public Page AddPage(Page page) public Page AddPage(Page page)

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="ForgotPasswordEmailBody" xml:space="preserve"> <data name="ForgotPasswordEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName]&lt;br&gt;&lt;br&gt;You recently requested to reset your password. Please use the link below to complete the process: &lt;b&gt;&lt;a href="[URL]"&gt;&lt;br&gt;&lt;br&gt;Click here to Reset Password&lt;/a&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site.&lt;br&gt;&lt;br&gt;If you did not request to reset your password you can safely ignore this message.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] team</value> <value>Dear [UserDisplayName]&lt;br&gt;&lt;br&gt;You recently requested to reset your password. Please use the link below to complete the process: &lt;b&gt;&lt;a href="[URL]"&gt;&lt;br&gt;&lt;br&gt;Click here to Reset Password&lt;/a&gt;&lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the link is only valid for 24 hours so if you are unable to take action within that time period, you should initiate another password reset on the site.&lt;br&gt;&lt;br&gt;If you did not request to reset your password you can safely ignore this message.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team</value>
</data> </data>
<data name="ForgotPasswordEmailSubject" xml:space="preserve"> <data name="ForgotPasswordEmailSubject" xml:space="preserve">
<value>Password Reset Notification Sent For [SiteName]</value> <value>Password Reset Notification Sent For [SiteName]</value>
@ -130,7 +130,7 @@
<value>User Account Notification for [SiteName]</value> <value>User Account Notification for [SiteName]</value>
</data> </data>
<data name="TwoFactorEmailBody" xml:space="preserve"> <data name="TwoFactorEmailBody" xml:space="preserve">
<value>Dear [UserDisplayName] + ",&lt;br&gt;&lt;br&gt;You requested a secure verification code to log in to your account. Please enter the secure verification code on the site:&lt;br&gt;&lt;br&gt;&lt;b&gt;[Token] &lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the [Alias].&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team"</value> <value>Dear [UserDisplayName],&lt;br&gt;&lt;br&gt;You requested a secure verification code to log in to your account. Please enter the secure verification code on the site:&lt;br&gt;&lt;br&gt;&lt;b&gt;[Token] &lt;/b&gt;&lt;br&gt;&lt;br&gt;Please note that the code is only valid for 10 minutes so if you are unable to take action within that time period, you should initiate a new login on the site.&lt;br&gt;&lt;br&gt;Thank You!&lt;br&gt;[SiteName] Team</value>
</data> </data>
<data name="TwoFactorEmailSubject" xml:space="preserve"> <data name="TwoFactorEmailSubject" xml:space="preserve">
<value>User Verification Code for [SiteName]</value> <value>User Verification Code for [SiteName]</value>

View File

@ -127,7 +127,6 @@ namespace Oqtane.Services
.ToDictionary(setting => setting.SettingName, setting => (setting.IsPrivate ? _private : "") + setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => (setting.IsPrivate ? _private : "") + setting.SettingValue);
site.Pages.Add(page); site.Pages.Add(page);
} }
site.Pages = GetPagesHierarchy(site.Pages);
// framework modules // framework modules
var modules = GetPageModules(site.SiteId); var modules = GetPageModules(site.SiteId);

View File

@ -20,10 +20,10 @@
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd" /> <Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh" /> <Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd" /> <Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh" /> <Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
</Target> </Target>
</Project> </Project>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>[Owner].Module.[Module]</id> <id>$projectname$</id>
<version>1.0.0</version> <version>1.0.0</version>
<authors>[Owner]</authors> <authors>[Owner]</authors>
<owners>[Owner]</owners> <owners>[Owner]</owners>
@ -20,12 +20,12 @@
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<file src="..\Client\bin\Release\net9.0\[Owner].Module.[Module].Client.Oqtane.dll" target="lib\net9.0" /> <file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\net9.0\[Owner].Module.[Module].Client.Oqtane.pdb" target="lib\net9.0" /> <file src="..\Client\bin\Release\$targetframework$\$ProjectName$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\net9.0\[Owner].Module.[Module].Server.Oqtane.dll" target="lib\net9.0" /> <file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Server\bin\Release\net9.0\[Owner].Module.[Module].Server.Oqtane.pdb" target="lib\net9.0" /> <file src="..\Server\bin\Release\$targetframework$\$ProjectName$.Server.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\net9.0\[Owner].Module.[Module].Shared.Oqtane.dll" target="lib\net9.0" /> <file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Shared\bin\Release\net9.0\[Owner].Module.[Module].Shared.Oqtane.pdb" target="lib\net9.0" /> <file src="..\Shared\bin\Release\$targetframework$\$ProjectName$.Shared.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Server\wwwroot\**\*.*" target="wwwroot" /> <file src="..\Server\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>

View File

@ -1,7 +1,11 @@
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Module.[Module].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y @echo off
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Module.[Module].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y set TargetFramework=%1
XCOPY "..\Server\bin\Debug\net9.0\[Owner].Module.[Module].Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y set ProjectName=%2
XCOPY "..\Server\bin\Debug\net9.0\[Owner].Module.[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y
XCOPY "..\Shared\bin\Debug\net9.0\[Owner].Module.[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\net9.0\[Owner].Module.[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\bin\Debug\%TargetFramework%\%ProjectName%.Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Shared\bin\Debug\%TargetFramework%\%ProjectName%.Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I

View File

@ -1,7 +1,12 @@
cp -f "../Client/bin/Debug/net9.0/[Owner].Module.[Module].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/" #!/bin/bash
cp -f "../Client/bin/Debug/net9.0/[Owner].Module.[Module].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Server/bin/Debug/net9.0/[Owner].Module.[Module].Server.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/" TargetFramework=$1
cp -f "../Server/bin/Debug/net9.0/[Owner].Module.[Module].Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/" ProjectName=$2
cp -f "../Shared/bin/Debug/net9.0/[Owner].Module.[Module].Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -f "../Shared/bin/Debug/net9.0/[Owner].Module.[Module].Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/" cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName$.Server.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName$.Shared.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/"

View File

@ -1,4 +1,7 @@
del "*.nupkg" @echo off
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].Module.[Module].nuspec set TargetFramework=%1
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y set ProjectName=%2
del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\" /Y

View File

@ -1,2 +1,5 @@
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack [Owner].Module.[Module].nuspec TargetFramework=$1
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\" ProjectName=$2
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\"

View File

@ -18,10 +18,10 @@
</ItemGroup> </ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent"> <Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd" /> <Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="debug.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh" /> <Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Debug'" Command="bash $(ProjectDir)debug.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd" /> <Exec Condition="'$(OS)' == 'Windows_NT' And '$(Configuration)' == 'Release'" Command="release.cmd $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
<Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh" /> <Exec Condition="'$(OS)' != 'Windows_NT' And '$(Configuration)' == 'Release'" Command="bash $(ProjectDir)release.sh $(TargetFramework) $([System.String]::Copy('$(MSBuildProjectName)').Replace('.Package',''))" />
</Target> </Target>
</Project> </Project>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>[Owner].Theme.[Theme]</id> <id>$projectname$</id>
<version>1.0.0</version> <version>1.0.0</version>
<authors>[Owner]</authors> <authors>[Owner]</authors>
<owners>[Owner]</owners> <owners>[Owner]</owners>
@ -20,8 +20,8 @@
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<file src="..\Client\bin\Release\net9.0\[Owner].Theme.[Theme].Client.Oqtane.dll" target="lib\net9.0" /> <file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.dll" target="lib\$targetframework$" />
<file src="..\Client\bin\Release\net9.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" target="lib\net9.0" /> <file src="..\Client\bin\Release\$targetframework$\$projectname$.Client.Oqtane.pdb" target="lib\$targetframework$" />
<file src="..\Client\wwwroot\**\*.*" target="wwwroot" /> <file src="..\Client\wwwroot\**\*.*" target="wwwroot" />
<file src="icon.png" target="" /> <file src="icon.png" target="" />
</files> </files>

View File

@ -1,3 +1,7 @@
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Theme.[Theme].Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y @echo off
XCOPY "..\Client\bin\Debug\net9.0\[Owner].Theme.[Theme].Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net9.0\" /Y set TargetFramework=%1
set ProjectName=%2
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\bin\Debug\%TargetFramework%\%ProjectName%.Client.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\%TargetFramework%\" /Y
XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I XCOPY "..\Client\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I

View File

@ -1,3 +1,8 @@
cp -f "../Client/bin/Debug/net9.0/[Owner].Theme.[Theme].Client.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/" #!/bin/bash
cp -f "../Client/bin/Debug/net9.0/[Owner].Theme.[Theme].Client.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/net9.0/"
cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/" TargetFramework=$1
ProjectName=$2
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.dll" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Client/bin/Debug/$TargetFramework/$ProjectName$.Client.Oqtane.pdb" "../../[RootFolder]/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -rf "../Server/wwwroot/"* "../../[RootFolder]/Oqtane.Server/wwwroot/"

View File

@ -1,3 +1,7 @@
@echo off
set TargetFramework=%1
set ProjectName=%2
del "*.nupkg" del "*.nupkg"
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack [Owner].Theme.[Theme].nuspec "..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Packages\" /Y XCOPY "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Packages\" /Y

View File

@ -1,2 +1,5 @@
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack [Owner].Theme.[Theme].nuspec TargetFramework=$1
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\" ProjectName=$2
"..\..\[RootFolder]\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
cp -f "*.nupkg" "..\..\[RootFolder]\Oqtane.Server\Packages\"

View File

@ -120,13 +120,22 @@ Oqtane.Interop = {
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore); this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
} }
}, },
includeScript: function (id, src, integrity, crossorigin, type, content, location) { includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
var script; var script;
if (src !== "") { if (src !== "") {
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]"); script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
} }
else { else {
if (id !== "") {
script = document.getElementById(id); script = document.getElementById(id);
} else {
const scripts = document.querySelectorAll("script:not([src])");
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].textContent.includes(content)) {
script = scripts[i];
}
}
}
} }
if (script !== null) { if (script !== null) {
script.remove(); script.remove();
@ -152,37 +161,36 @@ Oqtane.Interop = {
else { else {
script.innerHTML = content; script.innerHTML = content;
} }
script.async = false; if (dataAttributes !== null) {
this.addScript(script, location) for (var key in dataAttributes) {
.then(() => { script.setAttribute(key, dataAttributes[key]);
}
}
try {
this.addScript(script, location);
} catch (error) {
if (src !== "") { if (src !== "") {
console.log(src + ' loaded'); console.error("Failed to load external script: ${src}", error);
} else {
console.error("Failed to load inline script: ${content}", error);
} }
else {
console.log(id + ' loaded');
} }
})
.catch(() => {
if (src !== "") {
console.error(src + ' failed');
}
else {
console.error(id + ' failed');
}
});
} }
}, },
addScript: function (script, location) { addScript: function (script, location) {
return new Promise((resolve, reject) => {
script.async = false;
script.defer = false;
script.onload = () => resolve();
script.onerror = (error) => reject(error);
if (location === 'head') { if (location === 'head') {
document.head.appendChild(script); document.head.appendChild(script);
} } else {
if (location === 'body') {
document.body.appendChild(script); document.body.appendChild(script);
} }
return new Promise((res, rej) => {
script.onload = res();
script.onerror = rej();
}); });
}, },
includeScripts: async function (scripts) { includeScripts: async function (scripts) {
@ -222,10 +230,10 @@ Oqtane.Interop = {
if (scripts[s].crossorigin !== '') { if (scripts[s].crossorigin !== '') {
element.crossOrigin = scripts[s].crossorigin; element.crossOrigin = scripts[s].crossorigin;
} }
if (scripts[s].es6module === true) { if (scripts[s].type !== '') {
element.type = "module"; element.type = scripts[s].type;
} }
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) { if (scripts[s].dataAttributes !== null) {
for (var key in scripts[s].dataAttributes) { for (var key in scripts[s].dataAttributes) {
element.setAttribute(key, scripts[s].dataAttributes[key]); element.setAttribute(key, scripts[s].dataAttributes[key]);
} }

View File

@ -0,0 +1,80 @@
const scriptInfoBySrc = new Map();
function getKey(script) {
if (script.hasAttribute("src") && script.src !== "") {
return script.src;
} else {
return script.innerHTML;
}
}
export function onUpdate() {
let timestamp = Date.now();
let enhancedNavigation = scriptInfoBySrc.size !== 0;
// iterate over all script elements in page
const scripts = document.getElementsByTagName("script");
for (const script of Array.from(scripts)) {
let key = getKey(script);
let scriptInfo = scriptInfoBySrc.get(key);
if (!scriptInfo) {
// new script added
scriptInfo = { timestamp: timestamp };
scriptInfoBySrc.set(key, scriptInfo);
if (enhancedNavigation) {
reloadScript(script);
}
} else {
// existing script
scriptInfo.timestamp = timestamp;
if (script.hasAttribute("data-reload") && script.getAttribute("data-reload") === "true") {
reloadScript(script);
}
}
}
// remove scripts that are no longer referenced
for (const [key, scriptInfo] of scriptInfoBySrc) {
if (scriptInfo.timestamp !== timestamp) {
scriptInfoBySrc.delete(key);
}
}
}
function reloadScript(script) {
try {
replaceScript(script);
} catch (error) {
if (script.hasAttribute("src") && script.src !== "") {
console.error("Failed to load external script: ${script.src}", error);
} else {
console.error("Failed to load inline script: ${script.innerHtml}", error);
}
}
}
function replaceScript(script) {
return new Promise((resolve, reject) => {
var newScript = document.createElement("script");
// replicate attributes and content
for (let i = 0; i < script.attributes.length; i++) {
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
newScript.innerHTML = script.innerHTML;
// dynamically injected scripts cannot be async or deferred
newScript.async = false;
newScript.defer = false;
newScript.onload = () => resolve();
newScript.onerror = (error) => reject(error);
// remove existing script
script.remove();
// replace with new script to force reload in Blazor
document.head.appendChild(newScript);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -56,7 +56,9 @@ namespace Oqtane.Models
public string Description { get; set; } public string Description { get; set; }
/// <summary> /// <summary>
/// Deprecated - not used /// Deprecated
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
/// Therefore the property must be retained/mapped even though the framework no longer uses it
/// </summary> /// </summary>
public bool? IsDeleted { get; set; } public bool? IsDeleted { get; set; }

View File

@ -63,7 +63,9 @@ namespace Oqtane.Models
public bool IsSystem { get; set; } public bool IsSystem { get; set; }
/// <summary> /// <summary>
/// Deprecated - not used /// Deprecated
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
/// Therefore the property must be retained/mapped even though the framework no longer uses it
/// </summary> /// </summary>
public bool? IsDeleted { get; set; } public bool? IsDeleted { get; set; }

View File

@ -29,9 +29,10 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public bool IsDefault { get; set; } public bool IsDefault { get; set; }
[NotMapped]
/// <summary> /// <summary>
/// Language Name - corresponds to <see cref="Culture.DisplayName"/>, _not_ <see cref="Culture.Name"/> /// Language Name - corresponds to <see cref="Culture.DisplayName"/>, _not_ <see cref="Culture.Name"/>
/// Note that this property still exists in the database because columns cannot be dropped in SQLite
/// Therefore the property must be retained/mapped even though the framework populates it from the Culture API
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }

View File

@ -144,25 +144,25 @@ namespace Oqtane.Models
{ {
FromUserId = from.UserId; FromUserId = from.UserId;
FromDisplayName = from.DisplayName; FromDisplayName = from.DisplayName;
FromEmail = from.Email; FromEmail = from.Email ?? "";
} }
else else
{ {
FromUserId = null; FromUserId = null;
FromDisplayName = fromDisplayName; FromDisplayName = fromDisplayName;
FromEmail = fromEmail; FromEmail = fromEmail ?? "";
} }
if (to != null) if (to != null)
{ {
ToUserId = to.UserId; ToUserId = to.UserId;
ToDisplayName = to.DisplayName; ToDisplayName = to.DisplayName;
ToEmail = to.Email; ToEmail = to.Email ?? "";
} }
else else
{ {
ToUserId = null; ToUserId = null;
ToDisplayName = toDisplayName; ToDisplayName = toDisplayName;
ToEmail = toEmail; ToEmail = toEmail ?? "";
} }
Subject = subject; Subject = subject;
Body = body; Body = body;

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Shared; using Oqtane.Shared;
namespace Oqtane.Models namespace Oqtane.Models
@ -27,6 +29,11 @@ namespace Oqtane.Models
} }
} }
/// <summary>
/// For Scripts this allows type to be specified - not applicable to Stylesheets
/// </summary>
public string Type { get; set; }
/// <summary> /// <summary>
/// Integrity checks to increase the security of resources accessed. Especially common in CDN resources. /// Integrity checks to increase the security of resources accessed. Especially common in CDN resources.
/// </summary> /// </summary>
@ -52,11 +59,6 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public ResourceLocation Location { get; set; } public ResourceLocation Location { get; set; }
/// <summary>
/// For Scripts this allows type="module" registrations - not applicable to Stylesheets
/// </summary>
public bool ES6Module { get; set; }
/// <summary> /// <summary>
/// Allows specification of inline script - not applicable to Stylesheets /// Allows specification of inline script - not applicable to Stylesheets
/// </summary> /// </summary>
@ -72,6 +74,11 @@ namespace Oqtane.Models
/// </summary> /// </summary>
public bool Reload { get; set; } public bool Reload { get; set; }
/// <summary>
/// Cusotm data-* attributes for scripts - not applicable to Stylesheets
/// </summary>
public Dictionary<string, string> DataAttributes { get; set; }
/// <summary> /// <summary>
/// The namespace of the component that declared the resource - only used in SiteRouter /// The namespace of the component that declared the resource - only used in SiteRouter
/// </summary> /// </summary>
@ -82,14 +89,22 @@ namespace Oqtane.Models
var resource = new Resource(); var resource = new Resource();
resource.ResourceType = ResourceType; resource.ResourceType = ResourceType;
resource.Url = Url; resource.Url = Url;
resource.Type = Type;
resource.Integrity = Integrity; resource.Integrity = Integrity;
resource.CrossOrigin = CrossOrigin; resource.CrossOrigin = CrossOrigin;
resource.Bundle = Bundle; resource.Bundle = Bundle;
resource.Location = Location; resource.Location = Location;
resource.ES6Module = ES6Module;
resource.Content = Content; resource.Content = Content;
resource.RenderMode = RenderMode; resource.RenderMode = RenderMode;
resource.Reload = Reload; resource.Reload = Reload;
resource.DataAttributes = new Dictionary<string, string>();
if (DataAttributes != null && DataAttributes.Count > 0)
{
foreach (var kvp in DataAttributes)
{
resource.DataAttributes.Add(kvp.Key, kvp.Value);
}
}
resource.Level = level; resource.Level = level;
resource.Namespace = name; resource.Namespace = name;
return resource; return resource;
@ -97,5 +112,18 @@ namespace Oqtane.Models
[Obsolete("ResourceDeclaration is deprecated", false)] [Obsolete("ResourceDeclaration is deprecated", false)]
public ResourceDeclaration Declaration { get; set; } public ResourceDeclaration Declaration { get; set; }
[Obsolete("ES6Module is deprecated. Use Type property instead for scripts.", false)]
public bool ES6Module
{
get => (Type == "module");
set
{
if (value)
{
Type = "module";
};
}
}
} }
} }

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Oqtane.Shared;
namespace Oqtane.Models
{
/// <summary>
/// Script inherits from Resource and offers constructors with parameters specific to Scripts
/// </summary>
public class Script : Resource
{
private void SetDefaults()
{
this.ResourceType = ResourceType.Script;
this.Location = ResourceLocation.Body;
}
public Script(string Src)
{
SetDefaults();
this.Url = Src;
}
public Script(string Content, string Type)
{
SetDefaults();
this.Content = Content;
this.Type = Type;
}
public Script(string Src, string Integrity, string CrossOrigin)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
}
public Script(string Src, string Integrity, string CrossOrigin, string Type, string Content, ResourceLocation Location, string Bundle, bool Reload, Dictionary<string, string> DataAttributes, string RenderMode)
{
SetDefaults();
this.Url = Src;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
this.Type = Type;
this.Content = Content;
this.Location = Location;
this.Bundle = Bundle;
this.Reload = Reload;
this.DataAttributes = DataAttributes;
this.RenderMode = RenderMode;
}
}
}

View File

@ -0,0 +1,30 @@
using Oqtane.Shared;
namespace Oqtane.Models
{
/// <summary>
/// Stylesheet inherits from Resource and offers constructors with parameters specific to Stylesheets
/// </summary>
public class Stylesheet : Resource
{
private void SetDefaults()
{
this.ResourceType = ResourceType.Stylesheet;
this.Location = ResourceLocation.Head;
}
public Stylesheet(string Href)
{
SetDefaults();
this.Url = Href;
}
public Stylesheet(string Href, string Integrity, string CrossOrigin)
{
SetDefaults();
this.Url = Href;
this.Integrity = Integrity;
this.CrossOrigin = CrossOrigin;
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -4,8 +4,8 @@ namespace Oqtane.Shared
{ {
public class Constants public class Constants
{ {
public static readonly string Version = "6.0.0"; public static readonly string Version = "6.0.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,5.2.2,5.2.3,5.2.4,6.0.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,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1";
public const string PackageId = "Oqtane.Framework"; public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client"; public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater"; public const string UpdaterPackageId = "Oqtane.Updater";

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>6.0.0</Version> <Version>6.0.1</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>

View File

@ -1,26 +1,28 @@
# Latest Release
[5.2.4](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4) was released on October 17, 2024 and is a maintenance release including 51 pull requests by 7 different contributors, pushing the total number of project commits all-time to over 5900. 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)
# Oqtane Framework # Oqtane Framework
![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane") ![Oqtane](https://github.com/oqtane/framework/blob/master/oqtane.png?raw=true "Oqtane")
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI). Oqtane is an open source Content Management System (CMS) and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on modern .NET.
Oqtane is being developed based on some fundamental principles which are outlined in the [Oqtane Philosophy](https://www.oqtane.org/blog/!/20/oqtane-philosophy). Oqtane allows you to "Build Applications, Not Infrastructure" which means that you can focus your investment on solving your unique business challenges rather than wasting time and effort on building general infrastructure.
Please note that this project is owned by the .NET Foundation and is governed by the **[.NET Foundation Contributor Covenant Code of Conduct](https://dotnetfoundation.org/code-of-conduct)** Oqtane is "Rocket Fuel for Blazor" as it provides powerful capabilities to accelerate your Blazor development experience, providing scalable services and a composable UI which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
# Getting Started (Version 5.x) Oqtane is being developed based on some fundamental principles which are outlined in the [Oqtane Philosophy](https://www.oqtane.org/blog/!/20/oqtane-philosophy). This project is an official member of the .NET Foundation and is governed by the **[.NET Foundation Contributor Covenant Code of Conduct](https://dotnetfoundation.org/code-of-conduct)**
# Latest Release
[6.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0) was released on November 14, 2024 and is a major release including 39 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 6000. 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)
# Getting Started (Version 6.x)
**Installing using source code from the Dev/Master branch:** **Installing using source code from the Dev/Master branch:**
- Install **[.NET 8.0.10 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. - Install **[.NET 9.0.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.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**. - Install the latest edition (v17.12 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**.
- Clone (or download) the Oqtane Master or Dev branch source code to your local system. - Clone (or download) the Oqtane Master or Dev branch source code to your local system.
@ -84,7 +86,7 @@ Connect with other developers, get support, and share ideas by joining the Oqtan
# Roadmap # Roadmap
This project is open source, and therefore is a work in progress... This project is open source, and therefore is a work in progress...
6.0.0 (Nov 13, 2024) [6.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0) (Nov 14, 2024)
- [x] Migration to .NET 9 - [x] Migration to .NET 9
[5.2.4](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4) (Oct 17, 2024) [5.2.4](https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.4) (Oct 17, 2024)

15
SECURITY.md Normal file
View File

@ -0,0 +1,15 @@
# Security Policy
## Reporting a Vulnerability
We make every effort to ensure rapid and thorough analysis of reported issues and, where appropriate, provide workarounds and updated application releases to fix them. If you identify a potential security vulnerability please report it via [the GitHub feature for reporting a security vulnerability](https://github.com/oqtane/oqtane.framework/security/advisories/new).
All submitted information is viewed only by members of the Oqtane Security Team, and will not be discussed outside the Team without the permission of the person/company who reported the issue. Each confirmed issue is assigned a severity level (critical, moderate, or low) corresponding to its potential impact on an Oqtane installation.
* **Critical** means the issue can be exploited by a remote attacker to gain access to data or functionality. All critical issue security bulletins include a recommended workaround or fix that should be applied as soon as possible.
* **Moderate** means the issue can compromise data or functionality on a portal/website only if some other condition is met (e.g. a particular module or a user within a particular role is required). Moderate issue security bulletins typically include recommended actions to resolve the issue.
* **Low** means the issue is very difficult to exploit or has a limited potential impact.
Once an issue has been resolved via a public release of Oqtane, the release notes on GitHub are updated to reflect that security bulletins exist for the release. We strongly suggest using the "Watch" option on GitHub for "Releases" at a minimum to receive notifications of updated Oqtane releases.
As a general policy, Oqtane does not issue Hot Fix releases to prior versions of the software. If a remediation is possible via configuration it shall be noted as applicable in the posted bulletins.