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/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 db6c3813..0cddca87 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Add.razor @@ -80,17 +80,18 @@ 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)) + @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/Languages/Index.razor b/Oqtane.Client/Modules/Admin/Languages/Index.razor index b8a3d21d..08fc569b 100644 --- a/Oqtane.Client/Modules/Admin/Languages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Languages/Index.razor @@ -29,9 +29,9 @@ else @if (UpgradeAvailable(context.Code)) - { + { - } + } diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index ea396c7a..3b861320 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -36,18 +36,19 @@ @(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)) + @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/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index b5d23a21..abce34a4 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -22,6 +22,7 @@ else   @SharedLocalizer["Name"] @SharedLocalizer["Version"] + @SharedLocalizer["Expires"]   @@ -34,11 +35,14 @@ else @context.Name @context.Version + + @((MarkupString)PurchaseLink(context.PackageName)) + @if (UpgradeAvailable(context.PackageName, context.Version)) - { + { - } + } @@ -67,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; diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 45c0794d..583b62f2 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/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); } } } 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/Add.razor b/Oqtane.Client/Modules/Admin/Themes/Add.razor index 9b7bd6b2..661d8b62 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Add.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Add.razor @@ -37,17 +37,18 @@ @(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)) + @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/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 5346704e..e656d4c6 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -21,8 +21,9 @@ else
    - @SharedLocalizer["Name"] - @SharedLocalizer["Version"] + @SharedLocalizer["Name"] + @SharedLocalizer["Version"] + @SharedLocalizer["Expires"]  
@@ -35,11 +36,14 @@ else @context.Name @context.Version + + @((MarkupString)PurchaseLink(context.PackageName)) + @if (UpgradeAvailable(context.PackageName, context.Version)) - { + { - } + } @@ -69,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; 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 c0b285b7..464431dd 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -2,45 +2,57 @@ @inherits ModuleControlBase @typeparam TableItem -

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

- @if (_endPage > 1) +
+
  • + UpdateList(_pages))> +
  • +
  • + Page @_page of @_pages +
  • + } - @if (Format == "Table") + @if (Format == "Table" && Row != null) { @@ -58,90 +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) { -
    - @if (_endPage > 1) +
    +
  • + UpdateList(_pages))> +
  • +
  • + Page @_page of @_pages +
  • + } -

    +} @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; } @@ -177,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/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 diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index 9ef78435..86a88e73 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -300,4 +300,13 @@ Review License Terms + + Day Trial + + + Expires + + + Extend + \ No newline at end of file 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/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/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.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 5d7c9c0a..b553573a 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,126 @@ 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) { - new Permission(PermissionNames.Browse, user.UserId, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, user.UserId, true), - }.EncodePermissions(), - }); + 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, + 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(), + }); + } } } } - } - 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.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 447346e7..1a57e7bc 100644 --- a/Oqtane.Shared/Models/Package.cs +++ b/Oqtane.Shared/Models/Package.cs @@ -67,14 +67,28 @@ namespace Oqtane.Models /// public int Vulnerabilities { get; set; } + #region Commercial Properties + /// /// The price of the package /// - public decimal Price { get; set; } + public decimal? Price { get; set; } /// - /// The Url for purchasing the package ( if commercial ) + /// The Url for purchasing the package /// public string PaymentUrl { get; set; } + + /// + /// The trial period in days + /// + public int TrialPeriod { get; set; } + + /// + /// 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 db4e3e94..aad5849a 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) @@ -108,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;