From 07165ce68d266576e0155416de2da59cdb6e01c9 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 3 Sep 2021 15:24:51 -0400 Subject: [PATCH 1/7] add support for trial periods --- .../Modules/Admin/Languages/Add.razor | 1 + .../Modules/Admin/Languages/Index.razor | 25 ++++++++++++++-- .../Modules/Admin/ModuleDefinitions/Add.razor | 3 +- .../Admin/ModuleDefinitions/Index.razor | 29 ++++++++++++++++--- Oqtane.Client/Modules/Admin/Themes/Add.razor | 1 + .../Modules/Admin/Themes/Index.razor | 29 ++++++++++++++++--- Oqtane.Client/Resources/SharedResources.resx | 3 ++ Oqtane.Shared/Models/Package.cs | 5 ++++ 8 files changed, 85 insertions(+), 11 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index db6c3813..505793e8 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -80,6 +80,7 @@ else @(String.Format("{0:n0}", context.Downloads)) @SharedLocalizer["Search.Downloads"]  |   @SharedLocalizer["Search.Released"]: @context.ReleaseDate.ToString("MMM dd, yyyy")  |   @SharedLocalizer["Search.Version"]: @context.Version + @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index b8a3d21d..1bca6f2c 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -29,9 +29,13 @@ else @if (UpgradeAvailable(context.Code)) - { + { - } + } + else + { + @((MarkupString)PurchaseLink(context.Code)) + } @@ -91,6 +95,23 @@ else return upgradeavailable; } + private string PurchaseLink(string code) + { + string link = ""; + if (_packages != null) + { + var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault(); + if (package != null) + { + if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) + { + link = "" + package.Price.ToString("$#,##0.00") + ""; + } + } + } + return link; + } + private async Task DownloadLanguage(string code) { try diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index ea396c7a..07a1d059 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -36,7 +36,8 @@ @(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)
@(String.Format("{0:n0}", context.Downloads)) @SharedLocalizer["Search.Downloads"]  |   @SharedLocalizer["Search.Released"]: @context.ReleaseDate.ToString("MMM dd, yyyy")  |   - @SharedLocalizer["Search.Version"]: @context.Version + @SharedLocalizer["Search.Version"]: @context.Version + @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index b5d23a21..4b6cfca9 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -28,17 +28,21 @@ else @if (context.AssemblyName != "Oqtane.Client") - { + { - } + } @context.Name @context.Version @if (UpgradeAvailable(context.PackageName, context.Version)) - { + { - } + } + else + { + @((MarkupString)PurchaseLink(context.PackageName)) + } @@ -82,6 +86,23 @@ else return upgradeavailable; } + private string PurchaseLink(string packagename) + { + string link = ""; + if (!string.IsNullOrEmpty(packagename) && _packages != null) + { + var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); + if (package != null) + { + if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) + { + link = "" + package.Price.ToString("$#,##0.00") + ""; + } + } + } + return link; + } + private async Task DownloadModule(string packagename, string version) { try diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 9b7bd6b2..37bf4c14 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -37,6 +37,7 @@ @(String.Format("{0:n0}", context.Downloads)) @SharedLocalizer["Search.Downloads"]  |   @SharedLocalizer["Search.Released"]: @context.ReleaseDate.ToString("MMM dd, yyyy")  |   @SharedLocalizer["Search.Version"]: @context.Version + @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 5346704e..f04e77aa 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -29,17 +29,21 @@ else @if (context.AssemblyName != "Oqtane.Client") - { + { - } + } @context.Name @context.Version @if (UpgradeAvailable(context.PackageName, context.Version)) - { + { - } + } + else + { + @((MarkupString)PurchaseLink(context.PackageName)) + } @@ -99,6 +103,23 @@ else } } + private string PurchaseLink(string packagename) + { + string link = ""; + if (!string.IsNullOrEmpty(packagename) && _packages != null) + { + var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); + if (package != null) + { + if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) + { + link = "" + package.Price.ToString("$#,##0.00") + ""; + } + } + } + return link; + } + private async Task DeleteTheme(Theme Theme) { try diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index 9ef78435..ca38d971 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -300,4 +300,7 @@ Review License Terms + + Day Trial + \ No newline at end of file diff --git a/Oqtane.Shared/Models/Package.cs b/Oqtane.Shared/Models/Package.cs index 447346e7..83450574 100644 --- a/Oqtane.Shared/Models/Package.cs +++ b/Oqtane.Shared/Models/Package.cs @@ -76,5 +76,10 @@ namespace Oqtane.Models /// The Url for purchasing the package ( if commercial ) /// public string PaymentUrl { get; set; } + + /// + /// The trial period in days ( if commercial ) + /// + public int TrialPeriod { get; set; } } } From 3cd724975001847ba7c669644c32abba1f154d1e Mon Sep 17 00:00:00 2001 From: Leigh Date: Wed, 8 Sep 2021 08:08:24 +0200 Subject: [PATCH 2/7] Page create - Recycle Bin Check After Delete Page, Cant create page of same name #1645 issue. Added check and message if the page is in the recycle bin. --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 11 +++++++++++ Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx | 3 +++ 2 files changed, 14 insertions(+) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 45c0794d..806ef585 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -322,6 +322,12 @@ } } + if(!PagePathIsDeleted(page.Path, page.SiteId, _pageList)) + { + AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning); + return; + } + if (!PagePathIsUnique(page.Path, page.SiteId, _pageList)) { AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning); @@ -412,4 +418,9 @@ { return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath); } + + private static bool PagePathIsDeleted(string pagePath, int siteId, List existingPages) + { + return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true); + } } diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx index 51d01dc9..5f2f31e5 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx @@ -228,4 +228,7 @@ Appearance + + A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again. + \ No newline at end of file From 53e5728ad22d29cc04f47fc0d50a44ae9ad68f47 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 10 Sep 2021 08:24:05 -0400 Subject: [PATCH 3/7] fix #1640 to resolve issue with server prerendering, upgrade Installer to Bootstrap5, add more defensive logic and logging to DatabaseManager, fix file logger issue, update Pager to use Bootstrap5 pagination, add expiry date support for commercial extensions --- Oqtane.Client/App.razor | 27 +-- Oqtane.Client/Installer/Installer.razor | 3 +- .../Modules/Admin/Languages/Index.razor | 21 -- .../Admin/ModuleDefinitions/Index.razor | 50 ++--- .../Modules/Admin/SystemInfo/Index.razor | 22 --- .../Modules/Admin/Themes/Index.razor | 54 +++--- Oqtane.Client/Modules/Controls/Pager.razor | 94 ++++++--- Oqtane.Client/Resources/SharedResources.resx | 6 + .../Infrastructure/DatabaseManager.cs | 180 ++++++++++-------- .../Infrastructure/Logging/FileLogger.cs | 2 +- Oqtane.Shared/Models/Package.cs | 7 +- 11 files changed, 257 insertions(+), 209 deletions(-) diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor index 01022680..ccb64327 100644 --- a/Oqtane.Client/App.razor +++ b/Oqtane.Client/App.razor @@ -33,23 +33,26 @@ private PageState PageState { get; set; } + protected override async Task OnParametersSetAsync() + { + _installation = await InstallationService.IsInstalled(); + if (_installation.Alias != null) + { + SiteState.Alias = _installation.Alias; + } + else + { + _installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name"; + } + _initialized = true; + } + protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender && !_initialized) + if (firstRender) { var interop = new Interop(JSRuntime); SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken); - _installation = await InstallationService.IsInstalled(); - if (_installation.Alias != null) - { - SiteState.Alias = _installation.Alias; - } - else - { - _installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name"; - } - _initialized = true; - StateHasChanged(); } } diff --git a/Oqtane.Client/Installer/Installer.razor b/Oqtane.Client/Installer/Installer.razor index 45ad895a..7c656e13 100644 --- a/Oqtane.Client/Installer/Installer.razor +++ b/Oqtane.Client/Installer/Installer.razor @@ -150,7 +150,8 @@ if (firstRender) { var interop = new Interop(JSRuntime); - await interop.IncludeLink("app-stylesheet", "stylesheet", "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", "text/css", "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", "anonymous", ""); + await interop.IncludeLink("", "stylesheet", "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", "text/css", "sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC", "anonymous", ""); + await interop.IncludeScript("", "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", "sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM", "anonymous", "", "head", ""); } } diff --git a/Oqtane.Client/Modules/Admin/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index 1bca6f2c..08fc569b 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -32,10 +32,6 @@ else { } - else - { - @((MarkupString)PurchaseLink(context.Code)) - } @@ -95,23 +91,6 @@ else return upgradeavailable; } - private string PurchaseLink(string code) - { - string link = ""; - if (_packages != null) - { - var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault(); - if (package != null) - { - if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) - { - link = "" + package.Price.ToString("$#,##0.00") + ""; - } - } - } - return link; - } - private async Task DownloadLanguage(string code) { try diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index 4b6cfca9..abce34a4 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -22,27 +22,27 @@ else   @SharedLocalizer["Name"] @SharedLocalizer["Version"] + @SharedLocalizer["Expires"]   @if (context.AssemblyName != "Oqtane.Client") - { + { - } + } @context.Name @context.Version + + @((MarkupString)PurchaseLink(context.PackageName)) + @if (UpgradeAvailable(context.PackageName, context.Version)) { } - else - { - @((MarkupString)PurchaseLink(context.PackageName)) - } @@ -71,6 +71,27 @@ else } } + private string PurchaseLink(string packagename) + { + string link = ""; + if (!string.IsNullOrEmpty(packagename) && _packages != null) + { + var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); + if (package != null) + { + if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date) + { + link += "" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + ""; + if (!string.IsNullOrEmpty(package.PaymentUrl)) + { + link += "  " + SharedLocalizer["Extend"] + ""; + } + } + } + } + return link; + } + private bool UpgradeAvailable(string packagename, string version) { var upgradeavailable = false; @@ -86,23 +107,6 @@ else return upgradeavailable; } - private string PurchaseLink(string packagename) - { - string link = ""; - if (!string.IsNullOrEmpty(packagename) && _packages != null) - { - var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); - if (package != null) - { - if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) - { - link = "" + package.Price.ToString("$#,##0.00") + ""; - } - } - } - return link; - } - private async Task DownloadModule(string packagename, string version) { try diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index 257a690d..9cd52ff2 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -44,12 +44,6 @@ -
-
-
-
@Localizer["Register"] -
-


@@ -196,20 +190,4 @@ await logger.LogError(ex, "Error Restarting Application"); } } - - private async Task RegisterChecked(ChangeEventArgs e) - { - try - { - if ((bool)e.Value) - { - await InstallationService.RegisterAsync(PageState.User.Email); - AddModuleMessage(Localizer["Success.Register"], MessageType.Success); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error On Register"); - } - } } \ No newline at end of file diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index f04e77aa..e656d4c6 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -21,29 +21,29 @@ else
    - @SharedLocalizer["Name"] - @SharedLocalizer["Version"] + @SharedLocalizer["Name"] + @SharedLocalizer["Version"] + @SharedLocalizer["Expires"]  
@if (context.AssemblyName != "Oqtane.Client") - { + { - } + } @context.Name @context.Version + + @((MarkupString)PurchaseLink(context.PackageName)) + @if (UpgradeAvailable(context.PackageName, context.Version)) { } - else - { - @((MarkupString)PurchaseLink(context.PackageName)) - } @@ -73,6 +73,27 @@ else } } + private string PurchaseLink(string packagename) + { + string link = ""; + if (!string.IsNullOrEmpty(packagename) && _packages != null) + { + var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); + if (package != null) + { + if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date) + { + link += "" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + ""; + if (!string.IsNullOrEmpty(package.PaymentUrl)) + { + link += "  " + SharedLocalizer["Extend"] + ""; + } + } + } + } + return link; + } + private bool UpgradeAvailable(string packagename, string version) { var upgradeavailable = false; @@ -103,23 +124,6 @@ else } } - private string PurchaseLink(string packagename) - { - string link = ""; - if (!string.IsNullOrEmpty(packagename) && _packages != null) - { - var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); - if (package != null) - { - if (package.Price > 0 && !string.IsNullOrEmpty(package.PaymentUrl)) - { - link = "" + package.Price.ToString("$#,##0.00") + ""; - } - } - } - return link; - } - private async Task DeleteTheme(Theme Theme) { try diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index c0b285b7..3f128785 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -5,40 +5,63 @@

@if (Toolbar == "Top") { -

+
+ } @if (Format == "Table") { @@ -74,40 +97,63 @@ } @if (Toolbar == "Bottom") { -
+
+ }

diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index ca38d971..86a88e73 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -303,4 +303,10 @@ Day Trial + + Expires + + + Extend + \ No newline at end of file diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 5d7c9c0a..1b8ec468 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -250,6 +250,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { result.Message = ex.Message; + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } return result; @@ -288,6 +289,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { result.Message = ex.Message; + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } else @@ -328,6 +330,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { result.Message = ex.Message; + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } } } @@ -432,6 +435,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { result.Message = ex.Message; + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); } // execute any version specific upgrade logic @@ -539,6 +543,10 @@ namespace Oqtane.Infrastructure { result.Success = true; } + else + { + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); + } return result; } @@ -549,110 +557,124 @@ namespace Oqtane.Infrastructure if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases) && !string.IsNullOrEmpty(install.SiteName)) { - using (var scope = _serviceScopeFactory.CreateScope()) + try { - // set the alias explicitly so the tenant can be resolved - var aliases = scope.ServiceProvider.GetRequiredService(); - var firstAlias = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; - var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); - var tenantManager = scope.ServiceProvider.GetRequiredService(); - tenantManager.SetAlias(alias); - - var sites = scope.ServiceProvider.GetRequiredService(); - var site = sites.GetSites().FirstOrDefault(item => item.Name == install.SiteName); - if (site == null) + using (var scope = _serviceScopeFactory.CreateScope()) { - var tenants = scope.ServiceProvider.GetRequiredService(); - var users = scope.ServiceProvider.GetRequiredService(); - var roles = scope.ServiceProvider.GetRequiredService(); - var userRoles = scope.ServiceProvider.GetRequiredService(); - var folders = scope.ServiceProvider.GetRequiredService(); - var log = scope.ServiceProvider.GetRequiredService(); - var identityUserManager = scope.ServiceProvider.GetRequiredService>(); + // set the alias explicitly so the tenant can be resolved + var aliases = scope.ServiceProvider.GetRequiredService(); + var firstAlias = install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0]; + var alias = aliases.GetAliases().FirstOrDefault(item => item.Name == firstAlias); + var tenantManager = scope.ServiceProvider.GetRequiredService(); + tenantManager.SetAlias(alias); - var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName); - - site = new Site + var sites = scope.ServiceProvider.GetRequiredService(); + var site = sites.GetSites().FirstOrDefault(item => item.Name == install.SiteName); + if (site == null) { - TenantId = tenant.TenantId, - Name = install.SiteName, - LogoFileId = null, - DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme, - DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer, - AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer, - SiteTemplateType = install.SiteTemplate - }; - site = sites.AddSite(site); + var tenants = scope.ServiceProvider.GetRequiredService(); + var users = scope.ServiceProvider.GetRequiredService(); + var roles = scope.ServiceProvider.GetRequiredService(); + var userRoles = scope.ServiceProvider.GetRequiredService(); + var folders = scope.ServiceProvider.GetRequiredService(); + var log = scope.ServiceProvider.GetRequiredService(); + var identityUserManager = scope.ServiceProvider.GetRequiredService>(); - if (!string.IsNullOrEmpty(install.HostUsername)) - { - var identityUser = identityUserManager.FindByNameAsync(install.HostUsername).GetAwaiter().GetResult(); - if (identityUser == null) + var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName); + + site = new Site { - identityUser = new IdentityUser { UserName = install.HostUsername, Email = install.HostEmail, EmailConfirmed = true }; - var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult(); - if (create.Succeeded) + TenantId = tenant.TenantId, + Name = install.SiteName, + LogoFileId = null, + DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme, + DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer, + AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer, + SiteTemplateType = install.SiteTemplate + }; + site = sites.AddSite(site); + + if (!string.IsNullOrEmpty(install.HostUsername)) + { + var identityUser = identityUserManager.FindByNameAsync(install.HostUsername).GetAwaiter().GetResult(); + if (identityUser == null) { - var user = new User + identityUser = new IdentityUser { UserName = install.HostUsername, Email = install.HostEmail, EmailConfirmed = true }; + var create = identityUserManager.CreateAsync(identityUser, install.HostPassword).GetAwaiter().GetResult(); + if (create.Succeeded) { - SiteId = site.SiteId, - Username = install.HostUsername, - Password = install.HostPassword, - Email = install.HostEmail, - DisplayName = install.HostName, - LastIPAddress = "", - LastLoginOn = null - }; - - user = users.AddUser(user); - var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0; - var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null }; - userRoles.AddUserRole(userRole); - - // add user folder - var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString())); - if (folder != null) - { - folders.AddFolder(new Folder + var user = new User { - SiteId = folder.SiteId, - ParentId = folder.FolderId, - Name = "My Folder", - Type = FolderTypes.Private, - Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()), - Order = 1, - IsSystem = true, - Permissions = new List + SiteId = site.SiteId, + Username = install.HostUsername, + Password = install.HostPassword, + Email = install.HostEmail, + DisplayName = install.HostName, + LastIPAddress = "", + LastLoginOn = null + }; + + user = users.AddUser(user); + var hostRoleId = roles.GetRoles(user.SiteId, true).FirstOrDefault(item => item.Name == RoleNames.Host)?.RoleId ?? 0; + var userRole = new UserRole { UserId = user.UserId, RoleId = hostRoleId, EffectiveDate = null, ExpiryDate = null }; + userRoles.AddUserRole(userRole); + + // add user folder + var folder = folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString())); + if (folder != null) + { + folders.AddFolder(new Folder + { + SiteId = folder.SiteId, + ParentId = folder.FolderId, + Name = "My Folder", + Type = FolderTypes.Private, + Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()), + Order = 1, + IsSystem = true, + Permissions = new List { new Permission(PermissionNames.Browse, user.UserId, true), new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.Edit, user.UserId, true), }.EncodePermissions(), - }); + }); + } } } } - } - foreach (var aliasName in install.Aliases.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)) - { - alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName); - if (alias != null) + foreach (var aliasName in install.Aliases.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - alias.SiteId = site.SiteId; - aliases.UpdateAlias(alias); + alias = aliases.GetAliases().FirstOrDefault(item => item.Name == aliasName); + if (alias != null) + { + alias.SiteId = site.SiteId; + aliases.UpdateAlias(alias); + } } + + tenant.Version = Constants.Version; + tenants.UpdateTenant(tenant); + + if (site != null) log.Log(site.SiteId, Shared.LogLevel.Information, this, LogFunction.Create, "Site Created {Site}", site); } - - tenant.Version = Constants.Version; - tenants.UpdateTenant(tenant); - - if (site != null) log.Log(site.SiteId, Shared.LogLevel.Information, this, LogFunction.Create, "Site Created {Site}", site); } } + catch (Exception ex) + { + result.Message = "An Error Occurred Creating Site - " + ex.Message; + } } - result.Success = true; + if (string.IsNullOrEmpty(result.Message)) + { + result.Success = true; + } + else + { + _filelogger.LogError(Utilities.LogMessage(this, result.Message)); + } return result; } diff --git a/Oqtane.Server/Infrastructure/Logging/FileLogger.cs b/Oqtane.Server/Infrastructure/Logging/FileLogger.cs index d793e631..91f1f948 100644 --- a/Oqtane.Server/Infrastructure/Logging/FileLogger.cs +++ b/Oqtane.Server/Infrastructure/Logging/FileLogger.cs @@ -55,7 +55,7 @@ namespace Oqtane.Infrastructure var filepath = Path.Combine(folder, "error.log"); // only retain an error log for the current day as it is intended for development purposes - if (File.GetCreationTime(filepath).ToUniversalTime().Date < DateTime.UtcNow.Date && File.Exists(filepath)) + if (File.Exists(filepath) && File.GetLastWriteTimeUtc(filepath).Date < DateTime.UtcNow.Date) { File.Delete(filepath); } diff --git a/Oqtane.Shared/Models/Package.cs b/Oqtane.Shared/Models/Package.cs index 83450574..859bd4ab 100644 --- a/Oqtane.Shared/Models/Package.cs +++ b/Oqtane.Shared/Models/Package.cs @@ -68,7 +68,7 @@ namespace Oqtane.Models public int Vulnerabilities { get; set; } /// - /// The price of the package + /// The price of the package ( if commercial ) /// public decimal Price { get; set; } @@ -81,5 +81,10 @@ namespace Oqtane.Models /// The trial period in days ( if commercial ) /// public int TrialPeriod { get; set; } + + /// + /// The expiry date of the package ( if commercial ) + /// + public DateTime? ExpiryDate { get; set; } } } From d2fa8902f91bb276101e7ab8aa65db7b9d6a10b2 Mon Sep 17 00:00:00 2001 From: Leigh Date: Fri, 10 Sep 2021 18:59:23 +0200 Subject: [PATCH 4/7] Auto stash before rebase of "origin/RecycleCheck" correction --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 806ef585..583b62f2 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -322,7 +322,7 @@ } } - if(!PagePathIsDeleted(page.Path, page.SiteId, _pageList)) + if(PagePathIsDeleted(page.Path, page.SiteId, _pageList)) { AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning); return; @@ -421,6 +421,6 @@ private static bool PagePathIsDeleted(string pagePath, int siteId, List existingPages) { - return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true); + return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true); } } From 14fbc3a5b47db11d3efb49d877f21b7d75b1015d Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Fri, 10 Sep 2021 13:12:00 -0400 Subject: [PATCH 5/7] fix #1647 - module reordering on page issue --- .../Controls/Container/ModuleActionsBase.cs | 30 +++++++++---------- Oqtane.Client/UI/ContainerBuilder.razor | 2 +- Oqtane.Shared/Shared/Utilities.cs | 10 +++++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs index edf5b8e4..3739ce5e 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs @@ -96,7 +96,7 @@ namespace Oqtane.Themes.Controls { PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); - string url = NavigateUrl(); + string url = NavigateUrl(true); if (action.Action != null) { @@ -115,7 +115,7 @@ namespace Oqtane.Themes.Controls await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane); - return NavigateUrl(url, true); + return url; } private async Task DeleteModule(string url, PageModule pagemodule) @@ -123,7 +123,7 @@ namespace Oqtane.Themes.Controls pagemodule.IsDeleted = true; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return NavigateUrl(url, true); + return url; } private async Task Settings(string url, PageModule pagemodule) @@ -133,7 +133,7 @@ namespace Oqtane.Themes.Controls return url; } - private async Task Publish(string s, PageModule pagemodule) + private async Task Publish(string url, PageModule pagemodule) { var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); foreach (var permissionstring in permissions) @@ -148,10 +148,10 @@ namespace Oqtane.Themes.Controls } pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); await ModuleService.UpdateModuleAsync(pagemodule.Module); - return NavigateUrl(s, true); + return url; } - private async Task Unpublish(string s, PageModule pagemodule) + private async Task Unpublish(string url, PageModule pagemodule) { var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); foreach (var permissionstring in permissions) @@ -166,39 +166,39 @@ namespace Oqtane.Themes.Controls } pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); await ModuleService.UpdateModuleAsync(pagemodule.Module); - return NavigateUrl(s, true); + return url; } - private async Task MoveTop(string s, PageModule pagemodule) + private async Task MoveTop(string url, PageModule pagemodule) { pagemodule.Order = 0; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return NavigateUrl(s, true); + return url; } - private async Task MoveBottom(string s, PageModule pagemodule) + private async Task MoveBottom(string url, PageModule pagemodule) { pagemodule.Order = int.MaxValue; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return NavigateUrl(s, true); + return url; } - private async Task MoveUp(string s, PageModule pagemodule) + private async Task MoveUp(string url, PageModule pagemodule) { pagemodule.Order -= 3; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return NavigateUrl(s, true); + return url; } - private async Task MoveDown(string s, PageModule pagemodule) + private async Task MoveDown(string url, PageModule pagemodule) { pagemodule.Order += 3; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - return NavigateUrl(s, true); + return url; } public class ActionViewModel diff --git a/Oqtane.Client/UI/ContainerBuilder.razor b/Oqtane.Client/UI/ContainerBuilder.razor index a48dfe0f..98d8702f 100644 --- a/Oqtane.Client/UI/ContainerBuilder.razor +++ b/Oqtane.Client/UI/ContainerBuilder.razor @@ -1,6 +1,6 @@ @namespace Oqtane.UI - + @if (_useadminborder) {
diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index db4e3e94..1c7845fa 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -1,6 +1,7 @@ using Oqtane.Models; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; using System.Linq; @@ -60,13 +61,16 @@ namespace Oqtane.Shared string urlparameters; string querystring; string anchor; - (urlparameters, querystring, anchor) = ParseParameters(parameters); + // parse parameters + (urlparameters, querystring, anchor) = ParseParameters(parameters); if (!string.IsNullOrEmpty(urlparameters)) { if (urlparameters.StartsWith("/")) urlparameters = urlparameters.Remove(0, 1); path += $"/{Constants.UrlParametersDelimiter}/{urlparameters}"; } + + // build url var uriBuilder = new UriBuilder { Path = !string.IsNullOrEmpty(alias) @@ -76,9 +80,9 @@ namespace Oqtane.Shared : $"{path}", Query = querystring, }; + anchor = string.IsNullOrEmpty(anchor) ? "" : "#" + anchor; - var navigateUrl = uriBuilder.Uri.PathAndQuery + anchor; - return navigateUrl; + return uriBuilder.Uri.PathAndQuery + anchor; } public static string EditUrl(string alias, string path, int moduleid, string action, string parameters) From f21b70a51e5b98396b5341acdc1e04a54a71d554 Mon Sep 17 00:00:00 2001 From: Grayson Walker Date: Sat, 11 Sep 2021 18:18:23 -0400 Subject: [PATCH 6/7] roles validation --- Oqtane.Client/Modules/Admin/Roles/Add.razor | 4 +- Oqtane.Client/Modules/Admin/Roles/Edit.razor | 4 +- Oqtane.Client/Modules/Admin/Roles/Users.razor | 200 ++++++++++-------- 3 files changed, 116 insertions(+), 92 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Roles/Add.razor b/Oqtane.Client/Modules/Admin/Roles/Add.razor index c7daf524..f20a721d 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Add.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Add.razor @@ -22,7 +22,7 @@
- @@ -72,7 +72,7 @@ } else { - AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning); + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } diff --git a/Oqtane.Client/Modules/Admin/Roles/Edit.razor b/Oqtane.Client/Modules/Admin/Roles/Edit.razor index 50eec71b..b217acce 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Edit.razor @@ -22,7 +22,7 @@
- @@ -101,7 +101,7 @@ } else { - AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning); + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } } diff --git a/Oqtane.Client/Modules/Admin/Roles/Users.razor b/Oqtane.Client/Modules/Admin/Roles/Users.razor index 57ea8656..92015116 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Users.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Users.razor @@ -12,65 +12,70 @@ else { -
-
- -
- +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+

+ + @SharedLocalizer["Cancel"] +
+
+

+ +

+ @Localizer["Users"] + @Localizer["Effective"] + @Localizer["Expiry"] +   +
+ + @context.User.DisplayName + @context.EffectiveDate + @context.ExpiryDate + + + + + +

+
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-

- - @SharedLocalizer["Cancel"] -
-
-

- -

- @Localizer["Users"] - @Localizer["Effective"] - @Localizer["Expiry"] -   -
- - @context.User.DisplayName - @context.EffectiveDate - @context.ExpiryDate - - - - - -

-
-
+ } @code { + private ElementReference form; + private bool validated = false; + private int roleid; private string name = string.Empty; private List users; @@ -118,59 +123,78 @@ else private async Task SaveUserRole() { - try + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) { - if (userid != -1) + try { - var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); - if (userrole != null) + if (userid != -1) { - userrole.EffectiveDate = effectivedate; - userrole.ExpiryDate = expirydate; - await UserRoleService.UpdateUserRoleAsync(userrole); + var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); + if (userrole != null) + { + userrole.EffectiveDate = effectivedate; + userrole.ExpiryDate = expirydate; + await UserRoleService.UpdateUserRoleAsync(userrole); + } + else + { + userrole = new UserRole(); + userrole.UserId = userid; + userrole.RoleId = roleid; + userrole.EffectiveDate = effectivedate; + userrole.ExpiryDate = expirydate; + + await UserRoleService.AddUserRoleAsync(userrole); + } + + await logger.LogInformation("User Assigned To Role {UserRole}", userrole); + AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); + await GetUserRoles(); + StateHasChanged(); } else { - userrole = new UserRole(); - userrole.UserId = userid; - userrole.RoleId = roleid; - userrole.EffectiveDate = effectivedate; - userrole.ExpiryDate = expirydate; - - await UserRoleService.AddUserRoleAsync(userrole); + AddModuleMessage(Localizer["Message.Required.UserSelect"], MessageType.Warning); } - - await logger.LogInformation("User Assigned To Role {UserRole}", userrole); - AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); - await GetUserRoles(); - StateHasChanged(); } - else + catch (Exception ex) { - AddModuleMessage(Localizer["Message.Required.UserSelect"], MessageType.Warning); + await logger.LogError(ex, "Error Saving User Roles {RoleId} {Error}", roleid, ex.Message); + AddModuleMessage(Localizer["Error.User.SaveRole"], MessageType.Error); } } - catch (Exception ex) + + else { - await logger.LogError(ex, "Error Saving User Roles {RoleId} {Error}", roleid, ex.Message); - AddModuleMessage(Localizer["Error.User.SaveRole"], MessageType.Error); + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } private async Task DeleteUserRole(int UserRoleId) { - try + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) { - await UserRoleService.DeleteUserRoleAsync(UserRoleId); - await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId); - AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success); - await GetUserRoles(); - StateHasChanged(); + try + { + await UserRoleService.DeleteUserRoleAsync(UserRoleId); + await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId); + AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success); + await GetUserRoles(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message); + AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error); + } } - catch (Exception ex) + else { - await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message); - AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error); + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } } From 898b908c1b82445a157f7d0b6a9a97eb43e38a58 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 15 Sep 2021 08:02:55 -0400 Subject: [PATCH 7/7] Added support for File descriptions, Folder capacity and image sizes. Added image resizing capability using ImageSharp - implemented in user profile. Added parameter to disable image preview in FileManager component. Overhauled Pager component and added Columns parameter for Grid mode. Populated PageState.User.IsAuthenticated in SiteRouter. Added support for zero price commercial extentions. --- .../Modules/Admin/Files/Details.razor | 9 + Oqtane.Client/Modules/Admin/Files/Edit.razor | 21 +- .../Modules/Admin/Languages/Add.razor | 6 +- .../Modules/Admin/ModuleDefinitions/Add.razor | 6 +- Oqtane.Client/Modules/Admin/Themes/Add.razor | 6 +- .../Modules/Admin/UserProfile/Index.razor | 37 +- .../Modules/Controls/FileManager.razor | 5 +- Oqtane.Client/Modules/Controls/Pager.razor | 330 +++++++++--------- Oqtane.Client/Modules/ModuleBase.cs | 5 + .../Modules/Admin/Files/Details.resx | 6 + .../Resources/Modules/Admin/Files/Edit.resx | 12 + .../Themes/Controls/Theme/ControlPanel.razor | 6 +- Oqtane.Client/Themes/ThemeBase.cs | 5 + Oqtane.Client/UI/SiteRouter.razor | 1 + Oqtane.Server/Controllers/FileController.cs | 193 ++++++++-- Oqtane.Server/Controllers/UserController.cs | 6 +- .../Infrastructure/DatabaseManager.cs | 12 +- .../Tenant/02030001_AddFolderCapacity.cs | 40 +++ Oqtane.Server/Repository/SiteRepository.cs | 6 +- Oqtane.Server/wwwroot/js/interop.js | 2 +- Oqtane.Shared/Models/File.cs | 5 + Oqtane.Shared/Models/Folder.cs | 12 +- Oqtane.Shared/Models/Package.cs | 18 +- Oqtane.Shared/Models/User.cs | 9 + Oqtane.Shared/Shared/Constants.cs | 2 + Oqtane.Shared/Shared/Utilities.cs | 6 + 26 files changed, 525 insertions(+), 241 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/02030001_AddFolderCapacity.cs diff --git a/Oqtane.Client/Modules/Admin/Files/Details.razor b/Oqtane.Client/Modules/Admin/Files/Details.razor index f1caba10..56eca8c7 100644 --- a/Oqtane.Client/Modules/Admin/Files/Details.razor +++ b/Oqtane.Client/Modules/Admin/Files/Details.razor @@ -27,6 +27,12 @@
+
+ +
+ +
+
@@ -49,6 +55,7 @@ private string _name; private List _folders; private int _folderId = -1; + private string _description = string.Empty; private int _size; private string _createdBy; private DateTime _createdOn; @@ -70,6 +77,7 @@ { _name = file.Name; _folderId = file.FolderId; + _description = file.Description; _size = file.Size; _createdBy = file.CreatedBy; _createdOn = file.CreatedOn; @@ -97,6 +105,7 @@ File file = await FileService.GetFileAsync(_fileId); file.Name = _name; file.FolderId = _folderId; + file.Description = _description; file = await FileService.UpdateFileAsync(file); await logger.LogInformation("File Saved {File}", file); NavigationManager.NavigateTo(NavigateUrl()); diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index 56692be3..4f53443f 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -28,7 +28,7 @@
- +
@@ -47,6 +47,18 @@ }
+
+ +
+ +
+
+
+ +
+ +
+
@@ -84,6 +96,8 @@ private int _parentId = -1; private string _name; private string _type = FolderTypes.Private; + private string _imagesizes = string.Empty; + private string _capacity = "0"; private bool _isSystem; private string _permissions = string.Empty; private string _createdBy; @@ -114,6 +128,8 @@ _parentId = folder.ParentId ?? -1; _name = folder.Name; _type = folder.Type; + _imagesizes = folder.ImageSizes; + _capacity = folder.Capacity.ToString(); _isSystem = folder.IsSystem; _permissions = folder.Permissions; _createdBy = folder.CreatedBy; @@ -125,7 +141,6 @@ else { _parentId = _folders[0].FolderId; - _permissions = string.Empty; } } catch (Exception ex) @@ -178,6 +193,8 @@ folder.Name = _name; folder.Type = _type; + folder.ImageSizes = _imagesizes; + folder.Capacity = int.Parse(_capacity); folder.IsSystem = _isSystem; folder.Permissions = _permissionGrid.GetPermissions(); diff --git a/Oqtane.Client/Modules/Admin/Languages/Add.razor b/Oqtane.Client/Modules/Admin/Languages/Add.razor index 505793e8..0cddca87 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -83,15 +83,15 @@ else @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl)) { } - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PaymentUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) { - @context.Price.ToString("$#,##0.00") + @context.Price.Value.ToString("$#,##0.00") } else { diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index 07a1d059..3b861320 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -40,15 +40,15 @@ @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl)) { } - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PaymentUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) { - @context.Price.ToString("$#,##0.00") + @context.Price.Value.ToString("$#,##0.00") } else { diff --git a/Oqtane.Client/Modules/Admin/Themes/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 37bf4c14..661d8b62 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -40,15 +40,15 @@ @((MarkupString)(context.TrialPeriod > 0 ? "  |  " + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "" : "")) - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PackageUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl)) { } - @if (context.Price > 0 && !string.IsNullOrEmpty(context.PaymentUrl)) + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) { - @context.Price.ToString("$#,##0.00") + @context.Price.Value.ToString("$#,##0.00") } else { diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index d6c7f38c..d5771d47 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -6,12 +6,13 @@ @inject ISettingService SettingService @inject INotificationService NotificationService @inject IFileService FileService +@inject IFolderService FolderService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @if (PageState.User != null && photo != null) { - @displayname + @displayname } else { @@ -19,7 +20,7 @@ else } - @if (PageState.User != null) + @if (profiles != null && settings != null) {
@@ -55,7 +56,7 @@ else
- +
@@ -67,8 +68,6 @@ else @if (profiles != null && settings != null) { - -
@foreach (Profile profile in profiles) @@ -132,11 +131,11 @@ else {
-   -   - @Localizer["From"] - @Localizer["Subject"] - @Localizer["Received"] +   +   + @Localizer["From"] + @Localizer["Subject"] + @Localizer["Received"]
@@ -165,11 +164,11 @@ else {
-   -   - @Localizer["To"] - @Localizer["Subject"] - @Localizer["Sent"] +   +   + @Localizer["To"] + @Localizer["Subject"] + @Localizer["Sent"]
@@ -210,6 +209,7 @@ else private string email = string.Empty; private string displayname = string.Empty; private FileManager filemanager; + private int folderid = -1; private int photofileid = -1; private File photo = null; private List profiles; @@ -230,6 +230,13 @@ else email = PageState.User.Email; displayname = PageState.User.DisplayName; + // get user folder + var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); + if (folder != null) + { + folderid = folder.FolderId; + } + if (PageState.User.PhotoFileId != null) { photofileid = PageState.User.PhotoFileId.Value; diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index 5aa5c428..d454a0de 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -59,7 +59,7 @@
}
- @if (_image != string.Empty) + @if (_image != string.Empty && ShowImage) {
@((MarkupString) _image) @@ -110,6 +110,9 @@ [Parameter] public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true + [Parameter] + public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true + [Parameter] public int FileId { get; set; } = -1; // optional - for setting a specific file by default diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index 3f128785..464431dd 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -2,68 +2,57 @@ @inherits ModuleControlBase @typeparam TableItem -

- @if (Toolbar == "Top") +@if (ItemList != null) +{ + @if (Toolbar == "Top" && _pages > 0 && Items.Count() > _maxItems) {

} - @if (Format == "Table") + @if (Format == "Table" && Row != null) { @@ -81,113 +70,125 @@
} - @if (Format == "Grid") + @if (Format == "Grid" && Row != null) { + int count = 0; + if (ItemList != null) + { + count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns; + }
-
@Header
- @foreach (var item in ItemList) + @if (Header != null) { -
@Row(item)
- @if (Detail != null) - { -
@Detail(item)
- } +
@Header
+ } + @for (int row = 0; row < (count / _columns); row++) + { +
+ @for (int col = 0; col < _columns; col++) + { + int index = (row * _columns) + col; + if (index < ItemList.Count()) + { +
@Row(ItemList.ElementAt(index))
+ } + else + { +
 
+ } + } +
}
} - @if (Toolbar == "Bottom") + @if (Toolbar == "Bottom" && _pages > 0 && Items.Count() > _maxItems) { } -

+} @code { private int _pages = 0; private int _page = 1; private int _maxItems = 10; - private int _maxPages = 5; + private int _displayPages = 5; private int _startPage = 0; private int _endPage = 0; + private int _columns = 1; [Parameter] - public string Format { get; set; } + public string Format { get; set; } // Table or Grid [Parameter] - public string Toolbar { get; set; } + public string Toolbar { get; set; } // Top or Bottom [Parameter] - public RenderFragment Header { get; set; } + public RenderFragment Header { get; set; } = null; [Parameter] - public RenderFragment Row { get; set; } + public RenderFragment Row { get; set; } = null; [Parameter] - public RenderFragment Detail { get; set; } + public RenderFragment Detail { get; set; } = null; // only applicable to Table layouts [Parameter] - public IEnumerable Items { get; set; } + public IEnumerable Items { get; set; } // the IEnumerable data source [Parameter] - public string PageSize { get; set; } + public string PageSize { get; set; } // number of items to display on a page [Parameter] - public string DisplayPages { get; set; } + public string Columns { get; set; } // only applicable to Grid layouts + + [Parameter] + public string CurrentPage { get; set; } // optional property to set the initial page to display + + [Parameter] + public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection [Parameter] public string Class { get; set; } @@ -223,86 +224,89 @@ _maxItems = int.Parse(PageSize); } - if (!string.IsNullOrEmpty(DisplayPages)) + if (!string.IsNullOrEmpty(Columns)) { - _maxPages = int.Parse(DisplayPages); + _columns = int.Parse(Columns); + } + + if (!string.IsNullOrEmpty(DisplayPages)) + { + _displayPages = int.Parse(DisplayPages); + } + + if (!string.IsNullOrEmpty(CurrentPage)) + { + _page = int.Parse(CurrentPage); + } + else + { + _page = 1; } - _page = 1; _startPage = 0; _endPage = 0; if (Items != null) { - ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); _pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems); + if (_page > _pages) + { + _page = _pages; + } + ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems); + SetPagerSize(); } - - SetPagerSize("forward"); } - public void UpdateList(int currentPage) + public void SetPagerSize() { - ItemList = Items.Skip((currentPage - 1) * _maxItems).Take(_maxItems); - _page = currentPage; - + _startPage = ((_page - 1) / _displayPages) * _displayPages + 1; + _endPage = _startPage + _displayPages - 1; + if (_endPage > _pages) + { + _endPage = _pages; + } StateHasChanged(); } - public void SetPagerSize(string direction) + public void UpdateList(int page) { - if (direction == "forward") - { - if (_endPage + 1 < _pages) - { - _startPage = _endPage + 1; - } - else - { - _startPage = 1; - } + ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems); + _page = page; + SetPagerSize(); + } - if (_endPage + _maxPages < _pages) - { - _endPage = _startPage + _maxPages - 1; - } - else - { - _endPage = _pages; - } - - StateHasChanged(); - } - else if (direction == "back") + public void SkipPages(string direction) + { + switch (direction) { - _endPage = _startPage - 1; - _startPage = _startPage - _maxPages; + case "forward": + _page = _endPage + 1; + break; + case "back": + _page = _startPage - 1; + break; } + + SetPagerSize(); } public void NavigateToPage(string direction) { - if (direction == "next") + switch (direction) { - if (_page < _pages) - { - if (_page == _endPage) + case "next": + if (_page < _pages) { - SetPagerSize("forward"); + _page += 1; } - _page += 1; - } - } - else if (direction == "previous") - { - if (_page > 1) - { - if (_page == _startPage) + break; + case "previous": + if (_page > 1) { - SetPagerSize("back"); + _page -= 1; } - _page -= 1; - } + break; } UpdateList(_page); diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 7a6aad03..8db02254 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -134,6 +134,11 @@ namespace Oqtane.Modules return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); } + public string ImageUrl(int fileid, string size, string mode) + { + return Utilities.ImageUrl(PageState.Alias, fileid, size, mode); + } + public virtual Dictionary GetUrlParameters(string parametersTemplate = "") { var urlParameters = new Dictionary(); diff --git a/Oqtane.Client/Resources/Modules/Admin/Files/Details.resx b/Oqtane.Client/Resources/Modules/Admin/Files/Details.resx index 95bac50f..84ddc423 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Files/Details.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Files/Details.resx @@ -144,4 +144,10 @@ Size: + + A description of the file. This can be used as a caption for image files. + + + Description: + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx index b2740256..81d6a5a6 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx @@ -171,4 +171,16 @@ Type: + + Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited. + + + Capacity: + + + Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x) + + + Image Sizes: + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index 8ca0abf7..d6e76bd8 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -400,17 +400,17 @@ await PageModuleService.AddPageModuleAsync(pageModule); await PageModuleService.UpdatePageModuleOrderAsync(pageModule.PageId, pageModule.Pane); - Message = $"
{Localizer["Success.Page.ModuleAdd"]}
"; + Message = $"
{Localizer["Success.Page.ModuleAdd"]}
"; NavigationManager.NavigateTo(NavigateUrl()); } else { - Message = $"
{Localizer["Message.Require.ModuleSelect"]}
"; + Message = $"
{Localizer["Message.Require.ModuleSelect"]}
"; } } else { - Message = $"
{Localizer["Error.Authorize.No"]}
"; + Message = $"
{Localizer["Error.Authorize.No"]}
"; } } diff --git a/Oqtane.Client/Themes/ThemeBase.cs b/Oqtane.Client/Themes/ThemeBase.cs index 158321f9..2ab10f7b 100644 --- a/Oqtane.Client/Themes/ThemeBase.cs +++ b/Oqtane.Client/Themes/ThemeBase.cs @@ -103,5 +103,10 @@ namespace Oqtane.Themes { return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); } + + public string ImageUrl(int fileid, string size, string mode) + { + return Utilities.ImageUrl(PageState.Alias, fileid, size, mode); + } } } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 0c4217b7..fa91c338 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -138,6 +138,7 @@ if (authState.User.Identity.IsAuthenticated) { user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId); + user.IsAuthenticated = authState.User.Identity.IsAuthenticated; } } else diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 18d38597..efccfa95 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -17,6 +17,9 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Extensions; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -71,7 +74,7 @@ namespace Oqtane.Controllers { foreach (string file in Directory.GetFiles(folder)) { - files.Add(new Models.File {Name = Path.GetFileName(file), Extension = Path.GetExtension(file)?.Replace(".", "")}); + files.Add(new Models.File { Name = Path.GetFileName(file), Extension = Path.GetExtension(file)?.Replace(".", "") }); } } } @@ -169,7 +172,11 @@ namespace Oqtane.Controllers string filepath = _files.GetFilePath(file); if (System.IO.File.Exists(filepath)) { - System.IO.File.Delete(filepath); + // remove file and thumbnails + foreach(var f in Directory.GetFiles(Path.GetDirectoryName(filepath), Path.GetFileNameWithoutExtension(filepath) + ".*")) + { + System.IO.File.Delete(f); + } } _logger.Log(LogLevel.Information, this, LogFunction.Delete, "File Deleted {File}", file); @@ -194,7 +201,7 @@ namespace Oqtane.Controllers folder = _folders.GetFolder(FolderId); } - if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.Permissions)) + if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.Permissions)) { string folderPath = _folders.GetFolderPath(folder); CreateDirectory(folderPath); @@ -226,7 +233,11 @@ namespace Oqtane.Controllers } client.DownloadFile(url, targetPath); - file = _files.AddFile(CreateFile(filename, folder.FolderId, targetPath)); + file = CreateFile(filename, folder.FolderId, targetPath); + if (file != null) + { + file = _files.AddFile(file); + } } catch { @@ -235,7 +246,7 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {FolderId} {Url}", folderid, url); + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Download Attempt {FolderId} {Url}", folderid, url); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } @@ -244,16 +255,16 @@ namespace Oqtane.Controllers // POST api//upload [HttpPost("upload")] - public async Task UploadFile(string folder, IFormFile file) + public async Task UploadFile(string folder, IFormFile formfile) { - if (file.Length <= 0) + if (formfile.Length <= 0) { return; } - if (!file.FileName.IsPathOrFileValid()) + if (!formfile.FileName.IsPathOrFileValid()) { - HttpContext.Response.StatusCode = (int) HttpStatusCode.Conflict; + HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict; return; } @@ -280,20 +291,24 @@ namespace Oqtane.Controllers if (!string.IsNullOrEmpty(folderPath)) { CreateDirectory(folderPath); - using (var stream = new FileStream(Path.Combine(folderPath, file.FileName), FileMode.Create)) + using (var stream = new FileStream(Path.Combine(folderPath, formfile.FileName), FileMode.Create)) { - await file.CopyToAsync(stream); + await formfile.CopyToAsync(stream); } - string upload = await MergeFile(folderPath, file.FileName); + string upload = await MergeFile(folderPath, formfile.FileName); if (upload != "" && FolderId != -1) { - _files.AddFile(CreateFile(upload, FolderId, Path.Combine(folderPath, upload))); + var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload)); + if (file != null) + { + _files.AddFile(file); + } } } else { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, file); + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } @@ -479,6 +494,99 @@ namespace Oqtane.Controllers return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; } + [HttpGet("image/{id}/{size}/{mode?}")] + public IActionResult GetImage(int id, string size, string mode) + { + var file = _files.GetFile(id); + if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) + { + if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower())) + { + var filepath = _files.GetFilePath(file); + if (System.IO.File.Exists(filepath)) + { + size = size.ToLower(); + mode = (string.IsNullOrEmpty(mode)) ? "crop" : mode; + if ((_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.Permissions) || + size.Contains("x") && !string.IsNullOrEmpty(file.Folder.ImageSizes) && file.Folder.ImageSizes.ToLower().Split(",").Contains(size)) + && Enum.TryParse(mode, true, out ResizeMode resizemode)) + { + var imagepath = CreateImage(filepath, size, resizemode.ToString()); + if (!string.IsNullOrEmpty(imagepath)) + { + return PhysicalFile(imagepath, file.GetMimeType()); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, "Error Creating Image For File {File} {Size}", file, size); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Invalid Image Size For Folder Or Invalid Mode Specification {Folder} {Size} {Mode}", file.Folder, size, mode); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Is Not An Image {File}", file); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Access Attempt {FileId}", id); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + + string errorPath = Path.Combine(GetFolderPath("images"), "error.png"); + return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null; + } + + private string CreateImage(string filepath, string size, string mode) + { + string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + size + "." + mode.ToLower() + ".png"); + + if (!System.IO.File.Exists(imagepath)) + { + try + { + FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); + using (Image image = Image.Load(stream)) + { + var parts = size.Split('x'); + int width = (!string.IsNullOrEmpty(parts[0])) ? int.Parse(parts[0]) : 0; + int height = (!string.IsNullOrEmpty(parts[1])) ? int.Parse(parts[1]) : 0; + Enum.TryParse(mode, true, out ResizeMode resizemode); + + image.Mutate(x => + x.Resize(new ResizeOptions + { + Size = new Size(width, height), + Mode = resizemode + }) + .BackgroundColor(new Rgba32(255, 255, 255, 0))); + + image.Save(imagepath, new PngEncoder()); + } + stream.Close(); + } + catch // error creating image + { + imagepath = ""; + } + } + + return imagepath; + } + private string GetFolderPath(string folder) { return Utilities.PathCombine(_environment.ContentRootPath, folder); @@ -489,7 +597,7 @@ namespace Oqtane.Controllers if (!Directory.Exists(folderpath)) { string path = ""; - var separators = new char[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries); foreach (string folder in folders) { @@ -504,25 +612,52 @@ namespace Oqtane.Controllers private Models.File CreateFile(string filename, int folderid, string filepath) { - Models.File file = new Models.File(); - file.Name = filename; - file.FolderId = folderid; + Models.File file = null; + + int size = 0; + var folder = _folders.GetFolder(folderid); + if (folder.Capacity != 0) + { + foreach (var f in _files.GetFiles(folderid)) + { + size += f.Size; + } + } FileInfo fileinfo = new FileInfo(filepath); - file.Extension = fileinfo.Extension.ToLower().Replace(".", ""); - file.Size = (int) fileinfo.Length; - file.ImageHeight = 0; - file.ImageWidth = 0; - - if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower())) + if (folder.Capacity == 0 || ((size + fileinfo.Length) / 1000000) < folder.Capacity) { - FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); - using (Image image = Image.Load(stream)) + file = new Models.File(); + file.Name = filename; + file.FolderId = folderid; + + file.Extension = fileinfo.Extension.ToLower().Replace(".", ""); + file.Size = (int)fileinfo.Length; + file.ImageHeight = 0; + file.ImageWidth = 0; + + if (Constants.ImageFiles.Split(',').Contains(file.Extension.ToLower())) { - file.ImageHeight = image.Height; - file.ImageWidth = image.Width; + try + { + FileStream stream = new FileStream(filepath, FileMode.Open, FileAccess.Read); + using (Image image = Image.Load(stream)) + { + file.ImageHeight = image.Height; + file.ImageWidth = image.Width; + } + stream.Close(); + } + catch + { + // error opening image file + } } - stream.Close(); + } + else + { + System.IO.File.Delete(filepath); + _logger.Log(LogLevel.Warning, this, LogFunction.Create, "File Exceeds Folder Capacity {Folder} {File}", folder, filepath); } return file; diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 78bb4e53..6e92bc15 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -187,8 +187,10 @@ namespace Oqtane.Controllers ParentId = folder.FolderId, Name = "My Folder", Type = FolderTypes.Private, - Path = Utilities.PathCombine(folder.Path, newUser.UserId.ToString(),Path.DirectorySeparatorChar.ToString()), + Path = Utilities.PathCombine(folder.Path, newUser.UserId.ToString(), Path.DirectorySeparatorChar.ToString()), Order = 1, + ImageSizes = "", + Capacity = Constants.UserFolderCapacity, IsSystem = true, Permissions = new List { @@ -196,7 +198,7 @@ namespace Oqtane.Controllers new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.Edit, newUser.UserId, true) }.EncodePermissions() - }); + }) ; } } } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 1b8ec468..b553573a 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -631,13 +631,15 @@ namespace Oqtane.Infrastructure Type = FolderTypes.Private, Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()), Order = 1, + ImageSizes = "", + Capacity = Constants.UserFolderCapacity, IsSystem = true, Permissions = new List - { - new Permission(PermissionNames.Browse, user.UserId, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, user.UserId, true), - }.EncodePermissions(), + { + new Permission(PermissionNames.Browse, user.UserId, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, user.UserId, true), + }.EncodePermissions(), }); } } diff --git a/Oqtane.Server/Migrations/Tenant/02030001_AddFolderCapacity.cs b/Oqtane.Server/Migrations/Tenant/02030001_AddFolderCapacity.cs new file mode 100644 index 00000000..3658598c --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/02030001_AddFolderCapacity.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.02.03.00.01")] + public class AddFolderCapacity : MultiDatabaseMigration + { + public AddFolderCapacity(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.AddIntegerColumn("Capacity", true); + folderEntityBuilder.UpdateColumn("Capacity", "0"); + folderEntityBuilder.UpdateColumn("Capacity", Constants.UserFolderCapacity.ToString(), $"{ActiveDatabase.RewriteName("Name")} = 'My Folder'"); + folderEntityBuilder.AddStringColumn("ImageSizes", 512, true, true); + + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.AddStringColumn("Description", 512, true, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.DropColumn("ImageSizes"); + folderEntityBuilder.DropColumn("Capacity"); + + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.DropColumn("Description"); + } + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index c47f3977..6200bc63 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -124,7 +124,7 @@ namespace Oqtane.Repository Folder folder = _folderRepository.AddFolder(new Folder { - SiteId = site.SiteId, ParentId = null, Name = "Root", Type = FolderTypes.Private, Path = "", Order = 1, IsSystem = true, + SiteId = site.SiteId, ParentId = null, Name = "Root", Type = FolderTypes.Private, Path = "", Order = 1, ImageSizes = "", Capacity = 0, IsSystem = true, Permissions = new List { new Permission(PermissionNames.Browse, RoleNames.Admin, true), @@ -132,7 +132,7 @@ namespace Oqtane.Repository new Permission(PermissionNames.Edit, RoleNames.Admin, true) }.EncodePermissions() }); - _folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = Utilities.PathCombine("Public", Path.DirectorySeparatorChar.ToString()), Order = 1, IsSystem = false, + _folderRepository.AddFolder(new Folder { SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Public", Type = FolderTypes.Public, Path = Utilities.PathCombine("Public", Path.DirectorySeparatorChar.ToString()), Order = 1, ImageSizes = "", Capacity = 0, IsSystem = false, Permissions = new List { new Permission(PermissionNames.Browse, RoleNames.Admin, true), @@ -142,7 +142,7 @@ namespace Oqtane.Repository }); _folderRepository.AddFolder(new Folder { - SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = Utilities.PathCombine("Users",Path.DirectorySeparatorChar.ToString()), Order = 3, IsSystem = true, + SiteId = site.SiteId, ParentId = folder.FolderId, Name = "Users", Type = FolderTypes.Private, Path = Utilities.PathCombine("Users",Path.DirectorySeparatorChar.ToString()), Order = 3, ImageSizes = "", Capacity = 0, IsSystem = true, Permissions = new List { new Permission(PermissionNames.Browse, RoleNames.Admin, true), diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 6d1b549d..5042005c 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -333,7 +333,7 @@ Oqtane.Interop = { var data = new FormData(); data.append('folder', folder); - data.append('file', Chunk, FileName); + data.append('formfile', Chunk, FileName); var request = new XMLHttpRequest(); request.open('POST', posturl, true); request.upload.onloadstart = function (e) { diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs index efba620a..4830e60e 100644 --- a/Oqtane.Shared/Models/File.cs +++ b/Oqtane.Shared/Models/File.cs @@ -50,6 +50,11 @@ namespace Oqtane.Models /// public int ImageWidth { get; set; } + /// + /// Description of a file + /// + public string Description { get; set; } + #region IAuditable Properties /// diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index ebe80584..d5e26b68 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -45,7 +45,17 @@ namespace Oqtane.Models public int Order { get; set; } /// - /// TODO: unclear what this is for + /// List of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x) + /// + public string ImageSizes { get; set; } + + /// + /// Maximum folder capacity (in bytes) + /// + public int Capacity { get; set; } + + /// + /// Folder is a dependency of the framework and cannot be modified or removed /// public bool IsSystem { get; set; } diff --git a/Oqtane.Shared/Models/Package.cs b/Oqtane.Shared/Models/Package.cs index 859bd4ab..1a57e7bc 100644 --- a/Oqtane.Shared/Models/Package.cs +++ b/Oqtane.Shared/Models/Package.cs @@ -67,24 +67,28 @@ namespace Oqtane.Models /// public int Vulnerabilities { get; set; } - /// - /// The price of the package ( if commercial ) - /// - public decimal Price { get; set; } + #region Commercial Properties /// - /// The Url for purchasing the package ( if commercial ) + /// The price of the package + /// + public decimal? Price { get; set; } + + /// + /// The Url for purchasing the package /// public string PaymentUrl { get; set; } /// - /// The trial period in days ( if commercial ) + /// The trial period in days /// public int TrialPeriod { get; set; } /// - /// The expiry date of the package ( if commercial ) + /// The expiry date of the package /// public DateTime? ExpiryDate { get; set; } + + #endregion } } diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index 3a389810..8e7a83af 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -88,5 +88,14 @@ namespace Oqtane.Models /// [NotMapped] public bool IsAuthenticated { get; set; } + + /// + /// The path name of the user's personal folder + /// + [NotMapped] + public string FolderPath + { + get => "Users\\" + UserId.ToString() + "\\"; + } } } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 27e975d3..d4300434 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -40,6 +40,8 @@ namespace Oqtane.Shared { public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server"; public const string ContentUrl = "/api/file/download/"; + public const string ImageUrl = "/api/file/image/"; + public const int UserFolderCapacity = 20; // megabytes [Obsolete("Use UserNames.Host instead.")] public const string HostUser = UserNames.Host; diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 1c7845fa..aad5849a 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -112,6 +112,12 @@ namespace Oqtane.Shared return $"{aliasUrl}{Constants.ContentUrl}{fileId}{method}"; } + public static string ImageUrl(Alias alias, int fileId, string size, string mode) + { + var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; + return $"{aliasUrl}{Constants.ImageUrl}{fileId}/{size}/{mode}"; + } + public static string TenantUrl(Alias alias, string url) { url = (!url.StartsWith("/")) ? "/" + url : url;