diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index c5590d51..9a89bce8 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -54,6 +54,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index ec8881b5..49ac137a 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -15,22 +15,34 @@
- - } + + } + else + { + + + }
- + @if (_isSystem) + { + + } + else + { + + }
@@ -229,7 +241,6 @@ if (folder != null) { - await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); await logger.LogInformation("Folder Saved {Folder}", folder); NavigationManager.NavigateTo(NavigateUrl()); } @@ -265,17 +276,9 @@ } if (!isparent) { - var files = await FileService.GetFilesAsync(_folderId); - if (files.Count == 0) - { - await FolderService.DeleteFolderAsync(_folderId); - await logger.LogInformation("Folder Deleted {Folder}", _folderId); - NavigationManager.NavigateTo(NavigateUrl()); - } - else - { - AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning); - } + await FolderService.DeleteFolderAsync(_folderId); + await logger.LogInformation("Folder Deleted {Folder}", _folderId); + NavigationManager.NavigateTo(NavigateUrl()); } else { diff --git a/Oqtane.Client/Modules/Admin/Files/Index.razor b/Oqtane.Client/Modules/Admin/Files/Index.razor index 06f7e61c..b730603b 100644 --- a/Oqtane.Client/Modules/Admin/Files/Index.razor +++ b/Oqtane.Client/Modules/Admin/Files/Index.razor @@ -53,7 +53,7 @@ else @context.Name - @context.ModifiedOn + @UtcToLocal(context.ModifiedOn) @context.Extension.ToUpper() @SharedLocalizer["File"] @string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB diff --git a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor index 1d359f66..bcb500ff 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor @@ -132,13 +132,13 @@ _isEnabled = job.IsEnabled.ToString(); _interval = job.Interval.ToString(); _frequency = job.Frequency; - _startDate = Utilities.UtcAsLocalDate(job.StartDate); - _startTime = Utilities.UtcAsLocalDateTime(job.StartDate); - _endDate = Utilities.UtcAsLocalDate(job.EndDate); - _endTime = Utilities.UtcAsLocalDateTime(job.EndDate); + _startDate = UtcToLocal(job.StartDate); + _startTime = UtcToLocal(job.StartDate); + _endDate = UtcToLocal(job.EndDate); + _endTime = UtcToLocal(job.EndDate); _retentionHistory = job.RetentionHistory.ToString(); - _nextDate = Utilities.UtcAsLocalDate(job.NextExecution); - _nextTime = Utilities.UtcAsLocalDateTime(job.NextExecution); + _nextDate = UtcToLocal(job.NextExecution); + _nextTime = UtcToLocal(job.NextExecution); createdby = job.CreatedBy; createdon = job.CreatedOn; modifiedby = job.ModifiedBy; @@ -176,10 +176,10 @@ { job.Interval = int.Parse(_interval); } - job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime); - job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime); + job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay)); + job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay)); job.RetentionHistory = int.Parse(_retentionHistory); - job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime); + job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay)); try { diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor index 4a2f5de6..033c3787 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor @@ -29,7 +29,7 @@ else @context.Name @DisplayStatus(context.IsEnabled, context.IsExecuting) @DisplayFrequency(context.Interval, context.Frequency) - @context.NextExecution?.ToLocalTime() + @UtcToLocal(context.NextExecution) @if (context.IsStarted) { diff --git a/Oqtane.Client/Modules/Admin/Jobs/Log.razor b/Oqtane.Client/Modules/Admin/Jobs/Log.razor index f5453315..55efc788 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Log.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Log.razor @@ -23,8 +23,8 @@ else @context.Job.Name @DisplayStatus(context.Job.IsExecuting, context.Succeeded) - @context.StartDate - @context.FinishDate + @UtcToLocal(context.StartDate) + @UtcToLocal(context.FinishDate) @((MarkupString)context.Notes) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index f10c0203..f0a057d0 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -144,7 +144,7 @@ else user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); if (user != null) { - await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username); + await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username); AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info); } else diff --git a/Oqtane.Client/Modules/Admin/Logs/Detail.razor b/Oqtane.Client/Modules/Admin/Logs/Detail.razor index fd980bce..379a4f3c 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Detail.razor @@ -141,7 +141,7 @@ var log = await LogService.GetLogAsync(_logId); if (log != null) { - _logDate = log.LogDate.ToString(CultureInfo.CurrentCulture); + _logDate = UtcToLocal(log.LogDate).Value.ToString(CultureInfo.CurrentCulture); _level = log.Level; _feature = log.Feature; _function = log.Function; diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index dfb0cf8d..594393ca 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -64,7 +64,7 @@ else - @context.LogDate + @UtcToLocal(context.LogDate) @context.Level @context.Feature @context.Function diff --git a/Oqtane.Client/Modules/Admin/Modules/Export.razor b/Oqtane.Client/Modules/Admin/Modules/Export.razor index d2e90193..10831a56 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Export.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Export.razor @@ -5,24 +5,57 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -
-
- -
- + + +
+
+ +
+ +
+
-
-
+
+ + @SharedLocalizer["Cancel"] + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + @SharedLocalizer["Cancel"] +
+ + - -@SharedLocalizer["Cancel"] @code { private string _content = string.Empty; + private FileManager _filemanager; + private string _filename = string.Empty; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Export Content"; - private async Task ExportModule() + protected override void OnInitialized() + { + _filename = Utilities.GetFriendlyUrl(ModuleState.Title); + } + + private async Task ExportText() { try { @@ -35,4 +68,34 @@ AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); } } + + private async Task ExportFile() + { + try + { + var folderid = _filemanager.GetFolderId(); + if (folderid != -1 && !string.IsNullOrEmpty(_filename)) + { + var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename); + if (fileid != -1) + { + AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success); + } + else + { + AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); + } + } + else + { + AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message); + AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error); + } + } + } diff --git a/Oqtane.Client/Modules/Admin/Modules/Import.razor b/Oqtane.Client/Modules/Admin/Modules/Import.razor index 04b2557a..eacec260 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Import.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Import.razor @@ -2,20 +2,27 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IModuleService ModuleService +@inject IFileService FileService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer
- + +
+ +
+
+
+
+
-
- +
@SharedLocalizer["Cancel"]
@@ -28,6 +35,12 @@ public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override string Title => "Import Content"; + private async Task OnSelectFile(int fileId) + { + var bytes = await FileService.DownloadFileAsync(fileId); + _content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + private async Task ImportModule() { validated = true; diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index a949271f..297a365a 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -97,6 +97,23 @@
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
} @@ -144,6 +161,8 @@ private string _pane; private string _containerType; private string _allPages = "false"; + private string _header = ""; + private string _footer = ""; private string _permissionNames = ""; private List _permissions = null; private string _pageId; @@ -167,37 +186,47 @@ protected override async Task OnInitializedAsync() { SetModuleTitle(Localizer["ModuleSettings.Title"]); - - _title = ModuleState.Title; _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; - _pane = ModuleState.Pane; + _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); - _containerType = ModuleState.ContainerType; - _allPages = ModuleState.AllPages.ToString(); - _permissions = ModuleState.PermissionList; - _pageId = ModuleState.PageId.ToString(); - createdby = ModuleState.CreatedBy; - createdon = ModuleState.CreatedOn; - modifiedby = ModuleState.ModifiedBy; - modifiedon = ModuleState.ModifiedOn; - _effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate); - _expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); - if (ModuleState.ModuleDefinition != null) - { - _module = ModuleState.ModuleDefinition.Name; - _permissionNames = ModuleState.ModuleDefinition?.PermissionNames; + var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); - if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) + _pageId = pagemodule.PageId.ToString(); + _title = pagemodule.Title; + _pane = pagemodule.Pane; + _containerType = pagemodule.ContainerType; + if (string.IsNullOrEmpty(_containerType)) + { + _containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType; + } + _header = pagemodule.Header; + _footer = pagemodule.Footer; + _effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate); + _expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate); + + _allPages = pagemodule.Module.AllPages.ToString(); + createdby = pagemodule.Module.CreatedBy; + createdon = pagemodule.Module.CreatedOn; + modifiedby = pagemodule.Module.ModifiedBy; + modifiedon = pagemodule.Module.ModifiedOn; + _permissions = pagemodule.Module.PermissionList; + + if (pagemodule.Module.ModuleDefinition != null) + { + _module = pagemodule.Module.ModuleDefinition.Name; + _permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames; + + if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType)) { // module settings type explicitly declared in IModule interface - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType); } else { // legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module ) - _moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); + _moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true); } if (_moduleSettingsType != null) { @@ -218,7 +247,7 @@ } else { - AddModuleMessage(string.Format(Localizer["Error.Module.Load"], ModuleState.ModuleDefinitionName), MessageType.Error); + AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error); } var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType))); @@ -270,10 +299,12 @@ { pagemodule.ContainerType = string.Empty; } + pagemodule.Header = _header; + pagemodule.Footer = _footer; await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); - var module = ModuleState; + var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId); module.AllPages = bool.Parse(_allPages); module.PageModuleId = ModuleState.PageModuleId; module.PermissionList = _permissionGrid.GetPermissionList(); diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 6d27b990..df9db9bb 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -30,16 +30,16 @@
- + + @foreach (Page page in _pages) + { + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId) { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList) && page.PageId != _pageId) - { - - } + } - + } +
@@ -217,6 +217,9 @@

+ + +

@@ -225,15 +228,28 @@
+

