@@ -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)
{
-
+
}
else
{
@@ -19,7 +20,7 @@ else
}
- @if (PageState.User != null)
+ @if (profiles != null && settings != null)
{
@@ -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)
{
}
- @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)
- }
+
+ }
+ @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;
|