diff --git a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor index 1c149c5e..9620a396 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor @@ -64,6 +64,12 @@ +
+ +
+ +
+
@@ -97,6 +103,7 @@ private string _maxlength = "0"; private string _defaultvalue = string.Empty; private string _options = string.Empty; + private string _validation = string.Empty; private string _isrequired = "False"; private string _isprivate = "False"; private string createdby; @@ -126,6 +133,7 @@ _maxlength = profile.MaxLength.ToString(); _defaultvalue = profile.DefaultValue; _options = profile.Options; + _validation = profile.Validation; _isrequired = profile.IsRequired.ToString(); _isprivate = profile.IsPrivate.ToString(); createdby = profile.CreatedBy; @@ -169,6 +177,7 @@ profile.MaxLength = int.Parse(_maxlength); profile.DefaultValue = _defaultvalue; profile.Options = _options; + profile.Validation = _validation; profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired)); profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate)); if (_profileid != -1) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index c0f2df68..a7f69bb4 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -145,9 +145,9 @@
- +
- @@ -183,7 +183,16 @@
-
+
+ +
+ +
+
+
@@ -354,6 +363,7 @@ private string _togglesmtppassword = string.Empty; private string _smtpsender = string.Empty; private string _smtprelay = "False"; + private string _smtpenabled = "True"; private string _retention = string.Empty; private string _pwaisenabled; private int _pwaappiconfileid = -1; @@ -434,6 +444,7 @@ _togglesmtppassword = SharedLocalizer["ShowPassword"]; _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); _smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False"); + _smtpenabled = SettingService.GetSetting(settings, "SMTPEnabled", "True"); _retention = SettingService.GetSetting(settings, "NotificationRetention", "30"); // aliases @@ -600,7 +611,8 @@ settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); - settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); + settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true); + settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await logger.LogInformation("Site Settings Saved {Site}", site); @@ -612,8 +624,8 @@ else { AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success); - await interop.ScrollTo(0, 0, "smooth"); - } + await ScrollToPageTop(); + } } } else @@ -681,9 +693,8 @@ await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); - var interop = new Interop(JSRuntime); - await interop.ScrollTo(0, 0, "smooth"); - } + await ScrollToPageTop(); + } catch (Exception ex) { await logger.LogError(ex, "Error Testing SMTP Configuration"); diff --git a/Oqtane.Client/Modules/Admin/Themes/Edit.razor b/Oqtane.Client/Modules/Admin/Themes/Edit.razor index 03bb6cf1..00e22f29 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Edit.razor @@ -13,7 +13,7 @@
- +
@@ -89,32 +89,32 @@ private string _themeName = ""; private string _isenabled; private string _name; - private string _version; - private string _packagename; - private string _owner = ""; - private string _url = ""; - private string _contact = ""; - private string _license = ""; + private string _version; + private string _packagename; + private string _owner = ""; + private string _url = ""; + private string _contact = ""; + private string _license = ""; private string _createdby; private DateTime _createdon; private string _modifiedby; private DateTime _modifiedon; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnInitializedAsync() - { - try - { + protected override async Task OnInitializedAsync() + { + try + { _themeId = Int32.Parse(PageState.QueryString["id"]); var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId); - if (theme != null) - { - _name = theme.Name; + if (theme != null) + { + _name = theme.Name; _isenabled =theme.IsEnabled.ToString(); _version = theme.Version; - _packagename = theme.PackageName; - _owner = theme.Owner; + _packagename = theme.PackageName; + _owner = theme.Owner; _url = theme.Url; _contact = theme.Contact; _license = theme.License; @@ -142,6 +142,7 @@ try { var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId); + theme.Name = _name; theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled)); await ThemeService.UpdateThemeAsync(theme); await logger.LogInformation("Theme Saved {Theme}", theme); diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index 70aa3487..e1eabc2c 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Modules.Admin.UserProfile +@using System.Text.RegularExpressions; @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService @@ -227,140 +228,140 @@ else

@code { - 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 List profiles; - private Dictionary settings; - private string category = string.Empty; - private string filter = "to"; - private List notifications; + 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 List profiles; + private Dictionary settings; + private string category = string.Empty; + private string filter = "to"; + private List notifications; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; - protected override async Task OnParametersSetAsync() - { - try - { - _togglepassword = SharedLocalizer["ShowPassword"]; + protected override async Task OnParametersSetAsync() + { + try + { + _togglepassword = SharedLocalizer["ShowPassword"]; - if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) - { - allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); - } + if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) + { + allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); + } - if (PageState.User != null) - { - username = PageState.User.Username; - twofactor = PageState.User.TwoFactorRequired.ToString(); - email = PageState.User.Email; - displayname = PageState.User.DisplayName; + if (PageState.User != null) + { + username = PageState.User.Username; + twofactor = PageState.User.TwoFactorRequired.ToString(); + email = PageState.User.Email; + displayname = PageState.User.DisplayName; - // get user folder - var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); - if (folder != null) - { - folderid = folder.FolderId; - } + // get user folder + var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); + if (folder != null) + { + folderid = folder.FolderId; + } - if (PageState.User.PhotoFileId != null) - { - photofileid = PageState.User.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); - } - else - { - photofileid = -1; - photo = null; - } + if (PageState.User.PhotoFileId != null) + { + photofileid = PageState.User.PhotoFileId.Value; + photo = await FileService.GetFileAsync(photofileid); + } + else + { + photofileid = -1; + photo = null; + } - profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); - settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); + profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); + settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); - await LoadNotificationsAsync(); - } - else - { - AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); - AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error); - } - } + await LoadNotificationsAsync(); + } + else + { + AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); + AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error); + } + } - 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(); - } + 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(); + } - private string GetProfileValue(string SettingName, string DefaultValue) - { - string value = SettingService.GetSetting(settings, SettingName, DefaultValue); - if (value.Contains("]")) - { - value = value.Substring(value.IndexOf("]") + 1); - } - return value; - } + private string GetProfileValue(string SettingName, string DefaultValue) + { + string value = SettingService.GetSetting(settings, SettingName, DefaultValue); + if (value.Contains("]")) + { + value = value.Substring(value.IndexOf("]") + 1); + } + return value; + } - private async Task Save() - { - try - { - if (username != string.Empty && email != string.Empty && ValidateProfiles()) - { - if (_password == confirm) - { - var user = PageState.User; - 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(); - if (user.PhotoFileId == -1) - { - user.PhotoFileId = null; - } - if (user.PhotoFileId != null) - { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); - } - else - { - photofileid = -1; - photo = null; - } + private async Task Save() + { + try + { + if (username != string.Empty && email != string.Empty && ValidateProfiles()) + { + if (_password == confirm) + { + var user = PageState.User; + 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(); + if (user.PhotoFileId == -1) + { + user.PhotoFileId = null; + } + if (user.PhotoFileId != null) + { + photofileid = user.PhotoFileId.Value; + photo = await FileService.GetFileAsync(photofileid); + } + else + { + photofileid = -1; + photo = null; + } - user = await UserService.UpdateUserAsync(user); - if (user != null) - { - await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); - await logger.LogInformation("User Profile Saved"); + user = await UserService.UpdateUserAsync(user); + if (user != null) + { + await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); + await logger.LogInformation("User Profile Saved"); - AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); - StateHasChanged(); - } - else - { - AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); - } - } + AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); + StateHasChanged(); + } + else + { + AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + } + } else { AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning); @@ -370,6 +371,8 @@ else { AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning); } + + await ScrollToPageTop(); } catch (Exception ex) { @@ -389,10 +392,15 @@ else } if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) { valid = false; } + if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + { + Regex regex = new Regex(profile.Validation); + valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + } } } return valid; diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 713dab6e..54c396ee 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Modules.Admin.Users +@using System.Text.RegularExpressions; @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService @@ -95,8 +96,8 @@ @code { private string username = string.Empty; private string _password = string.Empty; - private string _passwordtype = "password"; - private string _togglepassword = 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; @@ -121,15 +122,15 @@ } } - private string GetProfileValue(string SettingName, string DefaultValue) - { - string value = SettingService.GetSetting(settings, SettingName, DefaultValue); - if (value.Contains("]")) - { - value = value.Substring(value.IndexOf("]") + 1); - } - return value; - } + private string GetProfileValue(string SettingName, string DefaultValue) + { + string value = SettingService.GetSetting(settings, SettingName, DefaultValue); + if (value.Contains("]")) + { + value = value.Substring(value.IndexOf("]") + 1); + } + return value; + } private async Task SaveUser() { @@ -195,9 +196,17 @@ { settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); } - if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - valid = false; + if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + { + valid = false; + } + if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + { + Regex regex = new Regex(profile.Validation); + valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + } } } return valid; diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index af541617..fea1a6f0 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -1,4 +1,5 @@ @namespace Oqtane.Modules.Admin.Users +@using System.Text.RegularExpressions; @inherits ModuleBase @inject NavigationManager NavigationManager @inject IUserService UserService @@ -148,124 +149,124 @@ else @code { - 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 FileManager filemanager; - private int photofileid = -1; - private File photo = null; - private string isdeleted; - private string lastlogin; - private string lastipaddress; + 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 FileManager filemanager; + private int photofileid = -1; + private File photo = null; + private string isdeleted; + private string lastlogin; + private string lastipaddress; - private List profiles; - private Dictionary settings; - 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; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; - protected override async Task OnParametersSetAsync() - { - try - { - if (PageState.QueryString.ContainsKey("id")) - { - _togglepassword = SharedLocalizer["ShowPassword"]; - profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); - userid = Int32.Parse(PageState.QueryString["id"]); - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); - if (user != null) - { - username = user.Username; - email = user.Email; - displayname = user.DisplayName; - if (user.PhotoFileId != null) - { - photofileid = user.PhotoFileId.Value; - photo = await FileService.GetFileAsync(photofileid); - } - else - { - photofileid = -1; - photo = null; - } - isdeleted = user.IsDeleted.ToString(); - lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); - lastipaddress = user.LastIPAddress; + protected override async Task OnParametersSetAsync() + { + try + { + if (PageState.QueryString.ContainsKey("id")) + { + _togglepassword = SharedLocalizer["ShowPassword"]; + profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); + userid = Int32.Parse(PageState.QueryString["id"]); + var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + if (user != null) + { + username = user.Username; + email = user.Email; + displayname = user.DisplayName; + if (user.PhotoFileId != null) + { + photofileid = user.PhotoFileId.Value; + photo = await FileService.GetFileAsync(photofileid); + } + else + { + photofileid = -1; + photo = null; + } + isdeleted = user.IsDeleted.ToString(); + lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); + lastipaddress = user.LastIPAddress; - settings = await SettingService.GetUserSettingsAsync(user.UserId); - createdby = user.CreatedBy; - createdon = user.CreatedOn; - modifiedby = user.ModifiedBy; - modifiedon = user.ModifiedOn; - deletedby = user.DeletedBy; - deletedon = user.DeletedOn; - } - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); - AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); - } - } + settings = await SettingService.GetUserSettingsAsync(user.UserId); + createdby = user.CreatedBy; + createdon = user.CreatedOn; + modifiedby = user.ModifiedBy; + modifiedon = user.ModifiedOn; + deletedby = user.DeletedBy; + deletedon = user.DeletedOn; + } + } + } + catch (Exception ex) + { + 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(settings, SettingName, DefaultValue); - if (value.Contains("]")) - { - value = value.Substring(value.IndexOf("]") + 1); - } - return value; - } + private string GetProfileValue(string SettingName, string DefaultValue) + { + string value = SettingService.GetSetting(settings, SettingName, DefaultValue); + if (value.Contains("]")) + { + value = value.Substring(value.IndexOf("]") + 1); + } + return value; + } - private async Task SaveUser() - { - try - { - if (username != string.Empty && email != string.Empty && ValidateProfiles()) - { - if (_password == confirm) - { - var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); - user.SiteId = PageState.Site.SiteId; - user.Username = username; - user.Password = _password; - user.Email = email; - user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; - user.PhotoFileId = null; - user.PhotoFileId = filemanager.GetFileId(); - if (user.PhotoFileId == -1) - { - user.PhotoFileId = null; - } + private async Task SaveUser() + { + try + { + if (username != string.Empty && email != string.Empty && ValidateProfiles()) + { + if (_password == confirm) + { + var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); + user.SiteId = PageState.Site.SiteId; + user.Username = username; + user.Password = _password; + user.Email = email; + user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; + user.PhotoFileId = null; + user.PhotoFileId = filemanager.GetFileId(); + if (user.PhotoFileId == -1) + { + user.PhotoFileId = null; + } - 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(settings, user.UserId); - await logger.LogInformation("User Saved {User}", user); - NavigationManager.NavigateTo(NavigateUrl()); - } - else - { - AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); - } + user = await UserService.UpdateUserAsync(user); + if (user != null) + { + await SettingService.UpdateUserSettingsAsync(settings, user.UserId); + await logger.LogInformation("User Saved {User}", user); + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error); + } } else { @@ -293,9 +294,17 @@ else { settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue); } - if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - valid = false; + if (valid == true && profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty))) + { + valid = false; + } + if (valid == true && !string.IsNullOrEmpty(profile.Validation)) + { + Regex regex = new Regex(profile.Validation); + valid = regex.Match(SettingService.GetSetting(settings, profile.Name, string.Empty)).Success; + } } } return valid; diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor index bb609f80..72a9662c 100644 --- a/Oqtane.Client/Modules/HtmlText/Edit.razor +++ b/Oqtane.Client/Modules/HtmlText/Edit.razor @@ -53,7 +53,6 @@ public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" } }; diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor index 4428d033..77f7b95d 100644 --- a/Oqtane.Client/Modules/HtmlText/Index.razor +++ b/Oqtane.Client/Modules/HtmlText/Index.razor @@ -15,11 +15,6 @@ } @code { - public override List Resources => new List() - { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } - }; - private string content = ""; protected override async Task OnParametersSetAsync() diff --git a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs index 36e674fe..59a473c4 100644 --- a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs +++ b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using Oqtane.Documentation; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Modules.HtmlText { @@ -13,7 +15,11 @@ namespace Oqtane.Modules.HtmlText Version = "1.0.1", ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server", ReleaseVersions = "1.0.0,1.0.1", - SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client" + SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client", + Resources = new List() + { + new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } + } }; } } diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 01ad8848..8aceca83 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -291,6 +291,12 @@ namespace Oqtane.Modules } } + public async Task ScrollToPageTop() + { + var interop = new Interop(JSRuntime); + await interop.ScrollTo(0, 0, "smooth"); + } + // logging methods public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) { diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx index 720e34f9..8a3a23ca 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx @@ -183,4 +183,10 @@ Private? + + Optionally provide a regular expression (RegExp) for validating the value entered + + + Validation: + \ 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 5abaf9f2..0f9d634e 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -369,4 +369,10 @@ Page Content + + Specify if SMTP is enabled for this site + + + Enabled? + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index b37c6905..de4d7957 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -1,3 +1,4 @@ +@using System.Net @namespace Oqtane.Themes.Controls @inherits ThemeControlBase @inject NavigationManager NavigationManager @@ -501,7 +502,7 @@ module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); if (module != null) { - NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); + NavigationManager.NavigateTo(EditUrl("admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery))); } break; case "Add": diff --git a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs index 763c694c..e6105970 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs +++ b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using Oqtane.Documentation; using Oqtane.Models; +using Oqtane.Shared; namespace Oqtane.Themes.OqtaneTheme { @@ -11,7 +13,13 @@ namespace Oqtane.Themes.OqtaneTheme Name = "Oqtane Theme", Version = "1.0.0", ThemeSettingsType = "Oqtane.Themes.OqtaneTheme.ThemeSettings, Oqtane.Client", - ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client" + ContainerSettingsType = "Oqtane.Themes.OqtaneTheme.ContainerSettings, Oqtane.Client", + Resources = new List() + { + new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.0/cyborg/bootstrap.min.css", Integrity = "sha512-d6pZJl/sNcj0GFkp4kTjXtPE14deuUsOqFQtxkj0KyBJQl+4e0qsEyuIDcNqrYuGoauAW3sWyDCQp49mhF4Syw==", CrossOrigin = "anonymous" }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, + new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" } + } }; } } diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index fe88310c..e4ba70f7 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -110,14 +110,6 @@ public override string Panes => PaneNames.Default + ",Top Full Width,Top 100%,Left 50%,Right 50%,Left 33%,Center 33%,Right 33%,Left Outer 25%,Left Inner 25%,Right Inner 25%,Right Outer 25%,Left 25%,Center 50%,Right 25%,Left Sidebar 66%,Right Sidebar 33%,Left Sidebar 33%,Right Sidebar 66%,Bottom 100%,Bottom Full Width,Footer"; - public override List Resources => new List() - { - // obtained from https://cdnjs.com/libraries - new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.0/cyborg/bootstrap.min.css", Integrity = "sha512-d6pZJl/sNcj0GFkp4kTjXtPE14deuUsOqFQtxkj0KyBJQl+4e0qsEyuIDcNqrYuGoauAW3sWyDCQp49mhF4Syw==", CrossOrigin = "anonymous" }, - new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, - new Resource { ResourceType = ResourceType.Script, Bundle = "Bootstrap", Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.bundle.min.js", Integrity = "sha512-9GacT4119eY3AcosfWtHMsT5JyZudrexyEVzTBWV3viP/YfB9e2pEy3N7WXL3SV6ASXpTU0vzzSxsbfsuUH4sQ==", CrossOrigin = "anonymous" } - }; - private bool _login = true; private bool _register = true; private bool _footer = false; diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 8465faa8..d2ef90a4 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -531,10 +531,13 @@ { foreach (var resource in resources) { - if (!resource.Url.Contains("://") && resource.Url.StartsWith("~/")) + if (resource.Url.StartsWith("~")) { - // create local path - resource.Url = resource.Url.Replace("~", alias.BaseUrl + "/" + type + "/" + name); + resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/"); + } + if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl)) + { + resource.Url = alias.BaseUrl + resource.Url; } // ensure resource does not exist already diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index 157a4999..2b185b83 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -96,6 +96,30 @@ { await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body); } + + if (PageState.Page.Resources != null && PageState.Page.Resources.Exists(item => item.ResourceType == ResourceType.Script)) + { + var interop = new Interop(JSRuntime); + var scripts = new List(); + var inline = 0; + foreach (Resource resource in PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site)) + { + if (!string.IsNullOrEmpty(resource.Url)) + { + var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; + scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module, location = resource.Location.ToString().ToLower() }); + } + else + { + inline += 1; + await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower()); + } + } + if (scripts.Any()) + { + await interop.IncludeScripts(scripts.ToArray()); + } + } } } diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 9de2e512..c94163ca 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -33,8 +33,10 @@ namespace Oqtane.Controllers private readonly IHttpContextAccessor _accessor; private readonly IAliasRepository _aliases; private readonly ILogger _filelogger; + private readonly ITenantManager _tenantManager; + private readonly ServerStateManager _serverState; - public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger filelogger) + public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger filelogger, ITenantManager tenantManager, ServerStateManager serverState) { _configManager = configManager; _installationManager = installationManager; @@ -44,6 +46,8 @@ namespace Oqtane.Controllers _accessor = accessor; _aliases = aliases; _filelogger = filelogger; + _tenantManager = tenantManager; + _serverState = serverState; } // POST api/ @@ -115,7 +119,9 @@ namespace Oqtane.Controllers private List GetAssemblyList() { - return _cache.GetOrCreate("assemblieslist", entry => + int siteId = _tenantManager.GetAlias().SiteId; + + return _cache.GetOrCreate($"assemblieslist:{siteId}", entry => { var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var assemblyList = new List(); @@ -126,78 +132,43 @@ namespace Oqtane.Controllers { hashfilename = false; } - - // get list of assemblies which should be downloaded to client - var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); - var list = assemblies.Select(a => a.GetName().Name).ToList(); - // populate assemblies - for (int i = 0; i < list.Count; i++) + // get site assemblies which should be downloaded to client + var assemblies = _serverState.GetServerState(siteId).Assemblies; + + // populate assembly list + foreach (var assembly in assemblies) { - assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, list[i] + ".dll"), hashfilename)); + if (assembly != Constants.ClientId) + { + var filepath = Path.Combine(binFolder, assembly) + ".dll"; + if (System.IO.File.Exists(filepath)) + { + assemblyList.Add(new ClientAssembly(Path.Combine(binFolder, assembly + ".dll"), hashfilename)); + } + } } // insert satellite assemblies at beginning of list foreach (var culture in _localizationManager.GetInstalledCultures()) { - var assembliesFolderPath = Path.Combine(binFolder, culture); - if (culture == Constants.DefaultCulture) + if (culture != Constants.DefaultCulture) { - continue; - } - - if (Directory.Exists(assembliesFolderPath)) - { - foreach (var resourceFile in Directory.EnumerateFiles(assembliesFolderPath)) + var assembliesFolderPath = Path.Combine(binFolder, culture); + if (Directory.Exists(assembliesFolderPath)) { - assemblyList.Insert(0, new ClientAssembly(resourceFile, hashfilename)); - } - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); - } - } - - // insert module and theme dependencies at beginning of list - foreach (var assembly in assemblies) - { - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule)))) - { - var instance = Activator.CreateInstance(type) as IModule; - foreach (string name in instance.ModuleDefinition.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) - { - var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll"); - if (System.IO.File.Exists(filepath)) + foreach (var assembly in assemblies) { - if (!assemblyList.Exists(item => item.FilePath == filepath)) + var filepath = Path.Combine(assembliesFolderPath, assembly) + ".resources.dll"; + if (System.IO.File.Exists(filepath)) { - assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename)); + assemblyList.Insert(0, new ClientAssembly(Path.Combine(assembliesFolderPath, assembly + ".resources.dll"), hashfilename)); } } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist")); - } } - } - foreach (var type in assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme)))) - { - var instance = Activator.CreateInstance(type) as ITheme; - foreach (string name in instance.Theme.Dependencies.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Reverse()) + else { - var filepath = Path.Combine(binFolder, name.ToLower().EndsWith(".dll") ? name : name + ".dll"); - if (System.IO.File.Exists(filepath)) - { - if (!assemblyList.Exists(item => item.FilePath == filepath)) - { - assemblyList.Insert(0, new ClientAssembly(filepath, hashfilename)); - } - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist")); - } + _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); } } } @@ -240,21 +211,24 @@ namespace Oqtane.Controllers { foreach (var assembly in assemblies) { - if (System.IO.File.Exists(assembly.FilePath)) + if (Path.GetFileNameWithoutExtension(assembly.FilePath) != Constants.ClientId) { - using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(assembly.HashedName).Open()) + if (System.IO.File.Exists(assembly.FilePath)) { - filestream.CopyTo(entrystream); + using (var filestream = new FileStream(assembly.FilePath, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName).Open()) + { + filestream.CopyTo(entrystream); + } } - } - var pdb = assembly.FilePath.Replace(".dll", ".pdb"); - if (System.IO.File.Exists(pdb)) - { - using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read)) - using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open()) + var pdb = assembly.FilePath.Replace(".dll", ".pdb"); + if (System.IO.File.Exists(pdb)) { - filestream.CopyTo(entrystream); + using (var filestream = new FileStream(pdb, FileMode.Open, FileAccess.Read)) + using (var entrystream = archive.CreateEntry(assembly.HashedName.Replace(".dll", ".pdb")).Open()) + { + filestream.CopyTo(entrystream); + } } } } diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index f60a5add..c341155a 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -200,6 +200,8 @@ namespace Oqtane.Controllers case EntityNames.Tenant: case EntityNames.ModuleDefinition: case EntityNames.Host: + case EntityNames.Job: + case EntityNames.Theme: if (permissionName == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Host); @@ -262,6 +264,8 @@ namespace Oqtane.Controllers case EntityNames.Tenant: case EntityNames.ModuleDefinition: case EntityNames.Host: + case EntityNames.Job: + case EntityNames.Theme: filter = !User.IsInRole(RoleNames.Host); break; case EntityNames.Site: diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 2bb65c38..443d7c33 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -61,6 +61,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index ea12ac21..a761bd0f 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -42,119 +42,127 @@ namespace Oqtane.Infrastructure // get site settings List sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList(); Dictionary settings = GetSettings(sitesettings); - if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && - settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && - settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && - settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") + if (!settings.ContainsKey("SMTPEnabled") || settings["SMTPEnabled"] == "True") { - // construct SMTP Client - var client = new SmtpClient() + if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" && + settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" && + settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" && + settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "") { - DeliveryMethod = SmtpDeliveryMethod.Network, - UseDefaultCredentials = false, - Host = settings["SMTPHost"], - Port = int.Parse(settings["SMTPPort"]), - EnableSsl = bool.Parse(settings["SMTPSSL"]) - }; - if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") - { - client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); - } - - // iterate through undelivered notifications - int sent = 0; - List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); - foreach (Notification notification in notifications) - { - // get sender and receiver information from user object if not provided - if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) + // construct SMTP Client + var client = new SmtpClient() { - var user = userRepository.GetUser(notification.FromUserId.Value); - if (user != null) - { - notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; - notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; - } - } - if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) + DeliveryMethod = SmtpDeliveryMethod.Network, + UseDefaultCredentials = false, + Host = settings["SMTPHost"], + Port = int.Parse(settings["SMTPPort"]), + EnableSsl = bool.Parse(settings["SMTPSSL"]) + }; + if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "") { - var user = userRepository.GetUser(notification.ToUserId.Value); - if (user != null) - { - notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; - notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; - } + client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]); } - // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail)) + // iterate through undelivered notifications + int sent = 0; + List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); + foreach (Notification notification in notifications) { - log += "Recipient Missing For NotificationId: " + notification.NotificationId + "
"; - notification.IsDeleted = true; - notificationRepository.UpdateNotification(notification); - } - else - { - MailMessage mailMessage = new MailMessage(); - - // sender - if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail)) + // get sender and receiver information from user object if not provided + if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) { - if (!string.IsNullOrEmpty(notification.FromDisplayName)) + var user = userRepository.GetUser(notification.FromUserId.Value); + if (user != null) { - mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName); + notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; + notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; + } + } + if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) + { + var user = userRepository.GetUser(notification.ToUserId.Value); + if (user != null) + { + notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; + notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; + } + } + + // validate recipient + if (string.IsNullOrEmpty(notification.ToEmail)) + { + log += "Recipient Missing For NotificationId: " + notification.NotificationId + "
"; + notification.IsDeleted = true; + notificationRepository.UpdateNotification(notification); + } + else + { + MailMessage mailMessage = new MailMessage(); + + // sender + if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail)) + { + if (!string.IsNullOrEmpty(notification.FromDisplayName)) + { + mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName); + } + else + { + mailMessage.From = new MailAddress(notification.FromEmail); + } } else { - mailMessage.From = new MailAddress(notification.FromEmail); + mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); + } + + // recipient + if (!string.IsNullOrEmpty(notification.ToDisplayName)) + { + mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); + } + else + { + mailMessage.To.Add(new MailAddress(notification.ToEmail)); + } + + // subject + mailMessage.Subject = notification.Subject; + + //body + mailMessage.Body = notification.Body; + + // encoding + mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; + mailMessage.BodyEncoding = System.Text.Encoding.UTF8; + mailMessage.IsBodyHtml = true; + + // send mail + try + { + client.Send(mailMessage); + sent++; + notification.IsDelivered = true; + notification.DeliveredOn = DateTime.UtcNow; + notificationRepository.UpdateNotification(notification); + } + catch (Exception ex) + { + // error + log += ex.Message + "
"; } } - else - { - mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name); - } - - // recipient - if (!string.IsNullOrEmpty(notification.ToDisplayName)) - { - mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); - } - else - { - mailMessage.To.Add(new MailAddress(notification.ToEmail)); - } - - // subject - mailMessage.Subject = notification.Subject; - - //body - mailMessage.Body = notification.Body; - - // encoding - mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; - mailMessage.BodyEncoding = System.Text.Encoding.UTF8; - - // send mail - try - { - client.Send(mailMessage); - sent++; - notification.IsDelivered = true; - notification.DeliveredOn = DateTime.UtcNow; - notificationRepository.UpdateNotification(notification); - } - catch (Exception ex) - { - // error - log += ex.Message + "
"; - } } + log += "Notifications Delivered: " + sent + "
"; + } + else + { + log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; } - log += "Notifications Delivered: " + sent + "
"; } else { - log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "
"; + log += "SMTP Disabled In Site Settings" + "
"; } } diff --git a/Oqtane.Server/Infrastructure/ServerState.cs b/Oqtane.Server/Infrastructure/ServerState.cs new file mode 100644 index 00000000..c37d5e21 --- /dev/null +++ b/Oqtane.Server/Infrastructure/ServerState.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public class ServerState + { + public int SiteId { get; set; } + public List Assemblies { get; set; } = new List(); + public ListScripts { get; set; } = new List(); + } +} diff --git a/Oqtane.Server/Infrastructure/ServerStateManager.cs b/Oqtane.Server/Infrastructure/ServerStateManager.cs new file mode 100644 index 00000000..630ea691 --- /dev/null +++ b/Oqtane.Server/Infrastructure/ServerStateManager.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + // singleton + public class ServerStateManager + { + private List _serverStates { get; set; } + + public ServerStateManager() + { + _serverStates = new List(); + } + + public ServerState GetServerState(int siteId) + { + var serverState = _serverStates.FirstOrDefault(item => item.SiteId == siteId); + if (serverState == null) + { + serverState = new ServerState(); + serverState.SiteId = siteId; + serverState.Assemblies = new List(); + serverState.Scripts = new List(); + return serverState; + } + else + { + return serverState; + } + } + + public void SetServerState(int siteId, ServerState serverState) + { + var serverstate = _serverStates.FirstOrDefault(item => item.SiteId == siteId); + if (serverstate == null) + { + serverState.SiteId = siteId; + _serverStates.Add(serverState); + } + else + { + serverstate.Assemblies = serverState.Assemblies; + serverstate.Scripts = serverState.Scripts; + } + } + } +} diff --git a/Oqtane.Server/Migrations/Master/04000002_AddThemeName.cs b/Oqtane.Server/Migrations/Master/04000002_AddThemeName.cs new file mode 100644 index 00000000..90d02cc6 --- /dev/null +++ b/Oqtane.Server/Migrations/Master/04000002_AddThemeName.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Master +{ + [DbContext(typeof(MasterDBContext))] + [Migration("Master.04.00.00.02")] + public class AddThemeName : MultiDatabaseMigration + { + public AddThemeName(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var themeEntityBuilder = new ThemeEntityBuilder(migrationBuilder, ActiveDatabase); + themeEntityBuilder.AddStringColumn("Name", 200, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/04000003_AddProfileValidation.cs b/Oqtane.Server/Migrations/Tenant/04000003_AddProfileValidation.cs new file mode 100644 index 00000000..c2f46cb5 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/04000003_AddProfileValidation.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Components.Web; +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.04.00.00.03")] + public class AddProfileValidation : MultiDatabaseMigration + { + public AddProfileValidation(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var profileEntityBuilder = new ProfileEntityBuilder(migrationBuilder, ActiveDatabase); + profileEntityBuilder.AddStringColumn("Validation", 200, true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Pages/_Host.cshtml.cs b/Oqtane.Server/Pages/_Host.cshtml.cs index 180115ba..2cae21ba 100644 --- a/Oqtane.Server/Pages/_Host.cshtml.cs +++ b/Oqtane.Server/Pages/_Host.cshtml.cs @@ -36,9 +36,10 @@ namespace Oqtane.Pages private readonly IVisitorRepository _visitors; private readonly IAliasRepository _aliases; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly ILogManager _logger; - public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ILogManager logger) + public HostModel(IConfigManager configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, IJwtManager jwtManager, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors, IAliasRepository aliases, ISettingRepository settings, ServerStateManager serverState, ILogManager logger) { _configuration = configuration; _tenantManager = tenantManager; @@ -52,6 +53,7 @@ namespace Oqtane.Pages _visitors = visitors; _aliases = aliases; _settings = settings; + _serverState = serverState; _logger = logger; } @@ -117,33 +119,11 @@ namespace Oqtane.Pages { Runtime = site.Runtime; } - if (!string.IsNullOrEmpty(site.RenderMode)) + if (!string.IsNullOrEmpty(site.RenderMode)) { RenderMode = site.RenderMode; } - if (Runtime == "Server") - { - ReconnectScript = CreateReconnectScript(); - } - if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) - { - PWAScript = CreatePWAScript(alias, site, route); - } - // site level scripts - HeadResources += ParseScripts(site.HeadContent); - BodyResources += ParseScripts(site.BodyContent); - - // get jwt token for downstream APIs - if (User.Identity.IsAuthenticated) - { - var sitesettings = HttpContext.GetSiteSettings(); - var secret = sitesettings.GetValue("JwtOptions:Secret", ""); - if (!string.IsNullOrEmpty(secret)) - { - AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); - } - } - + if (site.VisitorTracking) { TrackVisitor(site.SiteId); @@ -172,11 +152,33 @@ namespace Oqtane.Pages } } - // include global resources - var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies(); - foreach (Assembly assembly in assemblies) + // get jwt token for downstream APIs + if (User.Identity.IsAuthenticated) { - ProcessHostResources(assembly, alias); + var sitesettings = HttpContext.GetSiteSettings(); + var secret = sitesettings.GetValue("JwtOptions:Secret", ""); + if (!string.IsNullOrEmpty(secret)) + { + AuthorizationToken = _jwtManager.GenerateToken(alias, (ClaimsIdentity)User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); + } + } + + // inject scripts + if (Runtime == "Server") + { + ReconnectScript = CreateReconnectScript(); + } + if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) + { + PWAScript = CreatePWAScript(alias, site, route); + } + HeadResources += ParseScripts(site.HeadContent); + BodyResources += ParseScripts(site.BodyContent); + _sites.InitializeSite(site.SiteId); // populates server state + var scripts = _serverState.GetServerState(site.SiteId).Scripts; + foreach (var script in scripts) + { + AddScript(script, alias); } // set culture if not specified @@ -409,20 +411,6 @@ namespace Oqtane.Pages ""; } - private void ProcessHostResources(Assembly assembly, Alias alias) - { - var types = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IHostResources))); - foreach (var type in types) - { - var obj = Activator.CreateInstance(type) as IHostResources; - foreach (var resource in obj.Resources) - { - resource.Level = ResourceLevel.App; - ProcessResource(resource, 0, alias); - } - } - } - private string ParseScripts(string headcontent) { // iterate scripts @@ -439,60 +427,39 @@ namespace Oqtane.Pages return scripts; } - private void ProcessResource(Resource resource, int count, Alias alias) + private void AddScript(Resource resource, Alias alias) { - var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; - switch (resource.ResourceType) + var script = CreateScript(resource, alias); + if (resource.Location == Shared.ResourceLocation.Head) { - case ResourceType.Stylesheet: - if (!HeadResources.Contains(url, StringComparison.OrdinalIgnoreCase)) - { - string id = ""; - if (resource.Level == ResourceLevel.Page) - { - id = "id=\"app-stylesheet-" + resource.Level.ToString().ToLower() + "-" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + count.ToString("00") + "\" "; - } - HeadResources += "" + Environment.NewLine; - } - break; - case ResourceType.Script: - if (resource.Location == Shared.ResourceLocation.Body) - { - if (!BodyResources.Contains(url, StringComparison.OrdinalIgnoreCase)) - { - BodyResources += "" + Environment.NewLine; - } - } - else - { - if (!HeadResources.Contains(resource.Url, StringComparison.OrdinalIgnoreCase)) - { - HeadResources += "" + Environment.NewLine; - } - } - break; - } - } - private string CrossOrigin(string crossorigin) - { - if (!string.IsNullOrEmpty(crossorigin)) - { - return " crossorigin=\"" + crossorigin + "\""; + if (!HeadResources.Contains(script)) + { + HeadResources += script + Environment.NewLine; + } } else { - return ""; + if (!BodyResources.Contains(script)) + { + BodyResources += script + Environment.NewLine; + } } } - private string Integrity(string integrity) + + private string CreateScript(Resource resource, Alias alias) { - if (!string.IsNullOrEmpty(integrity)) + if (!string.IsNullOrEmpty(resource.Url)) { - return " integrity=\"" + integrity + "\""; + var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; + return ""; } else { - return ""; + // inline script + return ""; } } diff --git a/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs b/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs index 8172efff..ebe3294b 100644 --- a/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISiteRepository.cs @@ -11,6 +11,7 @@ namespace Oqtane.Repository Site GetSite(int siteId); Site GetSite(int siteId, bool tracking); void DeleteSite(int siteId); + void InitializeSite(int siteId); void CreatePages(Site site, List pageTemplates); } } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 1d0c45f8..0104d8ea 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -4,12 +4,14 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Xml; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Modules; using Oqtane.Shared; +using Oqtane.Themes; namespace Oqtane.Repository { @@ -20,15 +22,17 @@ namespace Oqtane.Repository private readonly IPermissionRepository _permissions; private readonly ITenantManager _tenants; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly string settingprefix = "SiteEnabled:"; - public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings) + public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, ServerStateManager serverState) { _db = context; _cache = cache; _permissions = permissions; _tenants = tenants; _settings = settings; + _serverState = serverState; } public IEnumerable GetModuleDefinitions() @@ -182,6 +186,7 @@ namespace Oqtane.Repository var settings = _settings.GetSettings(EntityNames.ModuleDefinition).ToList(); // populate module definition site settings and permissions + var serverState = _serverState.GetServerState(siteId); foreach (ModuleDefinition moduledefinition in ModuleDefinitions) { moduledefinition.SiteId = siteId; @@ -196,6 +201,36 @@ namespace Oqtane.Repository moduledefinition.IsEnabled = moduledefinition.IsAutoEnabled; } + if (moduledefinition.IsEnabled) + { + // build list of assemblies for site + if (!serverState.Assemblies.Contains(moduledefinition.AssemblyName)) + { + serverState.Assemblies.Add(moduledefinition.AssemblyName); + } + if (!string.IsNullOrEmpty(moduledefinition.Dependencies)) + { + foreach (var assembly in moduledefinition.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse()) + { + if (!serverState.Assemblies.Contains(assembly)) + { + serverState.Assemblies.Insert(0, assembly); + } + } + } + // build list of scripts for site + if (moduledefinition.Resources != null) + { + foreach (var resource in moduledefinition.Resources.Where(item => item.Level == ResourceLevel.Site)) + { + if (!serverState.Scripts.Contains(resource)) + { + serverState.Scripts.Add(resource); + } + } + } + } + if (permissions.Count == 0) { // no module definition permissions exist for this site @@ -216,6 +251,7 @@ namespace Oqtane.Repository } } } + _serverState.SetServerState(siteId, serverState); // clean up any orphaned permissions var ids = new HashSet(ModuleDefinitions.Select(item => item.ModuleDefinitionId)); @@ -295,6 +331,16 @@ namespace Oqtane.Repository moduledefinition.ModuleDefinitionName = qualifiedModuleType; moduledefinition.ControlTypeTemplate = modulecontroltype.Namespace + "." + Constants.ActionToken + ", " + modulecontroltype.Assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name; + if (moduledefinition.Resources != null) + { + foreach (var resource in moduledefinition.Resources) + { + if (resource.Url.StartsWith("~")) + { + resource.Url = resource.Url.Replace("~", "/Modules/" + Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "/").Replace("//", "/"); + } + } + } moduledefinition.IsPortable = false; if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) @@ -313,10 +359,21 @@ namespace Oqtane.Repository if (moduledefinition.Categories == "Admin") { - moduledefinition.PermissionList = new List + var shortName = moduledefinition.ModuleDefinitionName.Replace("Oqtane.Modules.Admin.", "").Replace(", Oqtane.Client", ""); + if (Constants.DefaultHostModuleTypes.Contains(shortName)) { - new Permission(PermissionNames.Utilize, RoleNames.Admin, true) - }; + moduledefinition.PermissionList = new List + { + new Permission(PermissionNames.Utilize, RoleNames.Host, true) + }; + } + else + { + moduledefinition.PermissionList = new List + { + new Permission(PermissionNames.Utilize, RoleNames.Admin, true) + }; + } } else { diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index b17ca189..492b3af0 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -24,11 +24,12 @@ namespace Oqtane.Repository private readonly IModuleRepository _moduleRepository; private readonly IPageModuleRepository _pageModuleRepository; private readonly IModuleDefinitionRepository _moduleDefinitionRepository; + private readonly IThemeRepository _themeRepository; private readonly IServiceProvider _serviceProvider; private readonly IConfigurationRoot _config; public SiteRepository(TenantDBContext context, IRoleRepository roleRepository, IProfileRepository profileRepository, IFolderRepository folderRepository, IPageRepository pageRepository, - IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IServiceProvider serviceProvider, + IModuleRepository moduleRepository, IPageModuleRepository pageModuleRepository, IModuleDefinitionRepository moduleDefinitionRepository, IThemeRepository themeRepository, IServiceProvider serviceProvider, IConfigurationRoot config) { _db = context; @@ -39,6 +40,7 @@ namespace Oqtane.Repository _moduleRepository = moduleRepository; _pageModuleRepository = pageModuleRepository; _moduleDefinitionRepository = moduleDefinitionRepository; + _themeRepository = themeRepository; _serviceProvider = serviceProvider; _config = config; } @@ -88,6 +90,12 @@ namespace Oqtane.Repository _db.SaveChanges(); } + public void InitializeSite(int siteId) + { + _moduleDefinitionRepository.GetModuleDefinitions(siteId); + _themeRepository.GetThemes(); + } + private void CreateSite(Site site) { // create default entities for site diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 413ef6bb..cf1aa1bd 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -12,6 +12,7 @@ using Oqtane.Models; using Oqtane.Shared; using Oqtane.Themes; using System.Reflection.Metadata; +using Oqtane.Migrations.Master; namespace Oqtane.Repository { @@ -21,14 +22,16 @@ namespace Oqtane.Repository private readonly IMemoryCache _cache; private readonly ITenantManager _tenants; private readonly ISettingRepository _settings; + private readonly ServerStateManager _serverState; private readonly string settingprefix = "SiteEnabled:"; - public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings) + public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings, ServerStateManager serverState) { _db = context; _cache = cache; _tenants = tenants; _settings = settings; + _serverState = serverState; } public IEnumerable GetThemes() @@ -123,6 +126,12 @@ namespace Oqtane.Repository } else { + // override user customizable property values + Theme.Name = (!string.IsNullOrEmpty(theme.Name)) ? theme.Name : Theme.Name; + foreach (var themecontrol in Theme.Themes) + { + themecontrol.Name = Theme.Name + " - " + themecontrol.Name; + } // remove theme from list as it is already synced themes.Remove(theme); } @@ -147,6 +156,7 @@ namespace Oqtane.Repository var settings = _settings.GetSettings(EntityNames.Theme).ToList(); // populate theme site settings + var serverState = _serverState.GetServerState(siteId); foreach (Theme theme in Themes) { theme.SiteId = siteId; @@ -160,7 +170,38 @@ namespace Oqtane.Repository { theme.IsEnabled = theme.IsAutoEnabled; } + + if (theme.IsEnabled) + { + // build list of assemblies for site + if (!serverState.Assemblies.Contains(theme.AssemblyName)) + { + serverState.Assemblies.Add(theme.AssemblyName); + } + if (!string.IsNullOrEmpty(theme.Dependencies)) + { + foreach (var assembly in theme.Dependencies.Replace(".dll", "").Split(',', StringSplitOptions.RemoveEmptyEntries).Reverse()) + { + if (!serverState.Assemblies.Contains(assembly)) + { + serverState.Assemblies.Insert(0, assembly); + } + } + } + // build list of scripts for site + if (theme.Resources != null) + { + foreach (var resource in theme.Resources.Where(item => item.Level == ResourceLevel.Site)) + { + if (!serverState.Scripts.Contains(resource)) + { + serverState.Scripts.Add(resource); + } + } + } + } } + _serverState.SetServerState(siteId, serverState); } return Themes; @@ -225,12 +266,22 @@ namespace Oqtane.Repository Version = new Version(1, 0, 0).ToString() }; } + // set internal properties theme.ThemeName = qualifiedThemeType; theme.Themes = new List(); theme.Containers = new List(); theme.AssemblyName = assembly.FullName.Split(",")[0]; - + if (theme.Resources != null) + { + foreach (var resource in theme.Resources) + { + if (resource.Url.StartsWith("~")) + { + resource.Url = resource.Url.Replace("~", "/Themes/" + Utilities.GetTypeName(theme.ThemeName) + "/").Replace("//", "/"); + } + } + } Debug.WriteLine($"Oqtane Info: Registering Theme {theme.ThemeName}"); themes.Add(theme); index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType); @@ -242,7 +293,7 @@ namespace Oqtane.Repository new ThemeControl { TypeName = themeControlType.FullName + ", " + themeControlType.Assembly.GetName().Name, - Name = theme.Name + " - " + ((string.IsNullOrEmpty(themecontrolobject.Name)) ? Utilities.GetTypeNameLastSegment(themeControlType.FullName, 0) : themecontrolobject.Name), + Name = ((string.IsNullOrEmpty(themecontrolobject.Name)) ? Utilities.GetTypeNameLastSegment(themeControlType.FullName, 0) : themecontrolobject.Name), Thumbnail = themecontrolobject.Thumbnail, Panes = themecontrolobject.Panes } diff --git a/Oqtane.Shared/Enums/ResourceLevel.cs b/Oqtane.Shared/Enums/ResourceLevel.cs index 2c839c85..5fb9c9af 100644 --- a/Oqtane.Shared/Enums/ResourceLevel.cs +++ b/Oqtane.Shared/Enums/ResourceLevel.cs @@ -3,6 +3,7 @@ namespace Oqtane.Shared public enum ResourceLevel { App, + Site, Page, Module } diff --git a/Oqtane.Shared/Models/ModuleDefinition.cs b/Oqtane.Shared/Models/ModuleDefinition.cs index b3c3339a..961855a8 100644 --- a/Oqtane.Shared/Models/ModuleDefinition.cs +++ b/Oqtane.Shared/Models/ModuleDefinition.cs @@ -48,7 +48,7 @@ namespace Oqtane.Models public string ModuleDefinitionName { get; set; } /// - /// Nice name to show in admin / edit dialogs. + /// Friendly name to show in UI /// public string Name { get; set; } diff --git a/Oqtane.Shared/Models/Profile.cs b/Oqtane.Shared/Models/Profile.cs index 00c4ea88..e5586a13 100644 --- a/Oqtane.Shared/Models/Profile.cs +++ b/Oqtane.Shared/Models/Profile.cs @@ -68,5 +68,10 @@ namespace Oqtane.Models /// This gives possible values for dropdown input fields. /// public string Options { get; set; } + + /// + /// Optional RegExp validation expression + /// + public string Validation { get; set; } } } diff --git a/Oqtane.Shared/Models/Theme.cs b/Oqtane.Shared/Models/Theme.cs index b795e45f..4ff9fe2f 100644 --- a/Oqtane.Shared/Models/Theme.cs +++ b/Oqtane.Shared/Models/Theme.cs @@ -35,10 +35,12 @@ namespace Oqtane.Models /// public string ThemeName { get; set; } - // additional ITheme properties - [NotMapped] + /// + /// Friendly name to show in UI + /// public string Name { get; set; } + // additional ITheme properties [NotMapped] public string Version { get; set; } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index f9474777..916fa310 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -1,4 +1,7 @@ using System; +using Oqtane.Models; +using System.Collections.Generic; +using System.Runtime.InteropServices; namespace Oqtane.Shared { @@ -39,6 +42,8 @@ namespace Oqtane.Shared public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server"; + public static readonly string[] DefaultHostModuleTypes = new[] { "Upgrade", "Themes", "SystemInfo", "Sql", "Sites", "ModuleDefinitions", "Logs", "Jobs", "ModuleCreator" }; + public const string FileUrl = "/files/"; public const string ImageUrl = "/api/file/image/"; public const int UserFolderCapacity = 20; // megabytes diff --git a/README.md b/README.md index 768300e5..c66059ba 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ This project is open source, and therefore is a work in progress... Backlog (TBD) - [ ] Azure Autoscale support (ie. web farm) - [ ] Routable Modules (ie. declarative configuration) -- [ ] Client Assembly Loading / Site - [ ] Folder Providers - [ ] Generative AI Integration @@ -56,7 +55,9 @@ Backlog (TBD) - [ ] Migration to .NET 8 4.0.0 (Q2 2023) -- [ ] Migration to .NET 7 +- [x] Migration to .NET 7 +- [x] Improved JavaScript, CSS, and Meta support +- [x] Optimized Client Assembly Loading [3.4.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3) ( May 3, 2023 ) - [x] Stabilization improvements