+
+ +
+ +
+
+
+ +
-   -   - @Localizer["ModuleTitle"] - @Localizer["ModuleDefinition"] +   +   + @Localizer["ModuleTitle"] + @Localizer["ModuleDefinition"]
@@ -247,8 +263,10 @@ { @_themeSettingsComponent +
+ +
-
} } @@ -299,19 +317,21 @@ +
+ +
@if (_themeSettingsType != null) { @_themeSettingsComponent +
+ +
-
} } -
- - } @@ -348,6 +368,7 @@ private string _bodycontent; private List _permissions = null; private PermissionGrid _permissionGrid; + private string _updatemodulepermissions; private List _pageModules; private string _createdby; private DateTime _createdon; @@ -436,6 +457,7 @@ // permissions _permissions = _page.PermissionList; + _updatemodulepermissions = "True"; // page modules var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); @@ -651,6 +673,7 @@ if (_page.UserId == null) { _page.PermissionList = _permissionGrid.GetPermissionList(); + _page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions); } _page = await PageService.UpdatePageAsync(_page); diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor index 88ccdd56..712ba186 100644 --- a/Oqtane.Client/Modules/Admin/Register/Index.razor +++ b/Oqtane.Client/Modules/Admin/Register/Index.razor @@ -3,80 +3,98 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @inject ISettingService SettingService -@if (PageState.Site.AllowRegistration) +@if (_initialized) { - if (!_userCreated) + @if (PageState.Site.AllowRegistration) { - if (PageState.User != null) + if (!_userCreated) { - - } - else - { - -
-
-
- -
- + if (PageState.User != null) + { + + } + else + { + + +
+
+ +
+ +
-
-
- -
-
- - +
+ +
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
-
- -
-
- - -
-
-
-
- -
- -
-
-
- -
- -
-
-
-
- - - @if (_allowsitelogin) - {
+ + + @if (_allowsitelogin) + { +
-
- @Localizer["Login"] - } - +
+ @Localizer["Login"] + } + + } } - } -} -else -{ - + } + else + { + + } } @code { + private bool _initialized = false; + private List _timezones; private string _passwordrequirements; private string _username = string.Empty; private ElementReference form; @@ -87,6 +105,7 @@ else private string _confirm = string.Empty; private string _email = string.Empty; private string _displayname = string.Empty; + private string _timezoneid = string.Empty; private bool _userCreated = false; private bool _allowsitelogin = true; @@ -96,6 +115,9 @@ else { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true")); + _timezones = await TimeZoneService.GetTimeZonesAsync(); + _timezoneid = PageState.Site.TimeZoneId; + _initialized = true; } protected override void OnParametersSet() @@ -124,6 +146,7 @@ else Password = _password, Email = _email, DisplayName = (_displayname == string.Empty ? _username : _displayname), + TimeZoneId = _timezoneid, PhotoFileId = null }; user = await UserService.AddUserAsync(user); diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 5333cd3b..172c4225 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -10,6 +10,7 @@ @inject IAliasService AliasService @inject IThemeService ThemeService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject INotificationService NotificationService @@ -41,6 +42,18 @@
+
+ +
+ +
+
@@ -133,7 +146,7 @@
- +
@@ -416,9 +429,11 @@ private List _themes = new List(); private List _containers = new List(); private List _pages; + private List _timezones; private string _name = string.Empty; private string _homepageid = "-"; + private string _timezoneid = string.Empty; private string _isdeleted; private string _sitemap = ""; private string _siteguid = ""; @@ -435,7 +450,7 @@ private Dictionary _textEditors = new Dictionary(); private string _textEditor = ""; - private string _imageFiles = string.Empty; + private string _imagefiles = string.Empty; private string _headcontent = string.Empty; private string _bodycontent = string.Empty; @@ -493,11 +508,13 @@ Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); if (site != null) { + _timezones = await TimeZoneService.GetTimeZonesAsync(); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); _name = site.Name; + _timezoneid = site.TimeZoneId; if (site.HomePageId != null) { _homepageid = site.HomePageId.Value.ToString(); @@ -531,8 +548,8 @@ _textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName)); } _textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor); - _imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); - _imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles; + _imagefiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; // page content _headcontent = site.HeadContent; @@ -650,6 +667,7 @@ if (site != null) { site.Name = _name; + site.TimeZoneId = _timezoneid; site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null); site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); diff --git a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor index 31fee382..aeda1366 100644 --- a/Oqtane.Client/Modules/Admin/Upgrade/Index.razor +++ b/Oqtane.Client/Modules/Admin/Upgrade/Index.razor @@ -15,7 +15,7 @@ {
- +
@@ -52,15 +57,15 @@
+
-

- } @code { private bool _initialized = false; + private bool _downloaded = false; private Package _package; private bool _upgradeavailable = false; private string _backup = "True"; @@ -125,6 +130,7 @@ ShowProgressIndicator(); await PackageService.DownloadPackageAsync(packageid, version); await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version); + _downloaded = true; HideProgressIndicator(); AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success); } diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor index e57ad13e..68956c40 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Add.razor @@ -8,13 +8,16 @@
- +
- +
+ + +
- +
@@ -26,64 +29,80 @@ @code { - private ElementReference form; - private bool validated = false; + private ElementReference form; + private bool validated = false; - private string _url = string.Empty; - private string _mappedurl = string.Empty; + private string _url = string.Empty; + private string _mappedurl = string.Empty; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; - private async Task SaveUrlMapping() - { - validated = true; - var interop = new Interop(JSRuntime); - if (await interop.FormValid(form)) - { - if (_url != _mappedurl) - { - var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; - url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + private async Task SaveUrlMapping() + { + validated = true; + var interop = new Interop(JSRuntime); + if (await interop.FormValid(form)) + { + if (_url != _mappedurl) + { + var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; + url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); - _url = (_url.StartsWith("/")) ? _url.Substring(1) : _url; - _url = (!_url.StartsWith("http")) ? url + _url : _url; + _url = (_url.StartsWith("/")) ? _url.Substring(1) : _url; + _url = (!_url.StartsWith("http")) ? url + _url : _url; - if (_url.StartsWith(url)) - { - var urlmapping = new UrlMapping(); - urlmapping.SiteId = PageState.Site.SiteId; - var route = new Route(_url, PageState.Alias.Path); - urlmapping.Url = route.PagePath; - urlmapping.MappedUrl = _mappedurl.Replace(url, ""); - urlmapping.Requests = 0; - urlmapping.CreatedOn = DateTime.UtcNow; - urlmapping.RequestedOn = DateTime.UtcNow; + _mappedurl = _mappedurl.Replace(url, ""); + _mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl; - try - { - urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping); - await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); - NavigationManager.NavigateTo(NavigateUrl()); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); - AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); - } - } - else - { - AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning); - } - } - else - { + if (_url.StartsWith(url)) + { + var urlmapping = new UrlMapping(); + urlmapping.SiteId = PageState.Site.SiteId; + urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath; + urlmapping.MappedUrl = _mappedurl; + urlmapping.Requests = 0; + urlmapping.CreatedOn = DateTime.UtcNow; + urlmapping.RequestedOn = DateTime.UtcNow; + + try + { + urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping); + await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); + NavigationManager.NavigateTo(NavigateUrl()); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message); + AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error); + } + } + else + { + AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning); + } + } + else + { AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning); - } + } } else { AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); } } + + private void GenerateUrl() + { + var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; + url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + + var chars = "abcdefghijklmnopqrstuvwxyz"; + Random rnd = new Random(); + for (int i = 0; i < 5; i++) + { + url += chars.Substring(rnd.Next(0, chars.Length - 1), 1); + } + _url = url; + } } diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor index 10c9a57f..c6cb2e0e 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor @@ -8,13 +8,13 @@
- +
- +
@@ -67,8 +67,11 @@ var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/"; url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : ""); + _mappedurl = _mappedurl.Replace(url, ""); + _mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl; + var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid); - urlmapping.MappedUrl = _mappedurl.Replace(url, ""); + urlmapping.MappedUrl = _mappedurl; urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping); await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping); NavigationManager.NavigateTo(NavigateUrl()); diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor index 75a4c845..554213fa 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor @@ -48,7 +48,7 @@ else } @context.Requests - @context.RequestedOn + @UtcToLocal(context.RequestedOn) diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 63486fc7..2517c538 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -9,6 +9,7 @@ @inject INotificationService NotificationService @inject IFileService FileService @inject IFolderService FolderService +@inject ITimeZoneService TimeZoneService @inject IJSRuntime jsRuntime @inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @@ -16,9 +17,9 @@ @if (_initialized) { - @if (PageState.User != null && photo != null) + @if (PageState.User != null && _photo != null) { - @displayname + @_displayname } else { @@ -31,7 +32,7 @@
- +
@@ -47,17 +48,17 @@
- +
- @if (allowtwofactor) + @if (_allowtwofactor) {
- @@ -67,19 +68,31 @@
- +
- +
- +
- + +
+
+
+ +
+
@@ -91,17 +104,17 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -150,12 +163,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } else @@ -163,12 +176,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)" /> } } } @@ -179,12 +192,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } else @@ -192,12 +205,12 @@ @if (p.IsRequired) { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } else { + @attributes="@(p.MaxLength > 0 ? new Dictionary {{"maxlength", p.MaxLength }} : null)"> } } } @@ -220,11 +233,11 @@
- @if (filter == "to") + @if (_filter == "to") { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
    @@ -260,15 +273,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -285,9 +298,9 @@ } else { - @if (notifications.Any()) + @if (_notifications.Any()) { - +
@@ -324,15 +337,15 @@ context.Body = context.Body.Replace("\n", ""); context.Body = context.Body.Replace("\r", ""); } - notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; + _notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body; } @if (context.IsRead) { - @notificationSummary + @_notificationSummary } else { - @notificationSummary + @_notificationSummary } @@ -354,29 +367,32 @@ } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private string username = string.Empty; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private bool allowtwofactor = false; - private string twofactor = "False"; - 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 string _ImageFiles = string.Empty; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private string _confirm = string.Empty; + private bool _allowtwofactor = false; + private string _twofactor = "False"; + private string _email = string.Empty; + private string _displayname = string.Empty; + private FileManager _filemanager; + private int _folderid = -1; + private string _timezoneid = string.Empty; + private int _photofileid = -1; + private File _photo = null; + private string _imagefiles = string.Empty; - private string filter = "to"; - private List notifications; - private string notificationSummary = string.Empty; + private List _profiles; + private Dictionary _userSettings; + private string _category = string.Empty; + + private string _filter = "to"; + private List _notifications; + private string _notificationSummary = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; @@ -386,17 +402,19 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); - profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true"); + _profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.User != null) { - username = PageState.User.Username; - twofactor = PageState.User.TwoFactorRequired.ToString(); - email = PageState.User.Email; - displayname = PageState.User.DisplayName; + _username = PageState.User.Username; + _twofactor = PageState.User.TwoFactorRequired.ToString(); + _email = PageState.User.Email; + _displayname = PageState.User.DisplayName; + _timezoneid = PageState.User.TimeZoneId; - if (string.IsNullOrEmpty(email)) + if (string.IsNullOrEmpty(_email)) { AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning); } @@ -405,24 +423,24 @@ var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); if (folder != null) { - folderid = folder.FolderId; + _folderid = folder.FolderId; } if (PageState.User.PhotoFileId != null) { - photofileid = PageState.User.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = PageState.User.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } - userSettings = PageState.User.Settings; + _userSettings = PageState.User.Settings; var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles); - _ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles; + _imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles); + _imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles; await LoadNotificationsAsync(); @@ -442,13 +460,13 @@ private async Task LoadNotificationsAsync() { - notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); - notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); + _notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId); + _notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -460,38 +478,39 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { var user = PageState.User; - user.Username = username; + user.Username = _username; user.Password = _password; - user.TwoFactorRequired = bool.Parse(twofactor); - user.Email = email; - user.DisplayName = (displayname == string.Empty ? username : displayname); - user.PhotoFileId = filemanager.GetFileId(); + user.TwoFactorRequired = bool.Parse(_twofactor); + user.Email = _email; + user.DisplayName = (_displayname == string.Empty ? _username : _displayname); + user.TimeZoneId = _timezoneid; + user.PhotoFileId = _filemanager.GetFileId(); if (user.PhotoFileId == -1) { user.PhotoFileId = null; } if (user.PhotoFileId != null) { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); + _photofileid = user.PhotoFileId.Value; + _photo = await FileService.GetFileAsync(_photofileid); } else { - photofileid = -1; - photo = null; + _photofileid = -1; + _photo = null; } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId); + await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId); await logger.LogInformation("User Profile Saved"); if (!string.IsNullOrEmpty(PageState.ReturnUrl)) @@ -557,12 +576,12 @@ private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -594,7 +613,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _userSettings = SettingService.SetSetting(_userSettings, SettingName, value); } private async Task Delete(Notification Notification) @@ -624,7 +643,7 @@ private async void FilterChanged(ChangeEventArgs e) { - filter = (string)e.Value; + _filter = (string)e.Value; await LoadNotificationsAsync(); StateHasChanged(); } @@ -634,7 +653,7 @@ try { ShowProgressIndicator(); - foreach(var Notification in notifications) + foreach(var Notification in _notifications) { if (!Notification.IsDeleted) { diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 10cf19e8..510c87e7 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -5,6 +5,7 @@ @inject IUserService UserService @inject IProfileService ProfileService @inject ISettingService SettingService +@inject ITimeZoneService TimeZoneService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -12,7 +13,7 @@ { - @if (profiles != null) + @if (_profiles != null) {
@@ -33,6 +34,18 @@
+
+ +
+ +
+
@@ -48,20 +61,20 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
-
- @if (!string.IsNullOrEmpty(p.Options)) +
+ @if (!string.IsNullOrEmpty(p.Options)) { +
- +
@@ -32,32 +33,53 @@
- +
- +
- +
- +
- +
- + +
+
+
+ +
+ +
+
+
+ +
+
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) {
- +
- @@ -65,15 +87,15 @@
}
- +
- +
- +
- +
@@ -81,15 +103,15 @@
- @foreach (Profile profile in profiles) + @foreach (Profile profile in _profiles) { var p = profile; - if (p.Category != category) + if (p.Category != _category) {
@p.Category
- category = p.Category; + _category = p.Category; }
@@ -128,47 +150,50 @@
- +
@SharedLocalizer["Cancel"] - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost) + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost) { } - @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && isdeleted == "True") + @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True") { }

