diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index cb097355..bafe7e9b 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -47,7 +47,7 @@ else protected override async Task OnParametersSetAsync() { - _languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.PackageId); + _languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId); var cultures = await LocalizationService.GetCulturesAsync(); var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture)); @@ -78,7 +78,7 @@ else private bool UpgradeAvailable(string code, string version) { var upgradeavailable = false; - if (_packages != null) + if (_packages != null && version != null) { var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault(); if (package != null) diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index afd95fdb..d763a5de 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -109,7 +109,7 @@ else // external link to log item will display Details component if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id)) { - NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"id={id}")); + NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}")); } if (UrlParameters.ContainsKey("level")) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index c0f8c951..b425b9bb 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -102,46 +102,43 @@ @SharedLocalizer["Cancel"] - @if (_languages != null) + @if (_languages != null && _languages.Count > 0) { - @if (_languages.Count > 0) - { - -
- @SharedLocalizer["Name"] - @Localizer["Code"] - @Localizer["Version"] -   -
- - @context.Name - @context.Code - @context.Version - - @if (context.IsDefault) + +
+ @SharedLocalizer["Name"] + @Localizer["Code"] + @Localizer["Version"] +   +
+ + @context.Name + @context.Code + @context.Version + + @if (context.IsDefault) + { + + } + else + { + if (UpgradeAvailable(_packagename + "." + context.Code, context.Version)) { - + } - else - { - if (UpgradeAvailable(_packagename + "." + context.Code, context.Version)) - { - - } - } - - -
- - } - else - { -
-
- @Localizer["Search.NoResults"] -
-
- } + } + +
+
+ + } + else + { +
+
+ @Localizer["Search.NoResults"] +
+
}
@@ -237,17 +234,20 @@ _modifiedby = moduleDefinition.ModifiedBy; _modifiedon = moduleDefinition.ModifiedOn; - _packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename); - _languages = await LanguageService.GetLanguagesAsync(-1, _packagename); - foreach (var package in _packages) + if (!string.IsNullOrEmpty(_packagename)) { - var code = package.PackageId.Split('.').Last(); - if (!_languages.Any(item => item.Code == code)) + _packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename); + _languages = await LanguageService.GetLanguagesAsync(-1, _packagename); + foreach (var package in _packages) { - _languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true }); + var code = package.PackageId.Split('.').Last(); + if (!_languages.Any(item => item.Code == code)) + { + _languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true }); + } } + _languages = _languages.OrderBy(item => item.Name).ToList(); } - _languages = _languages.OrderBy(item => item.Name).ToList(); } } catch (Exception ex) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index 3cfbcb7e..dcde0c2c 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -50,7 +50,7 @@ else - @if (context.AssemblyName != "Oqtane.Client") + @if (context.AssemblyName != Constants.ClientId) { } @@ -58,7 +58,7 @@ else @context.Name @context.Version - @if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) + @if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) { @SharedLocalizer["Yes"] } diff --git a/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor b/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor index 34a46b6c..51752e84 100644 --- a/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor +++ b/Oqtane.Client/Modules/Admin/RecycleBin/Index.razor @@ -7,75 +7,77 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - - @if (_pages == null) - { -
-

@Localizer["NoPage.Deleted"]

- } - else - { - -
-   -   - @SharedLocalizer["Name"] - @Localizer["DeletedBy"] - @Localizer["DeletedOn"] -
- - - - @context.Name - @context.DeletedBy - @context.DeletedOn - -
- @if (_pages.Any()) - { -
- } - } -
- - @if (_modules == null) - { -
-

@Localizer["NoModule.Deleted"]

- } - else - { - -
-   -   - @Localizer["Page"] - @Localizer["Module"] - @Localizer["DeletedBy"] - @Localizer["DeletedOn"] -
- - - - @_pages.Find(item => item.PageId == context.PageId).Name - @context.Title - @context.DeletedBy - @context.DeletedOn - -
- @if (_modules.Any()) - { -
- } - - } -
-
+@if (_pages == null || _modules == null) +{ +

@SharedLocalizer["Loading"]

+} +else +{ + + + @if (!_pages.Where(item => item.IsDeleted).Any()) + { +
+

@Localizer["NoPage.Deleted"]

+ } + else + { + +
+   +   + @SharedLocalizer["Name"] + @Localizer["DeletedBy"] + @Localizer["DeletedOn"] +
+ + + + @context.Name + @context.DeletedBy + @context.DeletedOn + +
+
+ + } +
+ + @if (!_modules.Where(item => item.IsDeleted).Any()) + { +
+

@Localizer["NoModule.Deleted"]

+ } + else + { + +
+   +   + @Localizer["Page"] + @Localizer["Module"] + @Localizer["DeletedBy"] + @Localizer["DeletedOn"] +
+ + + + @_pages.Find(item => item.PageId == context.PageId).Name + @context.Title + @context.DeletedBy + @context.DeletedOn + +
+
+ + } +
+
+} @code { - private List _pages; - private List _modules; + private List _pages; + private List _modules; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; @@ -95,10 +97,7 @@ private async Task Load() { _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); - _pages = _pages.Where(item => item.IsDeleted).ToList(); - _modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); - _modules = _modules.Where(item => item.IsDeleted).ToList(); } private async Task RestorePage(Page page) @@ -141,7 +140,7 @@ try { ModuleInstance.ShowProgressIndicator(); - foreach (Page page in _pages) + foreach (Page page in _pages.Where(item => item.IsDeleted)) { await PageService.DeletePageAsync(page.PageId); await logger.LogInformation("Page Permanently Deleted {Page}", page); @@ -184,9 +183,8 @@ try { await PageModuleService.DeletePageModuleAsync(module.PageModuleId); - // check if there are any remaining module instances in the site - _modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); + // check if there are any remaining module instances in the site if (!_modules.Exists(item => item.ModuleId == module.ModuleId)) { await ModuleService.DeleteModuleAsync(module.ModuleId); @@ -208,12 +206,11 @@ try { ModuleInstance.ShowProgressIndicator(); - foreach (Module module in _modules) + foreach (Module module in _modules.Where(item => item.IsDeleted)) { await PageModuleService.DeletePageModuleAsync(module.PageModuleId); - // check if there are any remaining module instances in the site - _modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); + // check if there are any remaining module instances in the site if (!_modules.Exists(item => item.ModuleId == module.ModuleId)) { await ModuleService.DeleteModuleAsync(module.ModuleId); diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 90213b9e..e613f553 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -29,7 +29,7 @@ else - @if (context.AssemblyName != "Oqtane.Client") + @if (context.AssemblyName != Constants.ClientId) { } diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a8f38030..13d67f4f 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -286,6 +286,15 @@ else + @if (_providertype == AuthenticationProviderTypes.OpenIDConnect) + { +
+ +
+ +
+
+ }
@@ -385,6 +394,7 @@ else private string _redirecturl; private string _identifierclaimtype; private string _emailclaimtype; + private string _roleclaimtype; private string _domainfilter; private string _createusers; @@ -436,8 +446,9 @@ else _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; - _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"); - _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); + _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); + _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); + _roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", ""); _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); @@ -555,6 +566,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); @@ -590,14 +602,10 @@ else if (_providertype == AuthenticationProviderTypes.OpenIDConnect) { _scopes = "openid,profile,email"; - _identifierclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"; - _emailclaimtype = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"; } else { _scopes = ""; - _identifierclaimtype = "sub"; - _emailclaimtype = "email"; } } _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index e317f38b..1f2ce31c 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -70,6 +70,9 @@ } } + + @Footer +
} @@ -185,6 +188,9 @@ [Parameter] public RenderFragment Row { get; set; } = null; // required + [Parameter] + public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts + [Parameter] public RenderFragment Detail { get; set; } = null; // only applicable to Table layouts @@ -293,6 +299,7 @@ { _page = 1; } + if (_page < 1) _page = 1; _startPage = 0; _endPage = 0; diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index a67eb421..2231b02f 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -262,7 +262,7 @@ { var interop = new Interop(JSRuntime); int pos = await interop.GetCaretPosition("rawhtmleditor"); - var image = "\"""; + var image = "\"""; _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); _rawfilemanager = false; } diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index be2c9d12..0d0b0eec 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -75,7 +75,7 @@ namespace Oqtane.Modules var scripts = new List(); foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) { - 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 }); } if (scripts.Any()) @@ -91,7 +91,7 @@ namespace Oqtane.Modules public string ModulePath() { - return "Modules/" + GetType().Namespace + "/"; + return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/"; } // url methods @@ -145,14 +145,23 @@ namespace Oqtane.Modules return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters); } - public string ContentUrl(int fileid) + public string FileUrl(string folderpath, string filename) { - return ContentUrl(fileid, false); + return FileUrl(folderpath, filename, false); } - public string ContentUrl(int fileid, bool asAttachment) + public string FileUrl(string folderpath, string filename, bool download) { - return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); + return Utilities.FileUrl(PageState.Alias, folderpath, filename, download); + } + public string FileUrl(int fileid) + { + return FileUrl(fileid, false); + } + + public string FileUrl(int fileid, bool download) + { + return Utilities.FileUrl(PageState.Alias, fileid, download); } public string ImageUrl(int fileid, int width, int height) @@ -407,5 +416,17 @@ namespace Oqtane.Modules await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args); } } + + [Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)] + public string ContentUrl(int fileid) + { + return ContentUrl(fileid, false); + } + + [Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)] + public string ContentUrl(int fileid, bool asAttachment) + { + return Utilities.FileUrl(PageState.Alias, fileid, asAttachment); + } } } diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 2d9e6a7b..8756d3d7 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -5,7 +5,7 @@ Exe 3.0 Debug;Release - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -13,7 +13,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 610bf26e..31219ace 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -63,7 +63,7 @@ namespace Oqtane.Client { var dlls = new Dictionary(); var pdbs = new Dictionary(); - var filter = new List(); + var list = new List(); var jsRuntime = serviceProvider.GetRequiredService(); var interop = new Interop(jsRuntime); @@ -81,14 +81,14 @@ namespace Oqtane.Client var file = files.FirstOrDefault(item => item.Contains(assembly)); if (file == null) { - filter.Add(assembly); + list.Add(assembly); } else { // check if newer version available if (GetFileDate(assembly) > GetFileDate(file)) { - filter.Add(assembly); + list.Add(assembly); } } } @@ -96,7 +96,7 @@ namespace Oqtane.Client // get assemblies already downloaded foreach (var file in files) { - if (assemblies.Contains(file) && !filter.Contains(file)) + if (assemblies.Contains(file) && !list.Contains(file)) { try { @@ -117,6 +117,7 @@ namespace Oqtane.Client try { await interop.RemoveIndexedDBItem(file); + await interop.RemoveIndexedDBItem(file.Replace(".dll", ".pdb")); } catch { @@ -127,13 +128,13 @@ namespace Oqtane.Client } else { - filter.Add("*"); + list.Add("*"); } - if (filter.Count != 0) + if (list.Count != 0) { // get assemblies from server and load into client app domain - var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", filter)); + var zip = await http.GetByteArrayAsync($"/api/Installation/load?list=" + string.Join(",", list)); // asemblies and debug symbols are packaged in a zip file using (ZipArchive archive = new ZipArchive(new MemoryStream(zip))) diff --git a/Oqtane.Client/Resources/Modules/Admin/RecycleBin/Index.resx b/Oqtane.Client/Resources/Modules/Admin/RecycleBin/Index.resx index 864b7170..25184660 100644 --- a/Oqtane.Client/Resources/Modules/Admin/RecycleBin/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/RecycleBin/Index.resx @@ -1,4 +1,4 @@ - + Exe - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 3.2.0 + 3.2.1 1 14.2 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 3b773c5e..e00d316e 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 3.2.0 + 3.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 5a916fc7..6770b223 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 3.2.0 + 3.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v3.2.0/Oqtane.Framework.3.2.0.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/download/v3.2.1/Oqtane.Framework.3.2.1.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index a0bbbbb6..62de57e5 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 3.2.0 + 3.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 449e256f..7611f471 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 3.2.0 + 3.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index f42dec37..ee1e61f4 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 3.2.0 + 3.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 089fd43d..81b2aefc 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.0.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Install.zip" -Force \ No newline at end of file diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index f68a8c34..f762eabb 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.0.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Upgrade.zip" -Force \ No newline at end of file diff --git a/Oqtane.Server/Controllers/AliasController.cs b/Oqtane.Server/Controllers/AliasController.cs index c87176e5..adaf3945 100644 --- a/Oqtane.Server/Controllers/AliasController.cs +++ b/Oqtane.Server/Controllers/AliasController.cs @@ -17,13 +17,15 @@ namespace Oqtane.Controllers { private readonly IAliasRepository _aliases; private readonly ITenantRepository _tenants; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public AliasController(IAliasRepository aliases, ITenantRepository tenants, ILogManager logger, ITenantManager tenantManager) + public AliasController(IAliasRepository aliases, ITenantRepository tenants, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _aliases = aliases; _tenants = tenants; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -57,6 +59,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid) { alias = _aliases.AddAlias(alias); + _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Alias Added {Alias}", alias); } else @@ -76,6 +79,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && _aliases.GetAlias(alias.AliasId, false) != null) { alias = _aliases.UpdateAlias(alias); + _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Alias Updated {Alias}", alias); } else @@ -96,6 +100,7 @@ namespace Oqtane.Controllers if (alias != null) { _aliases.DeleteAlias(id); + _syncManager.AddSyncEvent(alias.TenantId, EntityNames.Alias, alias.AliasId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Alias Deleted {AliasId}", id); var aliases = _aliases.GetAliases(); diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 9f95cd70..fe8aab8c 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -32,15 +32,17 @@ namespace Oqtane.Controllers private readonly IFileRepository _files; private readonly IFolderRepository _folders; private readonly IUserPermissions _userPermissions; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager) + public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _environment = environment; _files = files; _folders = folders; _userPermissions = userPermissions; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -148,6 +150,7 @@ namespace Oqtane.Controllers file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", ""); file = _files.UpdateFile(file); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file); } else @@ -168,8 +171,6 @@ namespace Oqtane.Controllers Models.File file = _files.GetFile(id); if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, file.Folder.FolderId, PermissionNames.Edit)) { - _files.DeleteFile(id); - string filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { @@ -180,6 +181,8 @@ namespace Oqtane.Controllers } } + _files.DeleteFile(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file); } else @@ -251,6 +254,7 @@ namespace Oqtane.Controllers if (file != null) { file = _files.AddFile(file); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); } } catch (Exception ex) @@ -324,7 +328,8 @@ namespace Oqtane.Controllers var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload)); if (file != null) { - _files.AddFile(file); + file = _files.AddFile(file); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); } } } @@ -486,10 +491,15 @@ namespace Oqtane.Controllers var filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { - var result = asAttachment - ? PhysicalFile(filepath, file.GetMimeType(), file.Name) - : PhysicalFile(filepath, file.GetMimeType()); - return result; + if (asAttachment) + { + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download"); + return PhysicalFile(filepath, file.GetMimeType(), file.Name); + } + else + { + return PhysicalFile(filepath, file.GetMimeType()); + } } else { diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index cb2c0cf0..d15f89f3 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -20,13 +20,15 @@ namespace Oqtane.Controllers { private readonly IFolderRepository _folders; private readonly IUserPermissions _userPermissions; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager) + public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _folders = folders; _userPermissions = userPermissions; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -124,6 +126,7 @@ namespace Oqtane.Controllers } folder.Path = folder.Path + "/"; folder = _folders.AddFolder(folder); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder); } else @@ -172,6 +175,7 @@ namespace Oqtane.Controllers } folder = _folders.UpdateFolder(folder); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder); } else @@ -205,6 +209,7 @@ namespace Oqtane.Controllers { folder.Order = order; _folders.UpdateFolder(folder); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); } order += 2; } @@ -230,6 +235,7 @@ namespace Oqtane.Controllers Directory.Delete(_folders.GetFolderPath(folder)); } _folders.DeleteFolder(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id); } else diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index ff5e63c8..17398958 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -103,10 +103,7 @@ namespace Oqtane.Controllers [HttpGet("list")] public List List() { - return _cache.GetOrCreate("assemblieslist", entry => - { - return GetAssemblyList(); - }); + return GetAssemblyList().Select(item => item.HashedName).ToList(); } // GET api//load?list=x,y @@ -116,81 +113,90 @@ namespace Oqtane.Controllers return File(GetAssemblies(list), System.Net.Mime.MediaTypeNames.Application.Octet, "oqtane.dll"); } - private List GetAssemblyList() + private List GetAssemblyList() { - // get list of assemblies which should be downloaded to client - var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); - var list = assemblies.Select(a => a.GetName().Name).ToList(); - - // include version numbers - for (int i = 0; i < list.Count; i++) + return _cache.GetOrCreate("assemblieslist", entry => { - list[i] = Path.GetFileName(AddFileDate(Path.Combine(binFolder, list[i] + ".dll"))); - } + var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var assemblyList = new List(); - // insert satellite assemblies at beginning of list - foreach (var culture in _localizationManager.GetInstalledCultures()) - { - var assembliesFolderPath = Path.Combine(binFolder, culture); - if (culture == Constants.DefaultCulture) + // get list of assemblies which should be downloaded to client + var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); + var list = assemblies.Select(a => a.GetName().Name).ToList(); + + // populate assemblies + for (int i = 0; i < list.Count; i++) { - continue; + assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll"))); } - if (Directory.Exists(assembliesFolderPath)) + // insert satellite assemblies at beginning of list + foreach (var culture in _localizationManager.GetInstalledCultures()) { - foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath)) + var assembliesFolderPath = Path.Combine(binFolder, culture); + if (culture == Constants.DefaultCulture) { - list.Insert(0, culture + "/" + Path.GetFileName(AddFileDate(resourceFile))); + continue; + } + + if (Directory.Exists(assembliesFolderPath)) + { + foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath)) + { + assemblyList.Insert(0, new ClientAssembly(resourceFile)); + } + } + else + { + _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); } } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); - } - } - // insert module and theme dependencies at beginning of list - foreach (var assembly in assemblies) - { - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule)))) + // insert module and theme dependencies at beginning of list + foreach (var assembly in assemblies) { - var instance = Activator.CreateInstance(type) as IModule; - foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule)))) { - var path = Path.Combine(binFolder, name + ".dll"); - if (System.IO.File.Exists(path)) + var instance = Activator.CreateInstance(type) as IModule; + foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) { - path = Path.GetFileName(AddFileDate(path)); - if (!list.Contains(path)) list.Insert(0, path); + var filepath = Path.Combine(binFolder, name + ".dll"); + if (System.IO.File.Exists(filepath)) + { + if (!assemblyList.Exists(item => item.FilePath == filepath)) + { + assemblyList.Insert(0, new ClientAssembly(filepath)); + } + } + else + { + _filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist")); + } } - else + } + foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme)))) + { + var instance = Activator.CreateInstance(type) as ITheme; + foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) { - _filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist")); + var filepath = Path.Combine(binFolder, name + ".dll"); + if (System.IO.File.Exists(filepath)) + { + if (!assemblyList.Exists(item => item.FilePath == filepath)) + { + assemblyList.Insert(0, new ClientAssembly(filepath)); + } + } + else + { + _filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist")); + } } } } - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme)))) - { - var instance = Activator.CreateInstance(type) as ITheme; - foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - var path = Path.Combine(binFolder, name + ".dll"); - if (System.IO.File.Exists(path)) - { - path = Path.GetFileName(AddFileDate(path)); - if (!list.Contains(path)) list.Insert(0, path); - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist")); - } - } - } - } - return list; + return assemblyList; + }); } private byte[] GetAssemblies(string list) @@ -213,14 +219,11 @@ namespace Oqtane.Controllers var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); // get list of assemblies which should be downloaded to client - List assemblies; - if (list == "*") + List assemblies = GetAssemblyList(); + if (list != "*") { - assemblies = GetAssemblyList(); - } - else - { - assemblies = list.Split(',').ToList(); + var filter = list.Split(',').ToList(); + assemblies.RemoveAll(item => !filter.Contains(item.HashedName)); } // create zip file containing assemblies and debug symbols @@ -228,22 +231,21 @@ namespace Oqtane.Controllers { using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) { - foreach (string file in assemblies) + foreach (var assembly in assemblies) { - var filename = RemoveFileDate(file); - if (System.IO.File.Exists(Path.Combine(binFolder, filename))) + if (System.IO.File.Exists(assembly.FilePath)) { - using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(file).Open()) + using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName).Open()) { filestream.CopyTo(entrystream); } } - filename = filename.Replace(".dll", ".pdb"); - if (System.IO.File.Exists(Path.Combine(binFolder, filename))) + var pdb = assembly.FilePath.Replace(".dll", ".pdb"); + if (System.IO.File.Exists(pdb)) { - using (var filestream = new FileStream(Path.Combine(binFolder, filename), FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(file.Replace(".dll", ".pdb")).Open()) + using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open()) { filestream.CopyTo(entrystream); } @@ -255,18 +257,6 @@ namespace Oqtane.Controllers } } - private string AddFileDate(string filepath) - { - DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath); - return Path.GetFileNameWithoutExtension(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath); - } - - private string RemoveFileDate(string filepath) - { - var segments = filepath.Split("."); - return string.Join(".", segments, 0, segments.Length - 2) + Path.GetExtension(filepath); - } - private async Task RegisterContact(string email) { try @@ -292,5 +282,38 @@ namespace Oqtane.Controllers { await RegisterContact(email); } + + public struct ClientAssembly + { + public ClientAssembly(string filepath) + { + FilePath = filepath; + DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath); + HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath); + } + + public string FilePath { get; private set; } + public string HashedName { get; private set; } + } + + private static int GetDeterministicHashCode(string value) + { + unchecked + { + int hash1 = (5381 << 16) + 5381; + int hash2 = hash1; + + for (int i = 0; i < value.Length; i += 2) + { + hash1 = ((hash1 << 5) + hash1) ^ value[i]; + if (i == value.Length - 1) + break; + hash2 = ((hash2 << 5) + hash2) ^ value[i + 1]; + } + + return hash1 + (hash2 * 1566083941); + } + } + } } diff --git a/Oqtane.Server/Controllers/LanguageController.cs b/Oqtane.Server/Controllers/LanguageController.cs index dc63eda8..678b3f6d 100644 --- a/Oqtane.Server/Controllers/LanguageController.cs +++ b/Oqtane.Server/Controllers/LanguageController.cs @@ -42,10 +42,13 @@ namespace Oqtane.Controllers { if (!string.IsNullOrEmpty(packagename)) { - foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) + foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) { var code = Path.GetFileName(Path.GetDirectoryName(file)); - languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false }); + if (!languages.Any(item => item.Code == code)) + { + languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false }); + } } } } @@ -54,7 +57,7 @@ namespace Oqtane.Controllers languages = _languages.GetLanguages(SiteId).ToList(); if (!string.IsNullOrEmpty(packagename)) { - foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}.*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) + foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) { var code = Path.GetFileName(Path.GetDirectoryName(file)); if (languages.Any(item => item.Code == code)) @@ -99,7 +102,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && language.SiteId == _alias.SiteId) { language = _languages.AddLanguage(language); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Create); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language); } else @@ -119,7 +123,8 @@ namespace Oqtane.Controllers if (language != null && language.SiteId == _alias.SiteId) { _languages.DeleteLanguage(id); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Language, language.LanguageId, SyncEventActions.Delete); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id); } else diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 60d23951..1ba4e8ce 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -124,7 +124,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.PageId, PermissionNames.Edit)) { module = _modules.AddModule(module); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Added {Module}", module); } else @@ -174,7 +175,8 @@ namespace Oqtane.Controllers } } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Update); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Updated {Module}", module); } else @@ -195,7 +197,8 @@ namespace Oqtane.Controllers if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) { _modules.DeleteModule(id); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Deleted {ModuleId}", id); } else diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index d7cbd623..fae9a6a0 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -29,10 +29,11 @@ namespace Oqtane.Controllers private readonly IWebHostEnvironment _environment; private readonly IServiceProvider _serviceProvider; private readonly ITenantManager _tenantManager; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ILogManager logger) + public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, ITenantRepository tenants, ISqlRepository sql, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, IServiceProvider serviceProvider, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) { _moduleDefinitions = moduleDefinitions; _tenants = tenants; @@ -42,6 +43,7 @@ namespace Oqtane.Controllers _environment = environment; _serviceProvider = serviceProvider; _tenantManager = tenantManager; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -145,6 +147,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && moduleDefinition.SiteId == _alias.SiteId && _moduleDefinitions.GetModuleDefinition(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId) != null) { _moduleDefinitions.UpdateModuleDefinition(moduleDefinition); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Definition Updated {ModuleDefinition}", moduleDefinition); } else @@ -227,6 +230,7 @@ namespace Oqtane.Controllers // remove module definition _moduleDefinitions.DeleteModuleDefinition(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name); } else diff --git a/Oqtane.Server/Controllers/NotificationController.cs b/Oqtane.Server/Controllers/NotificationController.cs index c4c22f87..dfdba5f1 100644 --- a/Oqtane.Server/Controllers/NotificationController.cs +++ b/Oqtane.Server/Controllers/NotificationController.cs @@ -8,6 +8,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; +using System.Reflection.Metadata; namespace Oqtane.Controllers { @@ -16,13 +17,15 @@ namespace Oqtane.Controllers { private readonly INotificationRepository _notifications; private readonly IUserPermissions _userPermissions; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public NotificationController(INotificationRepository notifications, IUserPermissions userPermissions, ILogManager logger, ITenantManager tenantManager) + public NotificationController(INotificationRepository notifications, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _notifications = notifications; _userPermissions = userPermissions; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -83,6 +86,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId)) { notification = _notifications.AddNotification(notification); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {NotificationId}", notification.NotificationId); } else @@ -102,6 +106,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && notification.SiteId == _alias.SiteId && _notifications.GetNotification(notification.NotificationId, false) != null && IsAuthorized(notification.FromUserId)) { notification = _notifications.UpdateNotification(notification); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId); } else @@ -122,6 +127,7 @@ namespace Oqtane.Controllers if (notification != null && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId))) { _notifications.DeleteNotification(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Notification, notification.NotificationId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id); } else diff --git a/Oqtane.Server/Controllers/PackageController.cs b/Oqtane.Server/Controllers/PackageController.cs index d290d60b..d4ba5c21 100644 --- a/Oqtane.Server/Controllers/PackageController.cs +++ b/Oqtane.Server/Controllers/PackageController.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Oqtane.Models; -using Newtonsoft.Json; using System; using System.Net.Http; using System.Threading.Tasks; @@ -12,6 +11,7 @@ using Oqtane.Shared; using Oqtane.Infrastructure; using Oqtane.Enums; using System.Net.Http.Headers; +using System.Text.Json; // ReSharper disable PartialTypeWithSinglePart namespace Oqtane.Controllers @@ -106,11 +106,7 @@ namespace Oqtane.Controllers var stream = await response.Content.ReadAsStreamAsync(); using (var streamReader = new StreamReader(stream)) { - using (var jsonTextReader = new JsonTextReader(streamReader)) - { - var serializer = new JsonSerializer(); - return serializer.Deserialize(jsonTextReader); - } + return await JsonSerializer.DeserializeAsync(stream, new JsonSerializerOptions(JsonSerializerDefaults.Web)); } } return default(T); diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index 5112924f..92cf36b6 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -143,7 +143,8 @@ namespace Oqtane.Controllers if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions)) { page = _pages.AddPage(page); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Added {Page}", page); if (!page.Path.StartsWith("admin/")) @@ -200,7 +201,8 @@ namespace Oqtane.Controllers page.IsPersonalizable = false; page.UserId = int.Parse(userid); page = _pages.AddPage(page); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Create); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh); // copy modules List pagemodules = _pageModules.GetPageModules(page.SiteId).ToList(); @@ -313,7 +315,8 @@ namespace Oqtane.Controllers } } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Update); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page); } else @@ -353,10 +356,12 @@ namespace Oqtane.Controllers { page.Order = order; _pages.UpdatePage(page); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Update); } order += 2; } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, siteid); + + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, siteid, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Order Updated {SiteId} {PageId} {ParentId}", siteid, pageid, parentid); } else @@ -375,7 +380,8 @@ namespace Oqtane.Controllers if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit)) { _pages.DeletePage(page.PageId); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Delete); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, page.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Deleted {PageId}", page.PageId); } else diff --git a/Oqtane.Server/Controllers/PageModuleController.cs b/Oqtane.Server/Controllers/PageModuleController.cs index 9b396d0d..619b7d77 100644 --- a/Oqtane.Server/Controllers/PageModuleController.cs +++ b/Oqtane.Server/Controllers/PageModuleController.cs @@ -9,6 +9,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; +using Microsoft.AspNetCore.Mvc.RazorPages; namespace Oqtane.Controllers { @@ -75,7 +76,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit)) { pageModule = _pageModules.AddPageModule(pageModule); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Create); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Page Module Added {PageModule}", pageModule); } else @@ -96,7 +98,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _pageModules.GetPageModule(pageModule.PageModuleId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit)) { pageModule = _pageModules.UpdatePageModule(pageModule); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Update); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Updated {PageModule}", pageModule); } else @@ -124,10 +127,11 @@ namespace Oqtane.Controllers { pagemodule.Order = order; _pageModules.UpdatePageModule(pagemodule); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Update); } order += 2; } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Module Order Updated {PageId} {Pane}", pageid, pane); } else @@ -146,7 +150,8 @@ namespace Oqtane.Controllers if (pagemodule != null && pagemodule.Module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pagemodule.PageId, PermissionNames.Edit)) { _pageModules.DeletePageModule(id); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Delete); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Page Module Deleted {PageModuleId}", id); } else diff --git a/Oqtane.Server/Controllers/ProfileController.cs b/Oqtane.Server/Controllers/ProfileController.cs index 9793b1c6..60becc5f 100644 --- a/Oqtane.Server/Controllers/ProfileController.cs +++ b/Oqtane.Server/Controllers/ProfileController.cs @@ -14,12 +14,14 @@ namespace Oqtane.Controllers public class ProfileController : Controller { private readonly IProfileRepository _profiles; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public ProfileController(IProfileRepository profiles, ILogManager logger, ITenantManager tenantManager) + public ProfileController(IProfileRepository profiles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _profiles = profiles; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -66,6 +68,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && profile.SiteId == _alias.SiteId) { profile = _profiles.AddProfile(profile); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Profile Added {Profile}", profile); } else @@ -85,6 +88,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null) { profile = _profiles.UpdateProfile(profile); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Profile Updated {Profile}", profile); } else @@ -105,6 +109,7 @@ namespace Oqtane.Controllers if (profile != null && profile.SiteId == _alias.SiteId) { _profiles.DeleteProfile(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Profile, profile.ProfileId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Profile Deleted {ProfileId}", id); } else diff --git a/Oqtane.Server/Controllers/RoleController.cs b/Oqtane.Server/Controllers/RoleController.cs index b79723ae..b5779500 100644 --- a/Oqtane.Server/Controllers/RoleController.cs +++ b/Oqtane.Server/Controllers/RoleController.cs @@ -14,12 +14,14 @@ namespace Oqtane.Controllers public class RoleController : Controller { private readonly IRoleRepository _roles; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public RoleController(IRoleRepository roles, ILogManager logger, ITenantManager tenantManager) + public RoleController(IRoleRepository roles, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _roles = roles; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -72,6 +74,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && role.SiteId == _alias.SiteId) { role = _roles.AddRole(role); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Role Added {Role}", role); } else @@ -91,6 +94,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null) { role = _roles.UpdateRole(role); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Role Updated {Role}", role); } else @@ -111,6 +115,7 @@ namespace Oqtane.Controllers if (role != null && !role.IsSystem && role.SiteId == _alias.SiteId) { _roles.DeleteRole(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Role, role.RoleId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Role Deleted {RoleId}", id); } else diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 6fb6a443..092c232e 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -98,7 +98,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { setting = _settings.AddSetting(setting); - AddSyncEvent(setting.EntityName); + AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting); } else @@ -117,7 +117,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { setting = _settings.UpdateSetting(setting); - AddSyncEvent(setting.EntityName); + AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting); } else @@ -137,7 +137,7 @@ namespace Oqtane.Controllers if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit)) { _settings.DeleteSetting(setting.EntityName, id); - AddSyncEvent(setting.EntityName); + AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting); } else @@ -277,14 +277,16 @@ namespace Oqtane.Controllers return filter; } - private void AddSyncEvent(string EntityName) + private void AddSyncEvent(string EntityName, int SettingId, string Action) { + _syncManager.AddSyncEvent(_alias.TenantId, EntityName + "Setting", SettingId, Action); + switch (EntityName) { case EntityNames.Module: case EntityNames.Page: case EntityNames.Site: - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); break; } } diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index a2b7f2b7..eb3d6ed1 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -160,6 +160,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid) { site = _sites.AddSite(site); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Create); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Create, "Site Added {Site}", site); } else @@ -180,12 +181,13 @@ namespace Oqtane.Controllers if (ModelState.IsValid && site.SiteId == _alias.SiteId && site.TenantId == _alias.TenantId && current != null) { site = _sites.UpdateSite(site); - bool reload = false; + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Update); + string action = SyncEventActions.Refresh; if (current.Runtime != site.Runtime || current.RenderMode != site.RenderMode) { - reload = true; + action = SyncEventActions.Reload; } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, reload); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, action); _logger.Log(site.SiteId, LogLevel.Information, this, LogFunction.Update, "Site Updated {Site}", site); } else @@ -206,6 +208,7 @@ namespace Oqtane.Controllers if (site != null && site.SiteId == _alias.SiteId) { _sites.DeleteSite(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Site, site.SiteId, SyncEventActions.Delete); _logger.Log(id, LogLevel.Information, this, LogFunction.Delete, "Site Deleted {SiteId}", id); } else diff --git a/Oqtane.Server/Controllers/TenantController.cs b/Oqtane.Server/Controllers/TenantController.cs index 1b01d0f1..d3567fb1 100644 --- a/Oqtane.Server/Controllers/TenantController.cs +++ b/Oqtane.Server/Controllers/TenantController.cs @@ -2,9 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Oqtane.Models; using System.Collections.Generic; -using Oqtane.Enums; using Oqtane.Shared; -using Oqtane.Infrastructure; using Oqtane.Repository; namespace Oqtane.Controllers @@ -13,12 +11,10 @@ namespace Oqtane.Controllers public class TenantController : Controller { private readonly ITenantRepository _tenants; - private readonly ILogManager _logger; - public TenantController(ITenantRepository tenants, ILogManager logger) + public TenantController(ITenantRepository tenants) { _tenants = tenants; - _logger = logger; } // GET: api/ diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index 132b7a78..690db812 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -56,7 +56,7 @@ namespace Oqtane.Controllers { List themes = _themes.GetThemes().ToList(); Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault(); - if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client") + if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId) { // remove theme assets if (_installationManager.UninstallPackage(theme.PackageName)) diff --git a/Oqtane.Server/Controllers/UrlMappingController.cs b/Oqtane.Server/Controllers/UrlMappingController.cs index 9e8c745c..ddfd2ddb 100644 --- a/Oqtane.Server/Controllers/UrlMappingController.cs +++ b/Oqtane.Server/Controllers/UrlMappingController.cs @@ -14,12 +14,14 @@ namespace Oqtane.Controllers public class UrlMappingController : Controller { private readonly IUrlMappingRepository _urlMappings; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager) + public UrlMappingController(IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _urlMappings = urlMappings; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -85,6 +87,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId) { urlMapping = _urlMappings.AddUrlMapping(urlMapping); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping); } else @@ -104,6 +107,7 @@ namespace Oqtane.Controllers if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null) { urlMapping = _urlMappings.UpdateUrlMapping(urlMapping); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Update); _logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping); } else @@ -124,6 +128,7 @@ namespace Oqtane.Controllers if (urlMapping != null && urlMapping.SiteId == _alias.SiteId) { _urlMappings.DeleteUrlMapping(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UrlMapping, urlMapping.UrlMappingId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id); } else diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 5f17f40c..0cfdc502 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -23,7 +23,6 @@ namespace Oqtane.Controllers public class UserController : Controller { private readonly IUserRepository _users; - private readonly IRoleRepository _roles; private readonly IUserRoleRepository _userRoles; private readonly UserManager _identityUserManager; private readonly SignInManager _identitySignInManager; @@ -35,10 +34,9 @@ namespace Oqtane.Controllers private readonly IJwtManager _jwtManager; private readonly ILogManager _logger; - public UserController(IUserRepository users, IRoleRepository roles, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger) + public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger) { _users = users; - _roles = roles; _userRoles = userRoles; _identityUserManager = identityUserManager; _identitySignInManager = identitySignInManager; @@ -165,6 +163,7 @@ namespace Oqtane.Controllers if (allowregistration) { bool succeeded; + string errors = ""; IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); if (identityuser == null) { @@ -174,12 +173,20 @@ namespace Oqtane.Controllers identityuser.EmailConfirmed = verified; var result = await _identityUserManager.CreateAsync(identityuser, user.Password); succeeded = result.Succeeded; + if (!succeeded) + { + errors = string.Join(", ", result.Errors.Select(e => e.Description)); + } } else { var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); succeeded = result.Succeeded; - verified = true; + if (!succeeded) + { + errors = "Password Not Valid For User"; + } + verified = succeeded; } if (succeeded) @@ -187,6 +194,11 @@ namespace Oqtane.Controllers user.LastLoginOn = null; user.LastIPAddress = ""; newUser = _users.AddUser(user); + _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, newUser.UserId, SyncEventActions.Create); + } + else + { + _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {Username} - {Errors}", user.Username, errors); } if (newUser != null) @@ -242,7 +254,8 @@ namespace Oqtane.Controllers await _identityUserManager.UpdateAsync(identityuser); } user = _users.UpdateUser(user); - _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId); + _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update); + _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Refresh); user.Password = ""; // remove sensitive information _logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user); } @@ -297,6 +310,7 @@ namespace Oqtane.Controllers { // delete user _users.DeleteUser(user.UserId); + _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Deleted {UserId}", user.UserId); } else diff --git a/Oqtane.Server/Controllers/UserRoleController.cs b/Oqtane.Server/Controllers/UserRoleController.cs index 7624e7ad..05101732 100644 --- a/Oqtane.Server/Controllers/UserRoleController.cs +++ b/Oqtane.Server/Controllers/UserRoleController.cs @@ -122,9 +122,10 @@ namespace Oqtane.Controllers if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name)) { userRole = _userRoles.AddUserRole(userRole); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Create); _logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userRole); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh); } else { @@ -144,7 +145,8 @@ namespace Oqtane.Controllers if (ModelState.IsValid && role != null && SiteValid(role.SiteId) && RoleValid(role.Name) && _userRoles.GetUserRole(userRole.UserRoleId, false) != null) { userRole = _userRoles.UpdateUserRole(userRole); - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userRole.UserRoleId, SyncEventActions.Update); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userRole.UserId, SyncEventActions.Refresh); _logger.Log(LogLevel.Information, this, LogFunction.Update, "User Role Updated {UserRole}", userRole); } else @@ -165,6 +167,7 @@ namespace Oqtane.Controllers if (userrole != null && SiteValid(userrole.Role.SiteId) && RoleValid(userrole.Role.Name)) { _userRoles.DeleteUserRole(id); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.UserRole, userrole.UserRoleId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "User Role Deleted {UserRole}", userrole); if (userrole.Role.Name == RoleNames.Host) @@ -178,7 +181,7 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Information, this, LogFunction.Create, "User Role Added {UserRole}", userrole); } - _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userrole.UserId); + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.User, userrole.UserId, SyncEventActions.Refresh); } else { diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index fec61c63..ab6987f2 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Net; @@ -157,6 +158,9 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection ConfigureOqtaneAuthenticationOptions(this IServiceCollection services, IConfigurationRoot Configuration) { + // prevent remapping of claims + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + // settings defined in appsettings services.Configure(Configuration); services.Configure(Configuration); @@ -276,7 +280,7 @@ namespace Microsoft.Extensions.DependencyInjection var serviceTypes = assembly.GetTypes(hostedServiceType); foreach (var serviceType in serviceTypes) { - if (serviceType.IsSubclassOf(typeof(HostedServiceBase))) + if (!services.Any(item => item.ServiceType == serviceType)) { services.AddSingleton(hostedServiceType, serviceType); } diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs index 4d28a043..a5d703fd 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs @@ -56,6 +56,10 @@ namespace Oqtane.Extensions options.ClientId = sitesettings.GetValue("ExternalLogin:ClientId", ""); options.ClientSecret = sitesettings.GetValue("ExternalLogin:ClientSecret", ""); options.UsePkce = bool.Parse(sitesettings.GetValue("ExternalLogin:PKCE", "false")); + if (!string.IsNullOrEmpty(sitesettings.GetValue("ExternalLogin:RoleClaimType", ""))) + { + options.TokenValidationParameters.RoleClaimType = sitesettings.GetValue("ExternalLogin:RoleClaimType", ""); + } options.Scope.Clear(); foreach (var scope in sitesettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries)) { @@ -230,6 +234,18 @@ namespace Oqtane.Extensions var identity = await ValidateUser(email, id, claims, context.HttpContext); if (identity.Label == ExternalLoginStatus.Success) { + // external roles + if (!string.IsNullOrEmpty(context.HttpContext.GetSiteSettings().GetValue("ExternalLogin:RoleClaimType", ""))) + { + foreach (var claim in context.Principal.Claims.Where(item => item.Type == ClaimTypes.Role)) + { + if (!identity.Claims.Any(item => item.Type == ClaimTypes.Role && item.Value == claim.Value)) + { + identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value)); + } + } + } + identity.AddClaim(new Claim("access_token", context.SecurityToken.RawData)); context.Principal = new ClaimsPrincipal(identity); } diff --git a/Oqtane.Server/Infrastructure/ConfigManager.cs b/Oqtane.Server/Infrastructure/ConfigManager.cs index a9390912..85278929 100644 --- a/Oqtane.Server/Infrastructure/ConfigManager.cs +++ b/Oqtane.Server/Infrastructure/ConfigManager.cs @@ -1,9 +1,9 @@ using System; using System.Diagnostics; using System.IO; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Oqtane.Shared; namespace Oqtane.Infrastructure @@ -52,9 +52,9 @@ namespace Oqtane.Infrastructure try { var path = Path.Combine(Directory.GetCurrentDirectory(), file); - dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path)); - SetValueRecursively(key, jsonObj, value, "set"); - File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented)); + JsonNode node = JsonNode.Parse(File.ReadAllText(path)); + SetValueRecursively(node, key, value); + File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true })); if (reload) Reload(); } catch (Exception ex) @@ -73,9 +73,9 @@ namespace Oqtane.Infrastructure try { var path = Path.Combine(Directory.GetCurrentDirectory(), file); - dynamic jsonObj = JsonConvert.DeserializeObject(File.ReadAllText(path)); - SetValueRecursively(key, jsonObj, "", "remove"); - File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj, Formatting.Indented)); + JsonNode node = JsonNode.Parse(File.ReadAllText(path)); + RemovePropertyRecursively(node, key); + File.WriteAllText(path, JsonSerializer.Serialize(node, new JsonSerializerOptions() { WriteIndented = true })); if (reload) Reload(); } catch (Exception ex) @@ -84,30 +84,53 @@ namespace Oqtane.Infrastructure } } - private void SetValueRecursively(string key, dynamic jsonObj, T value, string action) + private void SetValueRecursively(JsonNode json, string key, T value) { - var remainingSections = key.Split(":", 2); + if (json != null && key != null && value != null) + { + var remainingSections = key.Split(":", 2); - var currentSection = remainingSections[0]; - if (remainingSections.Length > 1) - { - var nextSection = remainingSections[1]; - jsonObj[currentSection] ??= new JObject(); - SetValueRecursively(nextSection, jsonObj[currentSection], value, action); - } - else - { - switch (action) + var currentSection = remainingSections[0]; + if (remainingSections.Length > 1) { - case "set": - jsonObj[currentSection] = JToken.FromObject(value); - break; - case "remove": - if (jsonObj.Property(currentSection) != null) - { - jsonObj.Property(currentSection).Remove(); - } - break; + var nextSection = remainingSections[1]; + SetValueRecursively(json[currentSection] ??= new JsonObject(), nextSection, value); + } + else + { + if (value.GetType() == typeof(string) && (value.ToString()!.StartsWith("[") || value.ToString()!.StartsWith("{"))) + { + json[currentSection] = JsonNode.Parse(value.ToString()!); + } + else + { + json[currentSection] = JsonValue.Create(value); + } + } + } + } + + private void RemovePropertyRecursively(JsonNode json, string key) + { + if (json != null && key != null) + { + var remainingSections = key.Split(":", 2); + + var currentSection = remainingSections[0]; + if (remainingSections.Length > 1) + { + var nextSection = remainingSections[1]; + if (json[currentSection] != null) + { + RemovePropertyRecursively(json[currentSection], nextSection); + } + } + else + { + if (json.AsObject().ContainsKey(currentSection)) + { + json.AsObject().Remove(currentSection); + } } } } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 8c217265..0889a84c 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -16,7 +16,6 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; using Oqtane.Enums; -using Newtonsoft.Json; using Microsoft.Extensions.Logging; // ReSharper disable MemberCanBePrivate.Global @@ -51,7 +50,6 @@ namespace Oqtane.Infrastructure if (!string.IsNullOrEmpty(_config.GetConnectionString(SettingKeys.ConnectionStringKey))) { - result.Success = true; using (var db = GetInstallationContext()) { if (db.Database.CanConnect()) @@ -60,6 +58,7 @@ namespace Oqtane.Infrastructure { // verify master database contains a Tenant table ( ie. validate schema is properly provisioned ) var provisioned = db.Tenant.Any(); + result.Success = true; } catch (Exception ex) { @@ -715,7 +714,7 @@ namespace Oqtane.Infrastructure foreach (var upgrade in siteupgrades) { var aliasname = upgrade.Key.Split(' ').First(); - // in the future this equality condition could use RegEx to allow for more flexible matching + // in the future this equality condition could use RegEx to allow for more flexible matching if (string.Equals(alias.Name, aliasname, StringComparison.OrdinalIgnoreCase)) { tenantManager.SetTenant(alias.TenantId); @@ -825,7 +824,7 @@ namespace Oqtane.Infrastructure databases += "{ \"Name\": \"MySQL\", \"ControlType\": \"Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.MySQL.SqlServerDatabase, Oqtane.Database.MySQL\" },"; databases += "{ \"Name\": \"PostgreSQL\", \"ControlType\": \"Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client\", \"DBTYpe\": \"Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL\" }"; databases += "]"; - _configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, JsonConvert.DeserializeObject(databases), true); + _configManager.AddOrUpdateSetting(SettingKeys.AvailableDatabasesSection, databases, true); } } } diff --git a/Oqtane.Server/Infrastructure/HostedServices/CacheInvalidationHostedService.cs b/Oqtane.Server/Infrastructure/HostedServices/CacheInvalidationHostedService.cs new file mode 100644 index 00000000..68eade75 --- /dev/null +++ b/Oqtane.Server/Infrastructure/HostedServices/CacheInvalidationHostedService.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using System.Threading; +using Microsoft.Extensions.Hosting; +using Oqtane.Models; +using Microsoft.Extensions.Caching.Memory; +using Oqtane.Shared; + +namespace Oqtane.Infrastructure +{ + public class CacheInvalidationHostedService : IHostedService + { + private readonly ISyncManager _syncManager; + private readonly IMemoryCache _cache; + + public CacheInvalidationHostedService(ISyncManager syncManager, IMemoryCache cache) + { + _syncManager = syncManager; + _cache = cache; + } + + void EntityChanged(object sender, SyncEvent e) + { + if (e.EntityName == "Site" && e.Action == SyncEventActions.Refresh) + { + _cache.Remove($"site:{e.TenantId}:{e.EntityId}"); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _syncManager.EntityChanged += EntityChanged; + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + public void Dispose() + { + _syncManager.EntityChanged -= EntityChanged; + } + } +} diff --git a/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs b/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs index b21d70af..82ec7d26 100644 --- a/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs +++ b/Oqtane.Server/Infrastructure/Interfaces/ISyncManager.cs @@ -6,8 +6,8 @@ namespace Oqtane.Infrastructure { public interface ISyncManager { + event EventHandler EntityChanged; List GetSyncEvents(int tenantId, DateTime lastSyncDate); - void AddSyncEvent(int tenantId, string entityName, int entityId); - void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload); + void AddSyncEvent(int tenantId, string entityName, int entityId, string action); } } diff --git a/Oqtane.Server/Infrastructure/LocalizationManager.cs b/Oqtane.Server/Infrastructure/LocalizationManager.cs index 7faf27ca..8eec7195 100644 --- a/Oqtane.Server/Infrastructure/LocalizationManager.cs +++ b/Oqtane.Server/Infrastructure/LocalizationManager.cs @@ -40,7 +40,7 @@ namespace Oqtane.Infrastructure public string[] GetInstalledCultures() { var cultures = new List(); - foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"Oqtane.Client{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) + foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{Constants.ClientId}{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories)) { cultures.Add(Path.GetFileName(Path.GetDirectoryName(file))); } diff --git a/Oqtane.Server/Infrastructure/SyncManager.cs b/Oqtane.Server/Infrastructure/SyncManager.cs index b5ac1dc5..c6a15dfe 100644 --- a/Oqtane.Server/Infrastructure/SyncManager.cs +++ b/Oqtane.Server/Infrastructure/SyncManager.cs @@ -1,21 +1,19 @@ -using Microsoft.Extensions.Caching.Memory; using Oqtane.Models; using Oqtane.Shared; using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; namespace Oqtane.Infrastructure { public class SyncManager : ISyncManager { - private readonly IMemoryCache _cache; private List SyncEvents { get; set; } - public SyncManager(IMemoryCache cache) + public event EventHandler EntityChanged; + + public SyncManager() { - _cache = cache; SyncEvents = new List(); } @@ -24,20 +22,22 @@ namespace Oqtane.Infrastructure return SyncEvents.Where(item => (item.TenantId == tenantId || item.TenantId == -1) && item.ModifiedOn >= lastSyncDate).ToList(); } - public void AddSyncEvent(int tenantId, string entityName, int entityId) + public void AddSyncEvent(int tenantId, string entityName, int entityId, string action) { - AddSyncEvent(tenantId, entityName, entityId, false); - } + var syncevent = new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Action = action, ModifiedOn = DateTime.UtcNow }; - public void AddSyncEvent(int tenantId, string entityName, int entityId, bool reload) - { - SyncEvents.Add(new SyncEvent { TenantId = tenantId, EntityName = entityName, EntityId = entityId, Reload = reload, ModifiedOn = DateTime.UtcNow }); - if (entityName == EntityNames.Site) -{ - _cache.Remove($"site:{tenantId}:{entityId}"); + // client actions for PageState management + if (action == SyncEventActions.Refresh || action == SyncEventActions.Reload) + { + // trim sync events + SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1)); + + // add sync event + SyncEvents.Add(syncevent); } - // trim sync events - SyncEvents.RemoveAll(item => item.ModifiedOn < DateTime.UtcNow.AddHours(-1)); + + // raise event + EntityChanged?.Invoke(this, syncevent); } } } diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 8ce40fb2..2a108e81 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -57,6 +57,9 @@ namespace Oqtane.Infrastructure case "3.2.0": Upgrade_3_2_0(tenant, scope); break; + case "3.2.1": + Upgrade_3_2_1(tenant, scope); + break; } } } @@ -264,5 +267,41 @@ namespace Oqtane.Infrastructure } } + private void Upgrade_3_2_1(Tenant tenant, IServiceScope scope) + { + try + { + // convert Identifier Claim Type and Email Claim Type + var settingRepository = scope.ServiceProvider.GetRequiredService(); + var siteRepository = scope.ServiceProvider.GetRequiredService(); + foreach (Site site in siteRepository.GetSites().ToList()) + { + var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); + var setting = settings.FirstOrDefault(item => item.SettingName == "ExternalLogin:IdentifierClaimType"); + if (setting != null) + { + if (setting.SettingValue == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier") + { + setting.SettingValue = "sub"; + settingRepository.UpdateSetting(setting); + } + } + setting = settings.FirstOrDefault(item => item.SettingName == "ExternalLogin:EmailClaimType"); + if (setting != null) + { + if (setting.SettingValue == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress") + { + setting.SettingValue = "email"; + settingRepository.UpdateSetting(setting); + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Oqtane Error: Error In 3.2.1 Upgrade Logic - {ex}"); + } + } + } } diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 886290c5..7b53a44e 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net6.0 Debug;Release - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -34,7 +34,6 @@ - diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index b446c4d9..3c78469e 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -23,15 +23,17 @@ namespace Oqtane.Pages private readonly IFileRepository _files; private readonly IUserPermissions _userPermissions; private readonly IUrlMappingRepository _urlMappings; + private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager) + public FilesModel(IWebHostEnvironment environment, IFileRepository files, IUserPermissions userPermissions, IUrlMappingRepository urlMappings, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _environment = environment; _files = files; _userPermissions = userPermissions; _urlMappings = urlMappings; + _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); } @@ -42,6 +44,12 @@ namespace Oqtane.Pages var folderpath = ""; var filename = ""; + bool download = false; + if (Request.Query.ContainsKey("download")) + { + download = true; + } + var segments = path.Split('/'); if (segments.Length > 0) { @@ -52,15 +60,32 @@ namespace Oqtane.Pages } } - var file = _files.GetFile(_alias.SiteId, folderpath, filename); + Models.File file; + if (folderpath == "id/" && int.TryParse(filename, out int fileid)) + { + file = _files.GetFile(fileid, false); + } + else + { + file = _files.GetFile(_alias.SiteId, folderpath, filename); + } + if (file != null) { - if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) + if (file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) { var filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { - return PhysicalFile(filepath, file.GetMimeType()); + if (download) + { + _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, "Download"); + return PhysicalFile(filepath, file.GetMimeType(), file.Name); + } + else + { + return PhysicalFile(filepath, file.GetMimeType()); + } } else { @@ -91,7 +116,7 @@ namespace Oqtane.Pages } // broken link - string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot\\images"), "error.png"); + string errorPath = Path.Combine(Utilities.PathCombine(_environment.ContentRootPath, "wwwroot/images"), "error.png"); return PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)); } } diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index e8de6ad5..488bcff9 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -50,6 +50,10 @@ { } + @if (!string.IsNullOrEmpty(Model.ReconnectScript)) + { + @Html.Raw(Model.ReconnectScript) + } @if (!string.IsNullOrEmpty(Model.PWAScript)) { @Html.Raw(Model.PWAScript) diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 77e215b5..8b3044da 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -20,6 +20,7 @@ using Oqtane.Enums; using Oqtane.Security; using Oqtane.Extensions; using Oqtane.Themes; +using Oqtane.UI; namespace Oqtane.Pages { @@ -69,6 +70,7 @@ namespace Oqtane.Pages public string Meta = ""; public string FavIcon = "favicon.ico"; public string PWAScript = ""; + public string ReconnectScript = ""; public string Message = ""; public IActionResult OnGet() @@ -126,7 +128,11 @@ namespace Oqtane.Pages } if (site.FaviconFileId != null) { - FavIcon = Utilities.ContentUrl(alias, site.FaviconFileId.Value); + FavIcon = Utilities.FileUrl(alias, site.FaviconFileId.Value); + } + if (Runtime == "Server") + { + ReconnectScript = CreateReconnectScript(); } if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) { @@ -174,7 +180,7 @@ namespace Oqtane.Pages { ThemeType = page.ThemeType; } - ProcessThemeResources(ThemeType); + ProcessThemeResources(ThemeType, alias); } else // page not found { @@ -198,7 +204,7 @@ namespace Oqtane.Pages var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); foreach (Assembly assembly in assemblies) { - ProcessHostResources(assembly); + ProcessHostResources(assembly, alias); } // set culture if not specified @@ -374,44 +380,64 @@ namespace Oqtane.Pages private string CreatePWAScript(Alias alias, Site site, Route route) { return - "" + Environment.NewLine + - ""; } - private void ProcessHostResources(Assembly assembly) + private string CreateReconnectScript() + { + return + ""; + } + + private void ProcessHostResources(Assembly assembly, Alias alias) { var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources))); foreach (var type in types) @@ -420,12 +446,12 @@ namespace Oqtane.Pages foreach (var resource in obj.Resources) { resource.Level = ResourceLevel.App; - ProcessResource(resource, 0); + ProcessResource(resource, 0, alias); } } } - private void ProcessThemeResources(string ThemeType) + private void ProcessThemeResources(string ThemeType, Alias alias) { var type = Type.GetType(ThemeType); if (type != null) @@ -437,40 +463,41 @@ namespace Oqtane.Pages foreach (var resource in obj.Resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) { resource.Level = ResourceLevel.Page; - ProcessResource(resource, count++); + ProcessResource(resource, count++, alias); } } } } - private void ProcessResource(Resource resource, int count) + private void ProcessResource(Resource resource, int count, Alias alias) { + var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; switch (resource.ResourceType) { case ResourceType.Stylesheet: - if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) + if (!HeadResources.Contains(url, StringComparison.OrdinalIgnoreCase)) { string id = ""; if (resource.Level == ResourceLevel.Page) { id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; } - HeadResources += "" + Environment.NewLine; + HeadResources += "" + Environment.NewLine; } break; case ResourceType.Script: if (resource.Location == Shared.ResourceLocation.Body) { - if (!BodyResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) + if (!BodyResources.Contains(url, StringComparison.OrdinalIgnoreCase)) { - BodyResources += "" + Environment.NewLine; + BodyResources += "" + Environment.NewLine; } } else { if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) { - HeadResources += "" + Environment.NewLine; + HeadResources += "" + Environment.NewLine; } } break; diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 309f0c09..dac8d85c 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -133,7 +133,6 @@ namespace Oqtane { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }) - .AddNewtonsoftJson() .AddOqtaneApplicationParts() // register any Controllers from custom modules .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes @@ -191,7 +190,7 @@ namespace Oqtane }); // create a global sync event to identify server application startup - sync.AddSyncEvent(-1, "Application", -1, true); + sync.AddSyncEvent(-1, EntityNames.Host, -1, SyncEventActions.Reload); } } } diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak index 530eae4a..b1a41580 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.MySQL.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak index a2fdf20a..2b66afc9 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.PostgreSQL.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak index d7b0953d..4721d0a2 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.SqlServer.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak index 9e7dbc0c..2a754910 100644 Binary files a/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak and b/Oqtane.Server/wwwroot/Packages/Oqtane.Database.Sqlite.nupkg.bak differ diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 8a677952..90c2bda4 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -356,10 +356,15 @@ Oqtane.Interop = { } } }, - refreshBrowser: function (reload, wait) { - setInterval(function () { - window.location.reload(reload); - }, wait * 1000); + refreshBrowser: function (verify, wait) { + async function attemptReload (verify) { + if (verify) { + await fetch(''); + } + window.location.reload(); + } + attemptReload(verify); + setInterval(attemptReload, wait * 1000); }, redirectBrowser: function (url, wait) { setInterval(function () { diff --git a/Oqtane.Shared/Extensions/AssemblyExtensions.cs b/Oqtane.Shared/Extensions/AssemblyExtensions.cs index d2b667ea..7ebd9c68 100644 --- a/Oqtane.Shared/Extensions/AssemblyExtensions.cs +++ b/Oqtane.Shared/Extensions/AssemblyExtensions.cs @@ -79,7 +79,7 @@ namespace System.Reflection { return appDomain.GetOqtaneAssemblies() .Where(a => a.GetTypes().Any() || a.GetTypes().Any() || a.GetTypes().Any()) - .Where(a => Utilities.GetFullTypeName(a.GetName().Name) != "Oqtane.Client"); + .Where(a => Utilities.GetFullTypeName(a.GetName().Name) != Constants.ClientId); } /// diff --git a/Oqtane.Shared/Models/Resource.cs b/Oqtane.Shared/Models/Resource.cs index ce7087e8..fae66050 100644 --- a/Oqtane.Shared/Models/Resource.cs +++ b/Oqtane.Shared/Models/Resource.cs @@ -8,6 +8,8 @@ namespace Oqtane.Models /// public class Resource { + private string _url; + /// /// A so the Interop can properly create `script` or `link` tags /// @@ -16,7 +18,14 @@ namespace Oqtane.Models /// /// Path to the resources. /// - public string Url { get; set; } + public string Url + { + get => _url; + set + { + _url = (value.Contains("://")) ? value : (!value.StartsWith("/") ? "/" : "") + value; + } + } /// /// Integrity checks to increase the security of resources accessed. Especially common in CDN resources. diff --git a/Oqtane.Shared/Models/Sync.cs b/Oqtane.Shared/Models/Sync.cs index 6e58185f..f99c0ffd 100644 --- a/Oqtane.Shared/Models/Sync.cs +++ b/Oqtane.Shared/Models/Sync.cs @@ -9,12 +9,12 @@ namespace Oqtane.Models public List SyncEvents { get; set; } } - public class SyncEvent + public class SyncEvent : EventArgs { public int TenantId { get; set; } public string EntityName { get; set; } public int EntityId { get; set; } - public bool Reload { get; set; } + public string Action { get; set; } public DateTime ModifiedOn { get; set; } } } diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 752b8bf0..39a44605 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net6.0 Debug;Release - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index aabe2ef6..1efceed1 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,9 +4,10 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "3.2.0"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0"; + public static readonly string Version = "3.2.1"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1"; public const string PackageId = "Oqtane.Framework"; + public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; public const string PackageRegistryUrl = "https://www.oqtane.net"; @@ -16,17 +17,12 @@ namespace Oqtane.Shared public const string ContainerComponent = "Oqtane.UI.ContainerBuilder, Oqtane.Client"; public const string DefaultTheme = "Oqtane.Themes.OqtaneTheme.Default, Oqtane.Client"; - [Obsolete("DefaultLayout is deprecated")] - public const string DefaultLayout = ""; public const string DefaultContainer = "Oqtane.Themes.OqtaneTheme.Container, Oqtane.Client"; public const string DefaultAdminContainer = "Oqtane.Themes.AdminContainer, Oqtane.Client"; public const string ActionToken = "{Action}"; public const string DefaultAction = "Index"; - [Obsolete("Use PaneNames.Admin")] - public const string AdminPane = PaneNames.Admin; - public static readonly string[] ReservedRoutes = { "api", "pages", "files" }; public const string ModuleDelimiter = "*"; public const string UrlParametersDelimiter = "!"; @@ -42,29 +38,13 @@ namespace Oqtane.Shared public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server"; - public const string ContentUrl = "/api/file/download/"; + public const string FileUrl = "/files/"; public const string ImageUrl = "/api/file/image/"; public const int UserFolderCapacity = 20; // megabytes public const string PackagesFolder = "Packages"; - [Obsolete("Use UserNames.Host instead.")] - public const string HostUser = UserNames.Host; - - [Obsolete("Use TenantNames.Master instead")] - public const string MasterTenant = TenantNames.Master; public const string DefaultSite = "Default Site"; - const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; - - [Obsolete(RoleObsoleteMessage)] - public const string AllUsersRole = RoleNames.Everyone; - [Obsolete(RoleObsoleteMessage)] - public const string HostRole = RoleNames.Host; - [Obsolete(RoleObsoleteMessage)] - public const string AdminRole = RoleNames.Admin; - [Obsolete(RoleObsoleteMessage)] - public const string RegisteredRole = RoleNames.Registered; - public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png,ico,webp"; public const string UploadableFiles = ImageFiles + ",mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg,csv,json,xml,xslt,rss,html,htm,css"; public const string ReservedDevices = "CON,NUL,PRN,COM0,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT0,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,CONIN$,CONOUT$"; @@ -93,5 +73,33 @@ namespace Oqtane.Shared public static readonly string HttpContextSiteSettingsKey = "SiteSettings"; public static readonly string MauiUserAgent = "MAUI"; + + // Obsolete constants + + const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; + + [Obsolete(RoleObsoleteMessage)] + public const string AllUsersRole = RoleNames.Everyone; + [Obsolete(RoleObsoleteMessage)] + public const string HostRole = RoleNames.Host; + [Obsolete(RoleObsoleteMessage)] + public const string AdminRole = RoleNames.Admin; + [Obsolete(RoleObsoleteMessage)] + public const string RegisteredRole = RoleNames.Registered; + + [Obsolete("DefaultLayout is deprecated")] + public const string DefaultLayout = ""; + + [Obsolete("Use PaneNames.Admin")] + public const string AdminPane = PaneNames.Admin; + + [Obsolete("Use UserNames.Host instead.")] + public const string HostUser = UserNames.Host; + + [Obsolete("Use TenantNames.Master instead")] + public const string MasterTenant = TenantNames.Master; + + // [Obsolete("Use FileUrl instead")] + public const string ContentUrl = "/api/file/download/"; } } diff --git a/Oqtane.Shared/Shared/EntityNames.cs b/Oqtane.Shared/Shared/EntityNames.cs index 2a31e76d..1e480c3b 100644 --- a/Oqtane.Shared/Shared/EntityNames.cs +++ b/Oqtane.Shared/Shared/EntityNames.cs @@ -2,15 +2,25 @@ namespace Oqtane.Shared { public class EntityNames { + public const string Alias = "Alias"; + public const string File = "File"; + public const string Folder = "Folder"; + public const string Job = "Job"; + public const string Language = "Language"; public const string Module = "Module"; public const string ModuleDefinition = "ModuleDefinition"; - public const string PageModule = "PageModule"; - public const string Tenant = "Tenant"; - public const string Site = "Site"; + public const string Notification = "Notification"; public const string Page = "Page"; - public const string Folder = "Folder"; + public const string PageModule = "PageModule"; + public const string Profile = "Profile"; + public const string Role = "Role"; + public const string Setting = "Setting"; + public const string Site = "Site"; + public const string Tenant = "Tenant"; + public const string UrlMapping = "UrlMapping"; public const string User = "User"; + public const string UserRole = "UserRole"; public const string Visitor = "Visitor"; - public const string Host = "Host"; + public const string Host = "Host"; // a conceptual entity } } diff --git a/Oqtane.Shared/Shared/SyncEventActions.cs b/Oqtane.Shared/Shared/SyncEventActions.cs new file mode 100644 index 00000000..c3f9973b --- /dev/null +++ b/Oqtane.Shared/Shared/SyncEventActions.cs @@ -0,0 +1,12 @@ +namespace Oqtane.Shared { + public class SyncEventActions { + // client actions for PageState management + public const string Refresh = "Refresh"; + public const string Reload = "Reload"; + + // server actions for raising Events + public const string Create = "Create"; + public const string Update = "Update"; + public const string Delete = "Delete"; + } +} diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 03a6cabc..34b8e88b 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -98,23 +98,28 @@ namespace Oqtane.Shared return NavigateUrl(alias, path, parameters); } - public static string ContentUrl(Alias alias, int fileId) - { - return ContentUrl(alias, fileId, false); - } - - public static string ContentUrl(Alias alias, int fileId, bool asAttachment) - { - var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; - var method = asAttachment ? "/attach" : ""; - - return $"{alias.BaseUrl}{aliasUrl}{Constants.ContentUrl}{fileId}{method}"; - } - public static string FileUrl(Alias alias, string folderpath, string filename) + { + return FileUrl(alias, folderpath, filename, false); + } + + public static string FileUrl(Alias alias, string folderpath, string filename, bool download) { var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; - return $"{alias.BaseUrl}{aliasUrl}/files/{folderpath.Replace("\\", "/")}{filename}"; + var querystring = (download) ? "?download" : ""; + return $"{alias?.BaseUrl}{aliasUrl}{Constants.FileUrl}{folderpath.Replace("\\", "/")}{filename}{querystring}"; + } + + public static string FileUrl(Alias alias, int fileid) + { + return FileUrl(alias, fileid, false); + } + + public static string FileUrl(Alias alias, int fileid, bool download) + { + var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; + var querystring = (download) ? "?download" : ""; + return $"{alias?.BaseUrl}{aliasUrl}{Constants.FileUrl}id/{fileid}{querystring}"; } public static string ImageUrl(Alias alias, int fileId, int width, int height, string mode) @@ -128,25 +133,30 @@ namespace Oqtane.Shared mode = string.IsNullOrEmpty(mode) ? "crop" : mode; position = string.IsNullOrEmpty(position) ? "center" : position; background = string.IsNullOrEmpty(background) ? "000000" : background; - return $"{alias.BaseUrl}{url}{Constants.ImageUrl}{fileId}/{width}/{height}/{mode}/{position}/{background}/{rotate}/{recreate}"; + return $"{alias?.BaseUrl}{url}{Constants.ImageUrl}{fileId}/{width}/{height}/{mode}/{position}/{background}/{rotate}/{recreate}"; } public static string TenantUrl(Alias alias, string url) { url = (!url.StartsWith("/")) ? "/" + url : url; url = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path + url : url; - return $"{alias.BaseUrl}{url}"; + return $"{alias?.BaseUrl}{url}"; } public static string FormatContent(string content, Alias alias, string operation) { + var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; switch (operation) { case "save": + content = content.Replace(alias?.BaseUrl + aliasUrl + Constants.FileUrl, Constants.FileUrl); + // legacy content = content.Replace(UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString()), "[siteroot]"); content = content.Replace(alias.Path + Constants.ContentUrl, Constants.ContentUrl); break; case "render": + content = content.Replace(Constants.FileUrl, alias?.BaseUrl + aliasUrl + Constants.FileUrl); + // legacy content = content.Replace("[siteroot]", UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString())); content = content.Replace(Constants.ContentUrl, alias.Path + Constants.ContentUrl); break; @@ -491,5 +501,19 @@ namespace Oqtane.Shared return (localDateTime?.Date, localTime); } + [Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)] + public static string ContentUrl(Alias alias, int fileId) + { + return ContentUrl(alias, fileId, false); + } + + [Obsolete("ContentUrl(Alias alias, int fileId, bool asAttachment) is deprecated. Use FileUrl(Alias alias, int fileId, bool download) instead.", false)] + public static string ContentUrl(Alias alias, int fileId, bool asAttachment) + { + var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; + var method = asAttachment ? "/attach" : ""; + + return $"{alias?.BaseUrl}{aliasUrl}{Constants.ContentUrl}{fileId}{method}"; + } } } diff --git a/Oqtane.Test/Oqtane.Test.csproj b/Oqtane.Test/Oqtane.Test.csproj index 8448cfc5..45840dbc 100644 --- a/Oqtane.Test/Oqtane.Test.csproj +++ b/Oqtane.Test/Oqtane.Test.csproj @@ -3,7 +3,7 @@ net6.0 Debug;Release - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 26dbc8f2..b1a4b43e 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net6.0 Exe - 3.2.0 + 3.2.1 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/README.md b/README.md index 903be6fb..cf2aec76 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,15 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane. # Roadmap This project is open source, and therefore is a work in progress... -V.4.0.0 ( Q4 2022 ) +4.0.0 ( Q4 2022 ) - [ ] Migration to .NET 7 - [ ] Folder Providers -V.3.2.0 ( Q3 2022 ) +[3.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0) ( Sep 13, 2022 ) - [x] MAUI / Blazor Hybrid support - [x] Upgrade to Bootstrap 5.2 -[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( June 27, 2022 ) +[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( Jun 27, 2022 ) - [x] Stabilization improvements [3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 ) @@ -58,7 +58,7 @@ V.3.2.0 ( Q3 2022 ) [3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 ) - [x] Stabilization improvements -[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( April 5, 2022 ) +[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( Apr 5, 2022 ) - [x] User account lockout support - [x] Two factor authentication support - [x] Per-site configuration of password complexity, lockout criteria @@ -156,6 +156,8 @@ Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalke # Release Announcements +[Oqtane 3.2](https://www.oqtane.org/blog/!/50/oqtane-3-2-for-net-maui-blazor-hybrid) + [Oqtane 3.1](https://www.oqtane.org/blog/!/41/oqtane-3-1-released) [Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6)