- + } @code { + private List _timezones; private bool _initialized = false; private string _passwordrequirements; - private int userid; - private string username = string.Empty; + private int _userid; + private string _username = string.Empty; private string _password = string.Empty; private string _passwordtype = "password"; private string _togglepassword = string.Empty; - private string confirm = string.Empty; - private string email = string.Empty; - private string displayname = string.Empty; - private string isdeleted; - private string lastlogin; - private string lastipaddress; - private bool ishost = false; + private string _confirm = string.Empty; + private string _email = string.Empty; + private string _confirmed = string.Empty; + private string _displayname = string.Empty; + private string _timezoneid = string.Empty; + private string _isdeleted; + private string _lastlogin; + private string _lastipaddress; + private bool _ishost = false; - private List profiles; - private Dictionary userSettings; - private string category = string.Empty; + private List _profiles; + private Dictionary _settings; + private string _category = string.Empty; - private string createdby; - private DateTime createdon; - private string modifiedby; - private DateTime modifiedon; - private string deletedby; - private DateTime? deletedon; + private string _createdby; + private DateTime _createdon; + private string _modifiedby; + private DateTime _modifiedon; + private string _deletedby; + private DateTime? _deletedon; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; @@ -178,29 +203,32 @@ { _passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId); _togglepassword = SharedLocalizer["ShowPassword"]; - profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + _timezones = await TimeZoneService.GetTimeZonesAsync(); if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId)) { - userid = UserId; - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + _userid = UserId; + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); if (user != null) { - username = user.Username; - email = user.Email; - displayname = user.DisplayName; - isdeleted = user.IsDeleted.ToString(); - lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); - lastipaddress = user.LastIPAddress; - ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); + _username = user.Username; + _email = user.Email; + _confirmed = user.EmailConfirmed.ToString(); + _displayname = user.DisplayName; + _timezoneid = PageState.User.TimeZoneId; + _isdeleted = user.IsDeleted.ToString(); + _lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn)); + _lastipaddress = user.LastIPAddress; + _ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host); - userSettings = user.Settings; - createdby = user.CreatedBy; - createdon = user.CreatedOn; - modifiedby = user.ModifiedBy; - modifiedon = user.ModifiedOn; - deletedby = user.DeletedBy; - deletedon = user.DeletedOn; + _settings = user.Settings; + _createdby = user.CreatedBy; + _createdon = user.CreatedOn; + _modifiedby = user.ModifiedBy; + _modifiedon = user.ModifiedOn; + _deletedby = user.DeletedBy; + _deletedon = user.DeletedOn; } } @@ -208,14 +236,14 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); } } private string GetProfileValue(string SettingName, string DefaultValue) { - string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue); + string value = SettingService.GetSetting(_settings, SettingName, DefaultValue); if (value.Contains("]")) { value = value.Substring(value.IndexOf("]") + 1); @@ -227,27 +255,29 @@ { try { - if (username != string.Empty && email != string.Empty) + if (_username != string.Empty && _email != string.Empty) { - if (_password == confirm) + if (_password == _confirm) { if (ValidateProfiles()) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); user.SiteId = PageState.Site.SiteId; - user.Username = username; + user.Username = _username; user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; + user.Email = _email; + user.EmailConfirmed = bool.Parse(_confirmed); + user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname; + user.TimeZoneId = _timezoneid; if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); + user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted)); } user = await UserService.UpdateUserAsync(user); if (user != null) { - await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); + await SettingService.UpdateUserSettingsAsync(_settings, user.UserId); await logger.LogInformation("User Saved {User}", user); NavigationManager.NavigateTo(NavigateUrl()); } @@ -269,7 +299,7 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message); + await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error); } } @@ -278,17 +308,17 @@ { try { - await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", username, PageState.User.Username); + await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username); // post back to the server so that the cookies are set correctly var interop = new Interop(JSRuntime); - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = username, returnurl = PageState.Alias.Path }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/"); await interop.SubmitForm(url, fields); } catch (Exception ex) { - await logger.LogError(ex, "Error Impersonating User {Username} {Error}", username, ex.Message); + await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message); AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error); } } @@ -297,9 +327,9 @@ { try { - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && userid != PageState.User.UserId) + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId) { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId); await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); await logger.LogInformation("User Permanently Deleted {User}", user); NavigationManager.NavigateTo(NavigateUrl()); @@ -307,19 +337,19 @@ } catch (Exception ex) { - await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", userid, ex.Message); + await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message); AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error); } } private bool ValidateProfiles() { - foreach (Profile profile in profiles) + foreach (Profile profile in _profiles) { var value = GetProfileValue(profile.Name, string.Empty); if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue)) { - userSettings = SettingService.SetSetting(userSettings, profile.Name, profile.DefaultValue); + _settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue); } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { @@ -346,7 +376,7 @@ private void ProfileChanged(ChangeEventArgs e, string SettingName) { var value = (string)e.Value; - userSettings = SettingService.SetSetting(userSettings, SettingName, value); + _settings = SettingService.SetSetting(_settings, SettingName, value); } private void TogglePassword() diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index a73350f0..7d34670f 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -43,7 +43,7 @@ else @context.User.Username @context.User.DisplayName @((MarkupString)string.Format("{1}", @context.User.Email, @context.User.Email)) - @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "") + @((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", UtcToLocal(context.User.LastLoginOn)) : "") @@ -59,29 +59,23 @@ else
+ @if (_allowregistration == "true") + { +
+ +
+ +
+
+ } +
+ +
+ +
+
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { - @if (_providertype != "") - { -
- -
- -
-
- } - else - { -
- -
- -
-
- }
@@ -432,6 +426,15 @@ else
+
+ +
+ +
+
} } @@ -485,7 +488,8 @@ else private List users; private string _allowregistration; - private string _allowsitelogin; + private string _registerurl; + private string _profileurl; private string _twofactor; private string _cookiename; private string _cookieexpiration; @@ -533,6 +537,7 @@ else private string _createusers; private string _verifyusers; private string _allowhostrole; + private string _allowsitelogin; private string _secret; private string _secrettype = "password"; @@ -553,7 +558,8 @@ else var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); _allowregistration = PageState.Site.AllowRegistration.ToString().ToLower(); - _allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true"); + _registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", ""); + _profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", ""); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { @@ -616,6 +622,7 @@ else _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); _allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false"); + _allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true"); } private async Task LoadUsersAsync(bool load) @@ -673,10 +680,11 @@ else await SiteService.UpdateSiteAsync(site); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); - settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) { + settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false); + settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false); settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); @@ -720,6 +728,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true); + settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); diff --git a/Oqtane.Client/Modules/Admin/Visitors/Detail.razor b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor index e0570431..13dd456f 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Detail.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Detail.razor @@ -101,8 +101,8 @@ _url = visitor.Url; _referrer = visitor.Referrer; _visits = visitor.Visits.ToString(); - _visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture); - _created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture); + _visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture); + _created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture); if (visitor.UserId != null) { diff --git a/Oqtane.Client/Modules/Admin/Visitors/Index.razor b/Oqtane.Client/Modules/Admin/Visitors/Index.razor index 1cb46d3e..2dccb94b 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Index.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Index.razor @@ -53,8 +53,8 @@ else @context.Language @context.Visits - @context.VisitedOn - @context.CreatedOn + @UtcToLocal(context.VisitedOn) + @UtcToLocal(context.CreatedOn)
diff --git a/Oqtane.Client/Modules/Controls/AuditInfo.razor b/Oqtane.Client/Modules/Controls/AuditInfo.razor index acbb3dc6..e2a025ec 100644 --- a/Oqtane.Client/Modules/Controls/AuditInfo.razor +++ b/Oqtane.Client/Modules/Controls/AuditInfo.razor @@ -52,7 +52,7 @@ if (CreatedOn != null) { - _text += $" {Localizer["On"]} {CreatedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; @@ -69,7 +69,7 @@ if (ModifiedOn != null) { - _text += $" {Localizer["On"]} {ModifiedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; @@ -86,7 +86,7 @@ if (DeletedOn != null) { - _text += $" {Localizer["On"]} {DeletedOn.Value.ToString(DateTimeFormat)}"; + _text += $" {Localizer["On"]} {UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}"; } _text += "

"; diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index b97b229b..39681067 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -1,23 +1,23 @@ -using Microsoft.AspNetCore.Components; -using Oqtane.Shared; -using Oqtane.Models; -using System.Threading.Tasks; -using Oqtane.Services; using System; -using Oqtane.Enums; -using Oqtane.UI; using System.Collections.Generic; -using Microsoft.JSInterop; -using System.Linq; using System.Dynamic; -using System.Reflection; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using Oqtane.Enums; +using Oqtane.Models; +using Oqtane.Services; +using Oqtane.Shared; +using Oqtane.UI; namespace Oqtane.Modules { public abstract class ModuleBase : ComponentBase, IModuleControl { private Logger _logger; - private string _urlparametersstate; + private string _urlparametersstate = string.Empty; private Dictionary _urlparameters; private bool _scriptsloaded = false; @@ -62,7 +62,7 @@ namespace Oqtane.Modules public Dictionary UrlParameters { get { - if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters) + if (string.IsNullOrEmpty(_urlparametersstate) || _urlparametersstate != PageState.UrlParameters) { _urlparametersstate = PageState.UrlParameters; _urlparameters = GetUrlParameters(UrlParametersTemplate); @@ -79,18 +79,21 @@ namespace Oqtane.Modules { List resources = null; var type = GetType(); - if (type.BaseType == typeof(ModuleBase)) + if (type.IsSubclassOf(typeof(ModuleBase))) { - if (PageState.Page.Resources != null) + if (type.IsSubclassOf(typeof(ModuleControlBase))) { - resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList(); + if (Resources != null) + { + resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + } } - } - else // modulecontrolbase - { - if (Resources != null) + else // ModuleBase { - resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); + if (PageState.Page.Resources != null) + { + resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList(); + } } } if (resources != null && resources.Any()) @@ -421,70 +424,109 @@ namespace Oqtane.Modules public string ReplaceTokens(string content, object obj) { - var tokens = new List(); - var pos = content.IndexOf("["); - if (pos != -1) - { - if (content.IndexOf("]", pos) != -1) - { - var token = content.Substring(pos, content.IndexOf("]", pos) - pos + 1); - if (token.Contains(":")) - { - tokens.Add(token.Substring(1, token.Length - 2)); - } - } - pos = content.IndexOf("[", pos + 1); - } - if (tokens.Count != 0) - { - foreach (string token in tokens) - { - var segments = token.Split(":"); - if (segments.Length >= 2 && segments.Length <= 3) - { - var objectName = string.Join(":", segments, 0, segments.Length - 1); - var propertyName = segments[segments.Length - 1]; - var propertyValue = ""; + // Using StringBuilder avoids the performance penalty of repeated string allocations + // that occur with string.Replace or string concatenation inside loops. + var sb = new StringBuilder(); + var cache = new Dictionary(); // Cache to store resolved tokens + int index = 0; - switch (objectName) + // Loop through content to find and replace all tokens + while (index < content.Length) + { + int start = content.IndexOf('[', index); // Find start of token + if (start == -1) + { + sb.Append(content, index, content.Length - index); // Append remaining content + break; + } + + int end = content.IndexOf(']', start); // Find end of token + if (end == -1) + { + sb.Append(content, index, content.Length - index); // Append unmatched content + break; + } + + sb.Append(content, index, start - index); // Append content before token + + string token = content.Substring(start + 1, end - start - 1); // Extract token without brackets + string[] parts = token.Split('|', 2); // Separate default fallback if present + string key = parts[0]; + string fallback = parts.Length == 2 ? parts[1] : null; + + if (!cache.TryGetValue(token, out string replacement)) // Check cache first + { + replacement = "[" + token + "]"; // Default replacement is original token + string[] segments = key.Split(':'); + + if (segments.Length >= 2) + { + object current = GetTarget(segments[0], obj); // Start from root object + for (int i = 1; i < segments.Length && current != null; i++) { - case "ModuleState": - propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString(); - break; - case "PageState": - propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null).ToString(); - break; - case "PageState:Alias": - propertyValue = PageState.Alias.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null).ToString(); - break; - case "PageState:Site": - propertyValue = PageState.Site.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null).ToString(); - break; - case "PageState:Page": - propertyValue = PageState.Page.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null).ToString(); - break; - case "PageState:User": - propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null).ToString(); - break; - case "PageState:Route": - propertyValue = PageState.Route.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null).ToString(); - break; - default: - if (obj != null && obj.GetType().Name == objectName) - { - propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null).ToString(); - } - break; - } - if (propertyValue != null) - { - content = content.Replace("[" + token + "]", propertyValue); + var type = current.GetType(); + var prop = type.GetProperty(segments[i]); + current = prop?.GetValue(current); } + if (current != null) + { + replacement = current.ToString(); + } + else if (fallback != null) + { + replacement = fallback; // Use fallback if available + } } + cache[token] = replacement; // Store in cache } + + sb.Append(replacement); // Append replacement value + index = end + 1; // Move index past token } - return content; + + return sb.ToString(); + } + + // Resolve the object instance for a given object name + // Easy to extend with additional object types + private object GetTarget(string name, object obj) + { + return name switch + { + "ModuleState" => ModuleState, + "PageState" => PageState, + _ => (obj != null && obj.GetType().Name == name) ? obj : null // Fallback to obj + }; + } + + // date methods + public DateTime? UtcToLocal(DateTime? datetime) + { + TimeZoneInfo timezone = null; + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); + } + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); + } + return Utilities.UtcAsLocalDateTime(datetime, timezone); + } + + public DateTime? LocalToUtc(DateTime? datetime) + { + TimeZoneInfo timezone = null; + if (PageState.User != null && !string.IsNullOrEmpty(PageState.User.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.User.TimeZoneId); + } + else if (!string.IsNullOrEmpty(PageState.Site.TimeZoneId)) + { + timezone = TimeZoneInfo.FindSystemTimeZoneById(PageState.Site.TimeZoneId); + } + return Utilities.LocalDateAndTimeAsUtc(datetime, timezone); } // logging methods diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index c974f27a..bb7671d6 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net9.0 Exe Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -12,7 +12,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -22,10 +22,10 @@ - - - - + + + + diff --git a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx index c35e179e..c0c4cb8c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx @@ -135,9 +135,6 @@ Error Saving Folder - - Folder Has Files And Cannot Be Deleted - Folder Has Subfolders And Cannot Be Deleted diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index 0b1a8780..c60c0716 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -121,10 +121,10 @@ Forgot Password - User Account Verified Successfully. You Can Now Login With Your Username And Password Below. + User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password. - User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions. + User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions. User Account Linked Successfully. You Can Now Login With Your External Login Below. @@ -133,7 +133,7 @@ External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions. - Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User. + Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification. Please Provide All Required Fields diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx index 90ffbe18..8d8e5cac 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Index.resx @@ -160,12 +160,12 @@ Enabled? - Synchronize + Check For Updates - Modules Have Been Successfully Synchronized With The Marketplace + Module Information Has Been Retrieved From The Marketplace - Error Synchronizing Modules With The Marketplace + Error Retrieving Module Information From The Marketplace \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx index 3ed705f8..23378774 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Export.resx @@ -121,7 +121,7 @@ Export - The Exported Module Content + Select the Export option and you will be able to view the module content Content: @@ -135,4 +135,25 @@ Export Content + + Content + + + File + + + Folder: + + + Select a folder where you wish to save the exported content + + + Please Select A Folder And Provide A Filename Before Choosing Export + + + Filename: + + + Specify a name for the file (without an extension) + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index a91aa36e..4f8f895c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -189,4 +189,19 @@ Module Settings + + Header: + + + Optionally provide content to be injected above the module instance + + + Footer: + + + Optionally provide content to be injected below the module instance + + + Content + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx index cf720e19..ded50502 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx @@ -303,4 +303,10 @@ Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash. + + Update Module Permissions? + + + Specify if changes made to page permissions should be propagated to the modules on this page + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx index 5a0af9ed..47e95add 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Register/Index.resx @@ -180,4 +180,10 @@ Already have account? Login now. + + Time Zone: + + + Your time zone + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 0127c7cc..acfd022e 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -447,4 +447,10 @@ Site Map Cache Cleared + + Time Zone: + + + The default time zone for the site + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx index 4edc516c..0d3b97ed 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Index.resx @@ -160,12 +160,12 @@ Assign - Synchronize + Check For Updates - Themes Have Been Successfully Synchronized With The Marketplace + Theme Information Has Been Retrieved From The Marketplace - Error Synchronizing Themes With The Marketplace + Error Retrieving Theme Information From The Marketplace \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx index e609f42a..a38e830c 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Upgrade/Index.resx @@ -124,7 +124,7 @@ Error Downloading Framework Package - Upload a framework package and select Install to complete the installation + Upload A Framework Package (Oqtane.Framework.#.#.#.nupkg) And Then Select Upgrade Framework: @@ -144,9 +144,6 @@ Framework Is Already Up To Date - - Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade - You Cannot Perform A System Update In A Development Environment @@ -157,6 +154,6 @@ Backup Files? - Specify if you want to backup files during the upgrade process. Disabling this option will result in a better experience in some environments. + Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade. \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx index e1185498..aeae4b29 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Add.resx @@ -1,4 +1,4 @@ - + Exe - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -14,7 +14,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -30,7 +30,7 @@ com.oqtane.maui - 6.1.2 + 6.1.3 1 @@ -67,14 +67,14 @@ - - - - - - - - + + + + + + + + diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 0d0217f8..b50b816c 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 0ebabcbb..a9e739dc 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/download/v6.1.3/Oqtane.Framework.6.1.3.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 06dcf42c..b5e58742 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index a240d6a2..9c2041be 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 8ef6a84a..09453309 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 6.1.2 + 6.1.3 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 readme.md icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 3f8d7188..fb0b6d7f 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Install.zip" -Force diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index 0452d806..8bc6518a 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -9,6 +9,8 @@ nuget.exe pack Oqtane.Framework.nuspec del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release +del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" > NUL +rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" setlocal ENABLEDELAYEDEXPANSION diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 9b53797a..e2f26321 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.2.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.3.Upgrade.zip" -Force diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index ae7d1632..570e4426 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -297,7 +297,7 @@ if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) { // redirect to mapped url - var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; + var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + (!urlMapping.MappedUrl.StartsWith("/") ? "/" : "") + urlMapping.MappedUrl + ((!urlMapping.MappedUrl.Contains("?")) ? route.Query : ""); NavigationManager.NavigateTo(url, true); } else // no url mapping exists diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 7ead95d7..f0b72f22 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Cors; using System.IO.Compression; using Oqtane.Services; using Microsoft.Extensions.Primitives; -using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Net.Http.Headers; // ReSharper disable StringIndexOfIsCultureSpecific.1 diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 62545760..12a9c3fa 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -20,14 +20,16 @@ namespace Oqtane.Controllers { private readonly IFolderRepository _folders; private readonly IUserPermissions _userPermissions; + private readonly IFileRepository _files; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public FolderController(IFolderRepository folders, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) + public FolderController(IFolderRepository folders, IUserPermissions userPermissions, IFileRepository files, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager) { _folders = folders; _userPermissions = userPermissions; + _files = files; _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); @@ -41,7 +43,8 @@ namespace Oqtane.Controllers int SiteId; if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) { - foreach (Folder folder in _folders.GetFolders(SiteId)) + var hierarchy = GetFoldersHierarchy(_folders.GetFolders(SiteId).ToList()); + foreach (Folder folder in hierarchy) { // note that Browse permission is used for this method if (_userPermissions.IsAuthorized(User, PermissionNames.Browse, folder.PermissionList)) @@ -49,7 +52,6 @@ namespace Oqtane.Controllers folders.Add(folder); } } - folders = GetFoldersHierarchy(folders); } else { @@ -244,34 +246,6 @@ namespace Oqtane.Controllers return folder; } - // PUT api//?siteid=x&folderid=y&parentid=z - [HttpPut] - [Authorize(Roles = RoleNames.Registered)] - public void Put(int siteid, int folderid, int? parentid) - { - if (siteid == _alias.SiteId && _folders.GetFolder(folderid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Folder, folderid, PermissionNames.Edit)) - { - int order = 1; - List folders = _folders.GetFolders(siteid).ToList(); - foreach (Folder folder in folders.Where(item => item.ParentId == parentid).OrderBy(item => item.Order)) - { - if (folder.Order != order) - { - folder.Order = order; - _folders.UpdateFolder(folder); - _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Update); - } - order += 2; - } - _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Order Updated {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid); - } - else - { - _logger.Log(LogLevel.Error, this, LogFunction.Update, "Unauthorized Folder Put Attempt {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid); - HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - } - } - // DELETE api//5 [HttpDelete("{id}")] [Authorize(Roles = RoleNames.Registered)] @@ -283,12 +257,20 @@ namespace Oqtane.Controllers var folderPath = _folders.GetFolderPath(folder); if (Directory.Exists(folderPath)) { + // remove all files from disk (including thumbnails, etc...) foreach (var filePath in Directory.GetFiles(folderPath)) { System.IO.File.Delete(filePath); } Directory.Delete(folderPath); } + + // remove files from database + foreach (var file in _files.GetFiles(id)) + { + _files.DeleteFile(file.FileId); + } + _folders.DeleteFolder(id); _syncManager.AddSyncEvent(_alias, EntityNames.Folder, folder.FolderId, SyncEventActions.Delete); _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id); @@ -304,7 +286,6 @@ namespace Oqtane.Controllers { List hierarchy = new List(); Action, Folder> getPath = null; - var folders1 = folders; getPath = (folderList, folder) => { IEnumerable children; @@ -312,23 +293,23 @@ namespace Oqtane.Controllers if (folder == null) { level = -1; - children = folders1.Where(item => item.ParentId == null); + children = folders.Where(item => item.ParentId == null); } else { level = folder.Level; - children = folders1.Where(item => item.ParentId == folder.FolderId); + children = folders.Where(item => item.ParentId == folder.FolderId); } foreach (Folder child in children) { child.Level = level + 1; - child.HasChildren = folders1.Any(item => item.ParentId == child.FolderId); + child.HasChildren = folders.Any(item => item.ParentId == child.FolderId); hierarchy.Add(child); - if (getPath != null) getPath(folderList, child); + getPath(folderList, child); } }; - folders = folders.OrderBy(item => item.Order).ToList(); + folders = folders.OrderBy(item => item.Name).ToList(); getPath(folders, null); // add any non-hierarchical items to the end of the list diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index a7c09fcb..61f69f33 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -9,6 +9,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; +using System.IO; namespace Oqtane.Controllers { @@ -20,18 +21,22 @@ namespace Oqtane.Controllers private readonly IPageRepository _pages; private readonly IModuleDefinitionRepository _moduleDefinitions; private readonly ISettingRepository _settings; + private readonly IFolderRepository _folders; + private readonly IFileRepository _files; private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; - public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) + public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IFolderRepository folders, IFileRepository files, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger) { _modules = modules; _pageModules = pageModules; _pages = pages; _moduleDefinitions = moduleDefinitions; _settings = settings; + _folders = folders; + _files = files; _userPermissions = userPermissions; _syncManager = syncManager; _logger = logger; @@ -76,6 +81,8 @@ namespace Oqtane.Controllers module.ContainerType = pagemodule.ContainerType; module.EffectiveDate = pagemodule.EffectiveDate; module.ExpiryDate = pagemodule.ExpiryDate; + module.Header = pagemodule.Header; + module.Footer = pagemodule.Footer; module.ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName)); @@ -246,6 +253,61 @@ namespace Oqtane.Controllers return content; } + // POST api//export?moduleid=x&pageid=y&folderid=z&filename=a + [HttpPost("export")] + [Authorize(Roles = RoleNames.Registered)] + public int Export(int moduleid, int pageid, int folderid, string filename) + { + var fileid = -1; + var module = _modules.GetModule(moduleid); + if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) && + _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename)) + { + // get content + var content = _modules.ExportModule(moduleid); + + // get folder + var folder = _folders.GetFolder(folderid, false); + string folderPath = _folders.GetFolderPath(folder); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + // create json file + filename = Utilities.GetFriendlyUrl(Path.GetFileNameWithoutExtension(filename)) + ".json"; + string filepath = Path.Combine(folderPath, filename); + if (System.IO.File.Exists(filepath)) + { + System.IO.File.Delete(filepath); + } + System.IO.File.WriteAllText(filepath, content); + + // register file + var file = _files.GetFile(folderid, filename); + if (file == null) + { + file = new Models.File { FolderId = folderid, Name = filename, Extension = "json", Size = (int)new FileInfo(filepath).Length, ImageWidth = 0, ImageHeight = 0 }; + _files.AddFile(file); + } + else + { + file.Size = (int)new FileInfo(filepath).Length; + _files.UpdateFile(file); + } + fileid = file.FileId; + + _logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + + return fileid; + } + // POST api//import?moduleid=x&pageid=y [HttpPost("import")] [Authorize(Roles = RoleNames.Registered)] diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs index f8ad1925..6086bd0f 100644 --- a/Oqtane.Server/Controllers/PageController.cs +++ b/Oqtane.Server/Controllers/PageController.cs @@ -246,6 +246,10 @@ namespace Oqtane.Controllers pagemodule.Pane = pm.Pane; pagemodule.Order = pm.Order; pagemodule.ContainerType = pm.ContainerType; + pagemodule.EffectiveDate = pm.EffectiveDate; + pagemodule.ExpiryDate = pm.ExpiryDate; + pagemodule.Header = pm.Header; + pagemodule.Footer = pm.Footer; _pageModules.AddPageModule(pagemodule); } @@ -295,38 +299,43 @@ namespace Oqtane.Controllers var removed = GetPermissionsDifferences(currentPermissions, page.PermissionList); // synchronize module permissions - if (added.Count > 0 || removed.Count > 0) + if (page.UpdateModulePermissions && (added.Count > 0 || removed.Count > 0)) { - foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList()) + var pageModules = _pageModules.GetPageModules(page.SiteId); + foreach (PageModule pageModule in pageModules.Where(item => item.PageId == page.PageId).ToList()) { - var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); - // permissions added - foreach (Permission permission in added) + // ignore "shared" modules + if (!pageModules.Any(item => item.ModuleId == pageModule.ModuleId && item.PageId != pageModule.PageId)) { - if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName - && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized)) + var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList(); + // permissions added + foreach (Permission permission in added) { - _permissionRepository.AddPermission(new Permission + if (!modulePermissions.Any(item => item.PermissionName == permission.PermissionName + && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized)) { - SiteId = page.SiteId, - EntityName = EntityNames.Module, - EntityId = pageModule.ModuleId, - PermissionName = permission.PermissionName, - RoleId = permission.RoleId, - UserId = permission.UserId, - IsAuthorized = permission.IsAuthorized - }); + _permissionRepository.AddPermission(new Permission + { + SiteId = page.SiteId, + EntityName = EntityNames.Module, + EntityId = pageModule.ModuleId, + PermissionName = permission.PermissionName, + RoleId = permission.RoleId, + UserId = permission.UserId, + IsAuthorized = permission.IsAuthorized + }); + } } - } - // permissions removed - foreach (Permission permission in removed) - { - var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName - && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized); - if (modulePermission != null) + // permissions removed + foreach (Permission permission in removed) { - _permissionRepository.DeletePermission(modulePermission.PermissionId); + var modulePermission = modulePermissions.FirstOrDefault(item => item.PermissionName == permission.PermissionName + && item.RoleId == permission.RoleId && item.UserId == permission.UserId && item.IsAuthorized == permission.IsAuthorized); + if (modulePermission != null) + { + _permissionRepository.DeletePermission(modulePermission.PermissionId); + } } } } diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 2579d379..fd8708c1 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -24,26 +24,50 @@ namespace Oqtane.Controllers private readonly IPageModuleRepository _pageModules; private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; - private readonly IAliasAccessor _aliasAccessor; - private readonly IOptionsMonitorCache _cookieCache; - private readonly IOptionsMonitorCache _oidcCache; - private readonly IOptionsMonitorCache _oauthCache; - private readonly IOptionsMonitorCache _identityCache; + + private readonly IOptions _cookieOptions; + private readonly IOptionsSnapshot _cookieOptionsSnapshot; + private readonly IOptionsMonitorCache _cookieOptionsMonitorCache; + + private readonly IOptions _oidcOptions; + private readonly IOptionsSnapshot _oidcOptionsSnapshot; + private readonly IOptionsMonitorCache _oidcOptionsMonitorCache; + + private readonly IOptions _oauthOptions; + private readonly IOptionsSnapshot _oauthOptionsSnapshot; + private readonly IOptionsMonitorCache _oauthOptionsMonitorCache; + + private readonly IOptions _identityOptions; + private readonly IOptionsSnapshot _identityOptionsSnapshot; + private readonly IOptionsMonitorCache _identityOptionsMonitorCache; + private readonly ILogManager _logger; private readonly Alias _alias; private readonly string _visitorCookie; - public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, IOptionsMonitorCache cookieCache, IOptionsMonitorCache oidcCache, IOptionsMonitorCache oauthCache, IOptionsMonitorCache identityCache, ILogManager logger) + public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, + IOptions cookieOptions, IOptionsSnapshot cookieOptionsSnapshot, IOptionsMonitorCache cookieOptionsMonitorCache, + IOptions oidcOptions, IOptionsSnapshot oidcOptionsSnapshot, IOptionsMonitorCache oidcOptionsMonitorCache, + IOptions oauthOptions, IOptionsSnapshot oauthOptionsSnapshot, IOptionsMonitorCache oauthOptionsMonitorCache, + IOptions identityOptions, IOptionsSnapshot identityOptionsSnapshot, IOptionsMonitorCache identityOptionsMonitorCache, + ILogManager logger) { _settings = settings; _pageModules = pageModules; _userPermissions = userPermissions; _syncManager = syncManager; - _aliasAccessor = aliasAccessor; - _cookieCache = cookieCache; - _oidcCache = oidcCache; - _oauthCache = oauthCache; - _identityCache = identityCache; + _cookieOptions = cookieOptions; + _cookieOptionsSnapshot = cookieOptionsSnapshot; + _cookieOptionsMonitorCache = cookieOptionsMonitorCache; + _oidcOptions = oidcOptions; + _oidcOptionsSnapshot = oidcOptionsSnapshot; + _oidcOptionsMonitorCache = oidcOptionsMonitorCache; + _oauthOptions = oauthOptions; + _oauthOptionsSnapshot = oauthOptionsSnapshot; + _oauthOptionsMonitorCache = oauthOptionsMonitorCache; + _identityOptions = identityOptions; + _identityOptionsSnapshot = identityOptionsSnapshot; + _identityOptionsMonitorCache = identityOptionsMonitorCache; _logger = logger; _alias = tenantManager.GetAlias(); _visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString(); @@ -210,21 +234,21 @@ namespace Oqtane.Controllers [Authorize(Roles = RoleNames.Admin)] public void Clear() { - // clear SiteOptionsCache for each option type - var cookieCache = new SiteOptionsCache(_aliasAccessor); - cookieCache.Clear(); - var oidcCache = new SiteOptionsCache(_aliasAccessor); - oidcCache.Clear(); - var oauthCache = new SiteOptionsCache(_aliasAccessor); - oauthCache.Clear(); - var identityCache = new SiteOptionsCache(_aliasAccessor); - identityCache.Clear(); + (_cookieOptions as SiteOptionsManager).Reset(); + (_cookieOptionsSnapshot as SiteOptionsManager).Reset(); + _cookieOptionsMonitorCache.Clear(); - // clear IOptionsMonitorCache for each option type - _cookieCache.Clear(); - _oidcCache.Clear(); - _oauthCache.Clear(); - _identityCache.Clear(); + (_oidcOptions as SiteOptionsManager).Reset(); + (_oidcOptionsSnapshot as SiteOptionsManager).Reset(); + _oidcOptionsMonitorCache.Clear(); + + (_oauthOptions as SiteOptionsManager).Reset(); + (_oauthOptionsSnapshot as SiteOptionsManager).Reset(); + _oauthOptionsMonitorCache.Clear(); + + (_identityOptions as SiteOptionsManager).Reset(); + (_identityOptionsSnapshot as SiteOptionsManager).Reset(); + _identityOptionsMonitorCache.Clear(); _logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared"); } diff --git a/Oqtane.Server/Controllers/TimeZoneController.cs b/Oqtane.Server/Controllers/TimeZoneController.cs new file mode 100644 index 00000000..158d9f72 --- /dev/null +++ b/Oqtane.Server/Controllers/TimeZoneController.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class TimeZoneController : Controller + { + public TimeZoneController() {} + + // GET: api/ + [HttpGet] + public IEnumerable Get() + { + return TimeZoneInfo.GetSystemTimeZones() + .Select(item => new Models.TimeZone + { + Id = item.Id, + DisplayName = item.DisplayName + }) + .OrderBy(item => item.DisplayName); + } + } +} diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 92aef5c5..859bd50c 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -131,14 +131,16 @@ namespace Oqtane.Controllers filtered.TwoFactorCode = ""; filtered.SecurityStamp = ""; - // include private properties if authenticated user is accessing their own user account os is an administrator + // include private properties if authenticated user is accessing their own user account or is an administrator if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId) { filtered.Email = user.Email; + filtered.TimeZoneId = user.TimeZoneId; filtered.PhotoFileId = user.PhotoFileId; filtered.LastLoginOn = user.LastLoginOn; filtered.LastIPAddress = user.LastIPAddress; filtered.TwoFactorRequired = user.TwoFactorRequired; + filtered.EmailConfirmed = user.EmailConfirmed; filtered.Roles = user.Roles; filtered.CreatedBy = user.CreatedBy; filtered.CreatedOn = user.CreatedOn; @@ -199,10 +201,15 @@ namespace Oqtane.Controllers [Authorize] public async Task Put(int id, [FromBody] User user) { - if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && _users.GetUser(user.UserId, false) != null + var existing = _userManager.GetUser(user.UserId, user.SiteId); + if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null && (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username)) { - user.EmailConfirmed = User.IsInRole(RoleNames.Admin); + // only authorized users can update the email confirmation + if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin)) + { + user.EmailConfirmed = existing.EmailConfirmed; + } user = await _userManager.UpdateUser(user); } else diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 9b4b09e9..98a392cc 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -104,6 +104,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); diff --git a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs index 6234d2fb..d00c01ae 100644 --- a/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneSiteIdentityBuilderExtensions.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Oqtane.Models; using Microsoft.AspNetCore.Identity; using System; diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index d65acb0a..4147455b 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -40,27 +40,26 @@ namespace Oqtane.Infrastructure log += "Processing Notifications For Site: " + site.Name + "
"; // get site settings - List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); - Dictionary settings = GetSettings(sitesettings); - if (!site.IsDeleted && (!settings.ContainsKey("SMTPEnabled") || settings["SMTPEnabled"] == "True")) + var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1); + + if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True") { - if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && - settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && - settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && - settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") + if (settingRepository.GetSettingValue(settings, "SMTPHost", "") != "" && + settingRepository.GetSettingValue(settings, "SMTPPort", "") != "" && + settingRepository.GetSettingValue(settings, "SMTPSender", "") != "") { // construct SMTP Client var client = new SmtpClient() { DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, - Host = settings["SMTPHost"], - Port = int.Parse(settings["SMTPPort"]), - EnableSsl = bool.Parse(settings["SMTPSSL"]) + Host = settingRepository.GetSettingValue(settings, "SMTPHost", ""), + Port = int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), + EnableSsl = bool.Parse(settingRepository.GetSettingValue(settings, "SMTPSSL", "False")) }; - if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") + if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "") { - client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); + client.Credentials = new NetworkCredential(settingRepository.GetSettingValue(settings, "SMTPUsername", ""), settingRepository.GetSettingValue(settings, "SMTPPassword", "")); } // iterate through undelivered notifications @@ -100,7 +99,7 @@ namespace Oqtane.Infrastructure MailMessage mailMessage = new MailMessage(); // sender - if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail)) + if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) { if (!string.IsNullOrEmpty(notification.FromDisplayName)) { @@ -113,7 +112,7 @@ namespace Oqtane.Infrastructure } else { - mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); + mailMessage.From = new MailAddress(settingRepository.GetSettingValue(settings, "SMTPSender", ""), (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); } // recipient @@ -162,7 +161,7 @@ namespace Oqtane.Infrastructure } else { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; + log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
"; } } else @@ -173,15 +172,5 @@ namespace Oqtane.Infrastructure return log; } - - private Dictionary GetSettings(List settings) - { - Dictionary dictionary = new Dictionary(); - foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList()) - { - dictionary.Add(setting.SettingName, setting.SettingValue); - } - return dictionary; - } } } diff --git a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs index 5cf4c59c..89a2238d 100644 --- a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs @@ -40,18 +40,13 @@ namespace Oqtane.Infrastructure foreach (Site site in sites) { log += "
Processing Site: " + site.Name + "
"; - int retention; int count; // get site settings - Dictionary settings = GetSettings(settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList()); + var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1); // purge event log - retention = 30; // 30 days - if (settings.ContainsKey("LogRetention") && !string.IsNullOrEmpty(settings["LogRetention"])) - { - retention = int.Parse(settings["LogRetention"]); - } + var retention = int.Parse(settingRepository.GetSettingValue(settings, "LogRetention", "30")); // 30 day default try { count = logRepository.DeleteLogs(site.SiteId, retention); @@ -65,11 +60,7 @@ namespace Oqtane.Infrastructure // purge visitors if (site.VisitorTracking) { - retention = 30; // 30 days - if (settings.ContainsKey("VisitorRetention") && !string.IsNullOrEmpty(settings["VisitorRetention"])) - { - retention = int.Parse(settings["VisitorRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "VisitorRetention", "30")); // 30 day default try { count = visitorRepository.DeleteVisitors(site.SiteId, retention); @@ -82,11 +73,7 @@ namespace Oqtane.Infrastructure } // purge notifications - retention = 30; // 30 days - if (settings.ContainsKey("NotificationRetention") && !string.IsNullOrEmpty(settings["NotificationRetention"])) - { - retention = int.Parse(settings["NotificationRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "NotificationRetention", "30")); // 30 day default try { count = notificationRepository.DeleteNotifications(site.SiteId, retention); @@ -98,11 +85,7 @@ namespace Oqtane.Infrastructure } // purge broken urls - retention = 30; // 30 days - if (settings.ContainsKey("UrlMappingRetention") && !string.IsNullOrEmpty(settings["UrlMappingRetention"])) - { - retention = int.Parse(settings["UrlMappingRetention"]); - } + retention = int.Parse(settingRepository.GetSettingValue(settings, "UrlMappingRetention", "30")); // 30 day default try { count = urlMappingRepository.DeleteUrlMappings(site.SiteId, retention); @@ -127,15 +110,5 @@ namespace Oqtane.Infrastructure return log; } - - private Dictionary GetSettings(List settings) - { - Dictionary dictionary = new Dictionary(); - foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList()) - { - dictionary.Add(setting.SettingName, setting.SettingValue); - } - return dictionary; - } } } diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs index 1c2ec3c9..13b74176 100644 --- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs +++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs @@ -23,10 +23,6 @@ namespace Oqtane.Infrastructure var config = context.RequestServices.GetService(typeof(IConfigManager)) as IConfigManager; string path = context.Request.Path.ToString(); - // note that in order to support Alias subfolders we used to ignore Blazor framework requests... - // but this does not work in static rendering as the web UI request originates from /_blazor - //if (config.IsInstalled() && !path.StartsWith("/_")) - if (config.IsInstalled()) { // get alias (note that this also sets SiteState.Alias) @@ -43,7 +39,7 @@ namespace Oqtane.Infrastructure var sitesettings = cache.GetOrCreate(Constants.HttpContextSiteSettingsKey + alias.SiteKey, entry => { var settingRepository = context.RequestServices.GetService(typeof(ISettingRepository)) as ISettingRepository; - return settingRepository.GetSettings(EntityNames.Site, alias.SiteId) + return settingRepository.GetSettings(EntityNames.Site, alias.SiteId, EntityNames.Host, -1) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); }); context.Items.Add(Constants.HttpContextSiteSettingsKey, sitesettings); diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs index e4737d9a..5c802fa9 100644 --- a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs +++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs @@ -19,7 +19,6 @@ namespace Oqtane.Infrastructure { var cache = map.GetOrAdd(GetKey(), new OptionsCache()); cache.Clear(); - } public TOptions GetOrAdd(string name, Func createOptions) diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 84679d23..5e1e6e64 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -65,7 +65,12 @@ namespace Oqtane.Managers { user.SiteId = siteid; user.Roles = GetUserRoles(user.UserId, user.SiteId); - user.SecurityStamp = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult()?.SecurityStamp; + var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult(); + if (identityuser != null) + { + user.SecurityStamp = identityuser.SecurityStamp; + user.EmailConfirmed = identityuser.EmailConfirmed; + } user.Settings = _settings.GetSettings(EntityNames.User, user.UserId) .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); } @@ -245,22 +250,30 @@ namespace Oqtane.Managers { identityuser.Email = user.Email; await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated - - // if email address changed and it is not confirmed, verification is required for new email address - if (!user.EmailConfirmed) - { - string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, user, "User Account Verification", body); - _notifications.AddNotification(notification); - } } if (user.EmailConfirmed) { - var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); + if (!identityuser.EmailConfirmed) + { + var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken); + + string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password."; + var notification = new Notification(user.SiteId, user, "User Account Verification", body); + _notifications.AddNotification(notification); + } + } + else + { + identityuser.EmailConfirmed = false; + await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated + + string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, user, "User Account Verification", body); + _notifications.AddNotification(notification); } user = _users.UpdateUser(user); diff --git a/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs new file mode 100644 index 00000000..0e7d40c0 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010301_AddTimeZone.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.01")] + public class AddTimeZone : MultiDatabaseMigration + { + public AddTimeZone(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + siteEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + + var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase); + userEntityBuilder.AddStringColumn("TimeZoneId", 50, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs new file mode 100644 index 00000000..39e01287 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/06010302_AddModuleHeaderFooter.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.06.01.03.02")] + public class AddModuleHeaderFooter : MultiDatabaseMigration + { + public AddModuleHeaderFooter(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var pageModuleEntityBuilder = new PageModuleEntityBuilder(migrationBuilder, ActiveDatabase); + pageModuleEntityBuilder.AddMaxStringColumn("Header", true); + pageModuleEntityBuilder.AddMaxStringColumn("Footer", true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 1ae9c302..2068c0ea 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -34,21 +34,21 @@
- - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - - - + + + diff --git a/Oqtane.Server/Pages/Files.cshtml.cs b/Oqtane.Server/Pages/Files.cshtml.cs index b241388e..f01cd0b7 100644 --- a/Oqtane.Server/Pages/Files.cshtml.cs +++ b/Oqtane.Server/Pages/Files.cshtml.cs @@ -112,7 +112,7 @@ namespace Oqtane.Pages url += Request.QueryString.Value.Substring(1); } - + return RedirectPermanent(url); } @@ -133,10 +133,29 @@ namespace Oqtane.Pages } } - string etag; + string etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); string downloadName = file.Name; string filepath = _files.GetFilePath(file); + var header = ""; + if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) + { + header = ifNoneMatch.ToString(); + } + + if (header.Equals(etag)) + { + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; + return Content(String.Empty); + } + + if (!System.IO.File.Exists(filepath)) + { + _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + return BrokenFile(); + } + // evaluate any querystring parameters bool isRequestingImageManipulation = false; @@ -165,34 +184,6 @@ namespace Oqtane.Pages isRequestingImageManipulation = true; } - if (isRequestingImageManipulation) - { - etag = Utilities.GenerateSimpleHash(Request.QueryString.Value); - } - else - { - etag = Convert.ToString(file.ModifiedOn.Ticks ^ file.Size, 16); - } - - var header = ""; - if (HttpContext.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var ifNoneMatch)) - { - header = ifNoneMatch.ToString(); - } - - if (header.Equals(etag)) - { - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotModified; - return Content(String.Empty); - } - - if (!System.IO.File.Exists(filepath)) - { - _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FilePath}", filepath); - HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; - return BrokenFile(); - } - if (isRequestingImageManipulation) { var _ImageFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "ImageFiles")?.SettingValue; diff --git a/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs b/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs index 5c231806..e74e8eda 100644 --- a/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISettingRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Repository { @@ -7,11 +8,13 @@ namespace Oqtane.Repository { IEnumerable GetSettings(string entityName); IEnumerable GetSettings(string entityName, int entityId); + IEnumerable GetSettings(string entityName1, int entityId1, string entityName2, int entityId2); Setting AddSetting(Setting setting); Setting UpdateSetting(Setting setting); Setting GetSetting(string entityName, int settingId); Setting GetSetting(string entityName, int entityId, string settingName); void DeleteSetting(string entityName, int settingId); void DeleteSettings(string entityName, int entityId); + string GetSettingValue(IEnumerable settings, string settingName, string defaultValue); } } diff --git a/Oqtane.Server/Repository/SettingRepository.cs b/Oqtane.Server/Repository/SettingRepository.cs index 0ca28d50..735ec981 100644 --- a/Oqtane.Server/Repository/SettingRepository.cs +++ b/Oqtane.Server/Repository/SettingRepository.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; @@ -39,24 +38,27 @@ namespace Oqtane.Repository public IEnumerable GetSettings(string entityName, int entityId) { - var settings = GetSettings(entityName).ToList(); - if (entityName == EntityNames.Site) + return GetSettings(entityName).Where(item => item.EntityId == entityId); + } + + public IEnumerable GetSettings(string entityName1, int entityId1, string entityName2, int entityId2) + { + // merge settings from entity2 into entity1 + var settings1 = GetSettings(entityName1, entityId1).ToList(); + foreach (var setting2 in GetSettings(entityName2, entityId2)) { - // site settings can be overridden by host settings - var hostsettings = GetSettings(EntityNames.Host); - foreach (var hostsetting in hostsettings) + var setting1 = settings1.FirstOrDefault(item => item.SettingName == setting2.SettingName); + if (setting1 == null) { - if (settings.Any(item => item.SettingName == hostsetting.SettingName)) - { - settings.First(item => item.SettingName == hostsetting.SettingName).SettingValue = hostsetting.SettingValue; - } - else - { - settings.Add(new Setting { SettingId = -1, EntityName = entityName, EntityId = entityId, SettingName = hostsetting.SettingName, SettingValue = hostsetting.SettingValue, IsPrivate = hostsetting.IsPrivate }); - } + settings1.Add(new Setting { EntityName = entityName1, EntityId = entityId1, SettingName = setting2.SettingName, SettingValue = setting2.SettingValue, IsPrivate = setting2.IsPrivate }); + } + else + { + setting1.SettingValue = setting2.SettingValue; + setting1.IsPrivate = setting2.IsPrivate; } } - return settings.Where(item => item.EntityId == entityId); + return settings1; } public Setting AddSetting(Setting setting) @@ -165,6 +167,19 @@ namespace Oqtane.Repository ManageCache(entityName); } + public string GetSettingValue(IEnumerable settings, string settingName, string defaultValue) + { + var setting = settings.FirstOrDefault(item => item.SettingName == settingName); + if (setting != null) + { + return setting.SettingValue; + } + else + { + return defaultValue; + } + } + private bool IsMaster(string EntityName) { return (EntityName == EntityNames.ModuleDefinition || EntityName == EntityNames.Host); diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 10de3576..48620ead 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -442,6 +442,8 @@ namespace Oqtane.Repository pageModule.Pane = (string.IsNullOrEmpty(pageTemplateModule.Pane)) ? PaneNames.Default : pageTemplateModule.Pane; pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.ContainerType = pageTemplateModule.ContainerType; + pageModule.Header = pageTemplateModule.Header; + pageModule.Footer = pageTemplateModule.Footer; pageModule.IsDeleted = pageTemplateModule.IsDeleted; pageModule.Module.PermissionList = new List(); foreach (var permission in pageTemplateModule.PermissionList) diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 11c624c3..f316b766 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -111,7 +111,7 @@ namespace Oqtane.Services if (site != null && site.SiteId == alias.SiteId) { // site settings - site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId) + site.Settings = _settings.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1) .ToDictionary(setting => setting.SettingName, setting => (setting.IsPrivate ? _private : "") + setting.SettingValue); // populate file extensions @@ -285,6 +285,8 @@ namespace Oqtane.Services ContainerType = pagemodule.ContainerType, EffectiveDate = pagemodule.EffectiveDate, ExpiryDate = pagemodule.ExpiryDate, + Header = pagemodule.Header, + Footer = pagemodule.Footer, ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)), diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 8c99ef7f..2101a861 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -136,7 +136,7 @@ namespace Oqtane policy => { // allow .NET MAUI client cross origin calls - policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0") + policy.WithOrigins("https://0.0.0.1", "http://0.0.0.1", "app://0.0.0.1") .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); @@ -195,9 +195,6 @@ namespace Oqtane app.UseHsts(); } - // execute any IServerStartup logic - app.ConfigureOqtaneAssemblies(env); - // allow oqtane localization middleware app.UseOqtaneLocalization(); @@ -228,6 +225,9 @@ namespace Oqtane app.UseAuthorization(); app.UseAntiforgery(); + // execute any IServerStartup logic + app.ConfigureOqtaneAssemblies(env); + if (_useSwagger) { app.UseSwagger(); diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj index f4e222ee..c40f7c41 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj @@ -13,11 +13,11 @@ - - - - - + + + + + diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs new file mode 100644 index 00000000..7c4c9cbe --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Resources; +using Microsoft.Extensions.Localization; + +[assembly: RootNamespace("[Owner].Module.[Module].Server")] diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index 3844f51a..75da4858 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -19,10 +19,10 @@ - - - - + + + + diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index a454ee59..77ae8f52 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index da875219..47177cdb 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -43,7 +43,7 @@ namespace Oqtane.Models public string Path { get; set; } /// - /// Sorting order of the folder + /// Sorting order of the folder ** not used as folders are sorted in alphabetical order ** /// public int Order { get; set; } diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index 55f31357..7775fcf5 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -113,6 +113,18 @@ namespace Oqtane.Models [NotMapped] public DateTime? ExpiryDate { get; set; } + /// + /// Header content to include at the top of a module instance in the UI + /// + [NotMapped] + public string Header { get; set; } + + /// + /// Footer content to include below a module instance in the UI + /// + [NotMapped] + public string Footer { get; set; } + #endregion #region SiteRouter properties @@ -218,6 +230,8 @@ namespace Oqtane.Models ContainerType = ContainerType, EffectiveDate = EffectiveDate, ExpiryDate = ExpiryDate, + Header = Header, + Footer = Footer, CreatedBy = CreatedBy, CreatedOn = CreatedOn, ModifiedBy = ModifiedBy, diff --git a/Oqtane.Shared/Models/Page.cs b/Oqtane.Shared/Models/Page.cs index bec0347e..15d640fe 100644 --- a/Oqtane.Shared/Models/Page.cs +++ b/Oqtane.Shared/Models/Page.cs @@ -122,6 +122,12 @@ namespace Oqtane.Models [NotMapped] public bool HasChildren { get; set; } + /// + /// Indicates if module permissions should be updated to be consistent with page permissions + /// + [NotMapped] + public bool UpdateModulePermissions { get; set; } + /// /// List of permissions for this page /// diff --git a/Oqtane.Shared/Models/PageModule.cs b/Oqtane.Shared/Models/PageModule.cs index 0e7c812f..20985e93 100644 --- a/Oqtane.Shared/Models/PageModule.cs +++ b/Oqtane.Shared/Models/PageModule.cs @@ -41,14 +41,27 @@ namespace Oqtane.Models /// Reference to a Razor Container which wraps this module instance. /// public string ContainerType { get; set; } + /// /// Start of when this assignment is valid. See also /// public DateTime? EffectiveDate { get; set; } + /// /// End of when this assignment is valid. See also /// public DateTime? ExpiryDate { get; set; } + + /// + /// Header content to include above the module instance in the UI + /// + public string Header { get; set; } + + /// + /// Footer content to include below the module instance in the UI + /// + public string Footer { get; set; } + #region IDeletable Properties public string DeletedBy { get; set; } diff --git a/Oqtane.Shared/Models/Result.cs b/Oqtane.Shared/Models/Result.cs index d550f2c4..8500d3ad 100644 --- a/Oqtane.Shared/Models/Result.cs +++ b/Oqtane.Shared/Models/Result.cs @@ -6,6 +6,8 @@ namespace Oqtane.Models public string Message { get; set; } + public Result() {} + public Result(bool success) { Success = success; diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index aeb6e37b..e674be2e 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -26,6 +26,11 @@ namespace Oqtane.Models /// public string Name { get; set; } + /// + /// The default time zone for the site + /// + public string TimeZoneId { get; set; } + /// /// Reference to a which has the Logo for this site. /// Should be an image. @@ -200,6 +205,7 @@ namespace Oqtane.Models SiteId = SiteId, TenantId = TenantId, Name = Name, + TimeZoneId = TimeZoneId, LogoFileId = LogoFileId, FaviconFileId = FaviconFileId, DefaultThemeType = DefaultThemeType, diff --git a/Oqtane.Shared/Models/SiteTemplate.cs b/Oqtane.Shared/Models/SiteTemplate.cs index 348e1834..42cbffee 100644 --- a/Oqtane.Shared/Models/SiteTemplate.cs +++ b/Oqtane.Shared/Models/SiteTemplate.cs @@ -95,6 +95,8 @@ namespace Oqtane.Models Pane = PaneNames.Default; Order = 1; ContainerType = ""; + Header = ""; + Footer = ""; IsDeleted = false; PermissionList = new List() { @@ -110,6 +112,8 @@ namespace Oqtane.Models public string Pane { get; set; } public int Order { get; set; } public string ContainerType { get; set; } + public string Header { get; set; } + public string Footer { get; set; } public bool IsDeleted { get; set; } public List PermissionList { get; set; } public List Settings { get; set; } diff --git a/Oqtane.Shared/Models/TimeZone.cs b/Oqtane.Shared/Models/TimeZone.cs new file mode 100644 index 00000000..a2ff00f0 --- /dev/null +++ b/Oqtane.Shared/Models/TimeZone.cs @@ -0,0 +1,10 @@ +namespace Oqtane.Models +{ + public class TimeZone + { + public string Id { get; set; } + + public string DisplayName { get; set; } + + } +} diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index d5009fb5..7b6b398b 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -29,6 +29,11 @@ namespace Oqtane.Models /// public string Email { get; set; } + /// + /// User time zone + /// + public string TimeZoneId { get; set; } + /// /// Reference to a containing the users photo. /// diff --git a/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs index d4f49d37..024c266a 100644 --- a/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs +++ b/Oqtane.Shared/Modules/HtmlText/Models/HtmlText.cs @@ -1,7 +1,5 @@ -using System; using Oqtane.Models; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using Oqtane.Documentation; namespace Oqtane.Modules.HtmlText.Models diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index c991ee17..fbffe080 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net9.0 Debug;Release - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -19,11 +19,11 @@ - - - + + + - + diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 23c17245..a11a80cc 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,8 +4,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "6.1.2"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2"; + public static readonly string Version = "6.1.3"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; @@ -46,7 +46,7 @@ namespace Oqtane.Shared public const string DefaultSite = "Default Site"; public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png,ico,webp"; - public const string UploadableFiles = ImageFiles + ",mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg,csv,json,rss,css"; + public const string UploadableFiles = ImageFiles + ",mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg,csv,json,rss,css,md"; public const string ReservedDevices = "CON,NUL,PRN,COM0,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT0,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,CONIN$,CONOUT$"; public static readonly char[] InvalidFileNameChars = diff --git a/Oqtane.Shared/Shared/ExternalLoginProviders.cs b/Oqtane.Shared/Shared/ExternalLoginProviders.cs index 8b62bad9..57cf9322 100644 --- a/Oqtane.Shared/Shared/ExternalLoginProviders.cs +++ b/Oqtane.Shared/Shared/ExternalLoginProviders.cs @@ -68,7 +68,7 @@ namespace Oqtane.Shared Name = "Facebook", Settings = new Dictionary() { - { "ExternalLogin:ProviderUrl", "https://developers.facebook.com/apps/" }, + { "ExternalLogin:ProviderUrl", "https://developers.facebook.com" }, { "ExternalLogin:ProviderType", "oauth2" }, { "ExternalLogin:ProviderName", "Facebook" }, { "ExternalLogin:AuthorizationUrl", "https://www.facebook.com/v18.0/dialog/oauth" }, diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 0efc8228..7474506c 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net9.0 Exe - 6.1.2 + 6.1.3 Oqtane Shaun Walker .NET Foundation @@ -11,7 +11,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Updater/Program.cs b/Oqtane.Updater/Program.cs index 5cac25f6..e99ac13f 100644 --- a/Oqtane.Updater/Program.cs +++ b/Oqtane.Updater/Program.cs @@ -15,19 +15,25 @@ namespace Oqtane.Updater /// static void Main(string[] args) { - // requires 3 arguments - the ContentRootPath, the WebRootPath of the site, and a backup flag + // note additional arguments must be added in a backward compatible manner as older versions will not pass them + // requires 2 arguments - the ContentRootPath and the WebRootPath of the site // for testing purposes you can uncomment and modify the logic below //Array.Resize(ref args, 3); //args[0] = @"C:\yourpath\oqtane.framework\Oqtane.Server"; //args[1] = @"C:\yourpath\oqtane.framework\Oqtane.Server\wwwroot"; - //args[2] = @"true"; + //args[2] = @"true"; // parameter added in 6.1.2 - if (args.Length == 3) + if (args.Length >= 2) { string contentrootfolder = args[0]; string webrootfolder = args[1]; - bool backup = bool.Parse(args[2]); + + bool backup = true; + if (args.Length >= 3) + { + backup = bool.Parse(args[2]); + } string deployfolder = Path.Combine(contentrootfolder, "Packages"); string backupfolder = Path.Combine(contentrootfolder, "Backup"); diff --git a/README.md b/README.md index 4a0fc61e..a69b4390 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline # Latest Release -[6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) was released on March 12, 2025 and is a maintenance release including 46 pull requests by 4 different contributors, pushing the total number of project commits all-time to over 6400. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) was released on April 10, 2025 and is a maintenance release including 41 pull requests by 3 different contributors, pushing the total number of project commits all-time to over 6500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -22,11 +22,11 @@ Microsoft's Public Cloud (requires an Azure account) A free ASP.NET hosting account. No hidden fees. No credit card required. [![Deploy to MonsterASP.NET](https://www.oqtane.org/files/Public/MonsterASPNET.png)](https://www.monsterasp.net/) -# Getting Started (Version 6.1.1) +# Getting Started (Version 6.1.2) **Installing using source code from the Dev/Master branch:** -- Install **[.NET 9.0.3 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. +- Install **[.NET 9.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. - Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. @@ -92,6 +92,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan # Roadmap This project is open source, and therefore is a work in progress... +[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) (Apr 10, 2025) +- [x] Stabilization improvements + [6.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1) (Mar 12, 2025) - [x] Stabilization improvements - [x] Cookie Consent Banner & Privacy/Terms @@ -165,7 +168,7 @@ The following diagram visualizes the client and server components in the Oqtane # Databases -As of version 2.1 (June 2021) Oqtane supports multiple relational database providers - SQL Server, SQLite, MySQL, PostgreSQL +Oqtane supports multiple relational database providers - SQL Server, SQLite, MySQL, PostgreSQL ![Databases](https://github.com/oqtane/framework/blob/dev/screenshots/databases.png?raw=true "Oqtane Databases") diff --git a/azuredeploy.json b/azuredeploy.json index 461130fc..e57cc1f8 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -220,7 +220,7 @@ "apiVersion": "2024-04-01", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]", "properties": { - "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.1/Oqtane.Framework.6.1.1.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"