From 93057d9449d2904b2577be57e82782a8db73ffec Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 29 Mar 2024 07:50:03 -0400 Subject: [PATCH 001/189] set active tab correctly in RichTextEditor for scenarios where rich text editor is disabled --- Oqtane.Client/Modules/Controls/RichTextEditor.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index b99032a4..e59cc893 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -159,7 +159,7 @@ _originalrichhtml = ""; // Quill wraps content in

tags which can be used as a signal to set the active tab - if (!string.IsNullOrEmpty(Content) && !Content.StartsWith("

") && AllowRawHtml) + if (!AllowRichText || (AllowRawHtml && !string.IsNullOrEmpty(Content) && !Content.StartsWith("

"))) { _activetab = "Raw"; } From 26220b2f5468ec03fe364db5453c495f1d4d0ef3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 29 Mar 2024 10:15:24 -0400 Subject: [PATCH 002/189] remove changes to allow path to support urls - urls should be specified as redirects --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 62 +++++++------------ Oqtane.Client/Modules/Admin/Pages/Edit.razor | 64 +++++++------------- 2 files changed, 45 insertions(+), 81 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 9009ba29..edaebf1a 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -385,45 +385,34 @@ page.ParentId = Int32.Parse(_parentid); } - // path can be a link to an external url - if (!_path.Contains("://")) + if (string.IsNullOrEmpty(_path)) { - if (string.IsNullOrEmpty(_path)) - { - _path = _name; - } + _path = _name; + } - (_path, string parameters) = Utilities.ParsePath(_path); - - if (_path.Contains("/")) + if (_path.Contains("/")) + { + if (_path.EndsWith("/") && _path != "/") { - if (_path.EndsWith("/") && _path != "/") - { - _path = _path.Substring(0, _path.Length - 1); - } - _path = _path.Substring(_path.LastIndexOf("/") + 1); + _path = _path.Substring(0, _path.Length - 1); } - if (_parentid == "-1") - { - page.Path = Utilities.GetFriendlyUrl(_path); - } - else - { - Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId); - if (parent.Path == string.Empty) - { - page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); - } - else - { - page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); - } - } - page.Path += parameters; + _path = _path.Substring(_path.LastIndexOf("/") + 1); + } + if (_parentid == "-1") + { + page.Path = Utilities.GetFriendlyUrl(_path); } else { - page.Path = _path; + Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId); + if (parent.Path == string.Empty) + { + page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); + } + else + { + page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); + } } var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); @@ -497,14 +486,7 @@ } else { - if (!page.Path.Contains("://")) - { - NavigationManager.NavigateTo(page.Path); // redirect to new page created - } - else - { - NavigationManager.NavigateTo(NavigateUrl("admin/pages")); - } + NavigationManager.NavigateTo(page.Path); // redirect to new page created } } else diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 46ba727b..453f1f69 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -380,7 +380,7 @@ } else { - if (_path.Contains("/") & !_path.Contains("://")) + if (_path.Contains("/")) { _path = _path.Substring(_path.LastIndexOf("/") + 1); } @@ -529,45 +529,34 @@ _page.ParentId = Int32.Parse(_parentid); } - // path can be a link to an external url - if (!_path.Contains("://")) + if (string.IsNullOrEmpty(_path)) { - if (string.IsNullOrEmpty(_path)) - { - _path = _name; - } + _path = _name; + } - (_path, string parameters) = Utilities.ParsePath(_path); - - if (_path.Contains("/")) + if (_path.Contains("/")) + { + if (_path.EndsWith("/") && _path != "/") { - if (_path.EndsWith("/") && _path != "/") - { - _path = _path.Substring(0, _path.Length - 1); - } - _path = _path.Substring(_path.LastIndexOf("/") + 1); + _path = _path.Substring(0, _path.Length - 1); } - if (_parentid == "-1") - { - _page.Path = Utilities.GetFriendlyUrl(_path); - } - else - { - Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); - if (parent.Path == string.Empty) - { - _page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); - } - else - { - _page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); - } - } - _page.Path += parameters; + _path = _path.Substring(_path.LastIndexOf("/") + 1); + } + if (_parentid == "-1") + { + _page.Path = Utilities.GetFriendlyUrl(_path); } else { - _page.Path = _path; + Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId); + if (parent.Path == string.Empty) + { + _page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); + } + else + { + _page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); + } } var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId); @@ -658,14 +647,7 @@ } else { - if (!_page.Path.Contains("://")) - { - NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited - } - else - { - NavigationManager.NavigateTo(NavigateUrl("admin/pages")); - } + NavigationManager.NavigateTo(NavigateUrl(), true); // redirect to page being edited } } else From 5b3849082fbb5bbf41cd04dec06749d25a7f2f69 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 29 Mar 2024 11:19:21 -0400 Subject: [PATCH 003/189] use Constants.RequestVerificationToken rather than magic string --- Oqtane.Client/Modules/Controls/ActionDialog.razor | 8 ++++---- Oqtane.Client/Modules/Controls/ModuleMessage.razor | 2 +- Oqtane.Client/Modules/Controls/Pager.razor | 2 +- Oqtane.Client/Themes/AdminContainer.razor | 2 +- Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor | 2 +- Oqtane.Client/Themes/Controls/Theme/Login.razor | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor index f21d5a64..1125d7b0 100644 --- a/Oqtane.Client/Modules/Controls/ActionDialog.razor +++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor @@ -54,7 +54,7 @@ else

@@ -65,12 +65,12 @@ else @if (!string.IsNullOrEmpty(Action)) {
- +
}
- +
@@ -88,7 +88,7 @@ else else {
- +
} diff --git a/Oqtane.Client/Modules/Controls/ModuleMessage.razor b/Oqtane.Client/Modules/Controls/ModuleMessage.razor index 7771a7a9..d8155b3d 100644 --- a/Oqtane.Client/Modules/Controls/ModuleMessage.razor +++ b/Oqtane.Client/Modules/Controls/ModuleMessage.razor @@ -13,7 +13,7 @@ View Details }
- +
} diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index fd593c73..a37166ec 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -73,7 +73,7 @@ @if (!string.IsNullOrEmpty(SearchProperties)) {
- +
diff --git a/Oqtane.Client/Themes/AdminContainer.razor b/Oqtane.Client/Themes/AdminContainer.razor index 6231023c..60deaa59 100644 --- a/Oqtane.Client/Themes/AdminContainer.razor +++ b/Oqtane.Client/Themes/AdminContainer.razor @@ -9,7 +9,7 @@ diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index fad09a2b..607743bf 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -12,7 +12,7 @@ @if (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))) {
- + @if (PageState.EditMode) {
From 650c6670f2065fd1c531f34760b813d6d94d9ca1 Mon Sep 17 00:00:00 2001 From: Jon Welfringer <7365166+W6HBR@users.noreply.github.com> Date: Sun, 31 Mar 2024 10:08:19 -0700 Subject: [PATCH 004/189] Fix incorrect parameter passed from ProfileService.cs to ProfileController.cs ProfileService was passing SiteId instead of ProfileId which was causing updates to profile entries to fail with "Unauthorized Profile Put Attempt". --- Oqtane.Client/Services/ProfileService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Services/ProfileService.cs b/Oqtane.Client/Services/ProfileService.cs index 5f0f1080..700fb764 100644 --- a/Oqtane.Client/Services/ProfileService.cs +++ b/Oqtane.Client/Services/ProfileService.cs @@ -33,7 +33,7 @@ namespace Oqtane.Services public async Task UpdateProfileAsync(Profile profile) { - return await PutJsonAsync($"{Apiurl}/{profile.SiteId}", profile); + return await PutJsonAsync($"{Apiurl}/{profile.ProfileId}", profile); } public async Task DeleteProfileAsync(int profileId) { From 9843dccdf0a506e55dac1b756cbe17735c53d5c3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 1 Apr 2024 12:00:53 -0400 Subject: [PATCH 005/189] fix #4088 - redirect to login if not authenticated --- Oqtane.Client/UI/SiteRouter.razor | 154 ++++++++++++++---------------- 1 file changed, 73 insertions(+), 81 deletions(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 4c6ce291..967492d3 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -251,105 +251,97 @@ } } - if (page != null) + // check if user is authorized to view page + if (page != null && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))) { - // check if user is authorized to view page - if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))) + // edit mode + if (user != null) { - // edit mode - if (user != null) + if (querystring.ContainsKey("editmode") && querystring["edit"] == "true") { - if (querystring.ContainsKey("editmode") && querystring["edit"] == "true") + editmode = true; + } + else + { + editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1)); + if (!editmode) { - editmode = true; - } - else - { - editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"]) : -1)); - if (!editmode) - { - var userSettings = new Dictionary { { "CP-editmode", "-1" } }; - await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); - } + var userSettings = new Dictionary { { "CP-editmode", "-1" } }; + await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId); } } - - // load additional metadata for current page - page = ProcessPage(page, site, user, SiteState.Alias); + } - // load additional metadata for modules - (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); + // load additional metadata for current page + page = ProcessPage(page, site, user, SiteState.Alias); - // populate page state (which acts as a client-side cache for subsequent requests) - _pagestate = new PageState + // load additional metadata for modules + (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); + + // populate page state (which acts as a client-side cache for subsequent requests) + _pagestate = new PageState + { + Alias = SiteState.Alias, + Site = site, + Page = page, + User = user, + Uri = new Uri(_absoluteUri, UriKind.Absolute), + Route = route, + QueryString = querystring, + UrlParameters = route.UrlParameters, + ModuleId = moduleid, + Action = action, + EditMode = editmode, + LastSyncDate = lastsyncdate, + RenderMode = RenderMode, + Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime), + VisitorId = visitorId, + RemoteIPAddress = SiteState.RemoteIPAddress, + ReturnUrl = returnurl, + IsInternalNavigation = _isInternalNavigation, + RenderId = Guid.NewGuid(), + Refresh = false + }; + OnStateChange?.Invoke(_pagestate); + + if (PageState.RenderMode == RenderModes.Interactive) + { + await ScrollToFragment(_pagestate.Uri); + } + } + else + { + if (page == null) + { + // check for url mapping + var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); + if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) { - Alias = SiteState.Alias, - Site = site, - Page = page, - User = user, - Uri = new Uri(_absoluteUri, UriKind.Absolute), - Route = route, - QueryString = querystring, - UrlParameters = route.UrlParameters, - ModuleId = moduleid, - Action = action, - EditMode = editmode, - LastSyncDate = lastsyncdate, - RenderMode = RenderMode, - Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), Runtime), - VisitorId = visitorId, - RemoteIPAddress = SiteState.RemoteIPAddress, - ReturnUrl = returnurl, - IsInternalNavigation = _isInternalNavigation, - RenderId = Guid.NewGuid(), - Refresh = false - }; - OnStateChange?.Invoke(_pagestate); - - if (PageState.RenderMode == RenderModes.Interactive) - { - await ScrollToFragment(_pagestate.Uri); + var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query; + NavigationManager.NavigateTo(url, false); + return; } } else - { - // Need to redirect 404 as page doesnot exist in a Permission or Timeframe - if (route.PagePath != "404") - { - // redirect to 404 page - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); - } - } - } - else // page not found - { - // look for url mapping - var urlMapping = await UrlMappingService.GetUrlMappingAsync(site.SiteId, route.PagePath); - if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) - { - var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl + route.Query; - NavigationManager.NavigateTo(url, false); - } - else // not mapped { if (user == null) { // redirect to login page if user not logged in as they may need to be authenticated NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "login", "?returnurl=" + WebUtility.UrlEncode(route.PathAndQuery))); + return; } - else - { - if (route.PagePath != "404") - { - // redirect to 404 page - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); - } - else - { - // redirect to home page as a fallback - NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); - } - } + } + + // page not found or user does not have sufficient access + if (route.PagePath != "404") + { + // redirect to 404 page + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "404", "")); + } + else + { + // redirect to home page as a fallback + NavigationManager.NavigateTo(Utilities.NavigateUrl(SiteState.Alias.Path, "", "")); } } } From e600da229ca654cd02e8e2a852ae1a69ecd31453 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 1 Apr 2024 15:11:20 -0400 Subject: [PATCH 006/189] fix #4091 - double slash generated for home page path ("/") and urlparameters --- Oqtane.Shared/Shared/Utilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 49905c1e..46616c74 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -80,8 +80,8 @@ namespace Oqtane.Shared // add urlparameters to path if (!string.IsNullOrEmpty(urlparameters)) { - if (urlparameters.StartsWith("/")) urlparameters = urlparameters.Remove(0, 1); - path += $"/{Constants.UrlParametersDelimiter}/{urlparameters}"; + if (urlparameters.StartsWith("/")) urlparameters = urlparameters.Substring(1); + path += (path.EndsWith("/") ? "" : "/") + $"{Constants.UrlParametersDelimiter}/{urlparameters}"; } // build url From 4944a9e51ee552212195330e71e7206b6029a240 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Apr 2024 08:44:51 -0400 Subject: [PATCH 007/189] fix SiteMap so that it supports page Urls --- Oqtane.Server/Pages/Sitemap.cshtml.cs | 13 ++++- Oqtane.Shared/Shared/Utilities.cs | 76 +++++++-------------------- 2 files changed, 31 insertions(+), 58 deletions(-) diff --git a/Oqtane.Server/Pages/Sitemap.cshtml.cs b/Oqtane.Server/Pages/Sitemap.cshtml.cs index 461bc3d6..7ba00eb4 100644 --- a/Oqtane.Server/Pages/Sitemap.cshtml.cs +++ b/Oqtane.Server/Pages/Sitemap.cshtml.cs @@ -47,14 +47,23 @@ namespace Oqtane.Pages var sitemap = new List(); // build site map + var rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/"))); var moduleDefinitions = _moduleDefinitions.GetModuleDefinitions(_alias.SiteId).ToList(); var pageModules = _pageModules.GetPageModules(_alias.SiteId); foreach (var page in _pages.GetPages(_alias.SiteId)) { if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.PermissionList) && page.IsNavigation) { - var rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/"))); - sitemap.Add(new Sitemap { Url = rooturl + Utilities.NavigateUrl(_alias.Path, page.Path, ""), ModifiedOn = DateTime.UtcNow }); + var pageurl = rooturl; + if (string.IsNullOrEmpty(page.Url)) + { + pageurl += Utilities.NavigateUrl(_alias.Path, page.Path, ""); + } + else + { + pageurl += (page.Url.StartsWith("/") ? "" : "/") + page.Url; + } + sitemap.Add(new Sitemap { Url = rooturl + pageurl, ModifiedOn = DateTime.UtcNow }); foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) { diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 46616c74..00cfb3f6 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -21,86 +21,50 @@ namespace Oqtane.Shared return $"{type.Namespace}, {assemblyName}"; } - public static (string UrlParameters, string Querystring, string Fragment) ParseParameters(string url) + public static (string UrlParameters, string Querystring, string Fragment) ParseParameters(string parameters) { - // /path/urlparameters // /urlparameters /urlparameters?Id=1 /urlparameters#5 /urlparameters?Id=1#5 /urlparameters?reload#5 // Id=1 Id=1#5 reload#5 reload // #5 - if (!url.Contains("://")) - { - if (!url.StartsWith("/")) // urlparameters always start with "/" - { - url = ((!url.StartsWith("#")) ? "?" : "/") + url; - } - url = Constants.PackageRegistryUrl + url; // create absolute url - } - - var uri = new Uri(url); + // create absolute url to convert to Uri + parameters = (!parameters.StartsWith("/") && !parameters.StartsWith("#") ? "?" : "") + parameters; + parameters = Constants.PackageRegistryUrl + parameters; + var uri = new Uri(parameters); var querystring = uri.Query.Replace("?", ""); var fragment = uri.Fragment.Replace("#", ""); var urlparameters = uri.LocalPath; urlparameters = (urlparameters == "/") ? "" : urlparameters; - if (urlparameters.Contains(Constants.UrlParametersDelimiter)) - { - urlparameters = urlparameters.Substring(urlparameters.IndexOf(Constants.UrlParametersDelimiter) + 1); - } - return (urlparameters, querystring, fragment); } - public static (string Path, string Parameters) ParsePath(string url) - { - url = ((!url.StartsWith("/") && !url.Contains("://")) ? "/" : "") + url; - - (string path, string querystring, string fragment) = ParseParameters(url); - - var uriBuilder = new UriBuilder - { - Path = path, - Query = querystring, - Fragment = fragment - }; - - return (uriBuilder.Path, uriBuilder.Uri.Query + uriBuilder.Uri.Fragment); - } - public static string NavigateUrl(string alias, string path, string parameters) { + string querystring = ""; + string fragment = ""; + if (!string.IsNullOrEmpty(parameters)) { - // parse path - (path, _) = ParsePath(path); - // parse parameters - (string urlparameters, string querystring, string fragment) = ParseParameters(parameters); - - // add urlparameters to path + (string urlparameters, querystring, fragment) = ParseParameters(parameters); if (!string.IsNullOrEmpty(urlparameters)) { - if (urlparameters.StartsWith("/")) urlparameters = urlparameters.Substring(1); - path += (path.EndsWith("/") ? "" : "/") + $"{Constants.UrlParametersDelimiter}/{urlparameters}"; + path += (path.EndsWith("/") ? "" : "/") + $"{Constants.UrlParametersDelimiter}/{urlparameters.Substring(1)}"; } - - // build url - var uriBuilder = new UriBuilder - { - Path = !string.IsNullOrEmpty(alias) - ? (!string.IsNullOrEmpty(path)) ? $"{alias}{path}": $"{alias}" - : $"{path}", - Query = querystring, - Fragment = fragment - }; - path = uriBuilder.Uri.PathAndQuery; } - else + + // build url + var uriBuilder = new UriBuilder { - path = ((!string.IsNullOrEmpty(alias)) ? alias + (!path.StartsWith("/") ? "/" : "") : "") + path; - } + Path = !string.IsNullOrEmpty(alias) + ? (!string.IsNullOrEmpty(path)) ? $"{alias}{path}": $"{alias}" + : $"{path}", + Query = querystring, + Fragment = fragment + }; - return path; + return uriBuilder.Uri.PathAndQuery; } public static string EditUrl(string alias, string path, int moduleid, string action, string parameters) From 010e4610f7c111e3a80fb19f146b14b3fa02051f Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Apr 2024 10:53:07 -0400 Subject: [PATCH 008/189] fix ThemeSettings SetSetting() references to not specify IsPrivate property --- Oqtane.Client/Themes/OqtaneTheme/Themes/ThemeSettings.razor | 6 +++--- .../Templates/External/Client/Themes/ThemeSettings.razor | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/ThemeSettings.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/ThemeSettings.razor index ca6c5d9c..d488e108 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/ThemeSettings.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/ThemeSettings.razor @@ -121,15 +121,15 @@ var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); if (_login != "-") { - settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true); + settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login); } if (_register != "-") { - settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true); + settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register); } if (_footer != "-") { - settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer, true); + settings = SettingService.SetSetting(settings, GetType().Namespace + ":Footer", _footer); } await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); } diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/Themes/ThemeSettings.razor b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/Themes/ThemeSettings.razor index 8db32e1f..3c9d2723 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/Themes/ThemeSettings.razor +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/Themes/ThemeSettings.razor @@ -108,12 +108,12 @@ var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); if (_login != "-") { - settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true); + settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login); } if (_register != "-") { - settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true); + settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register); } await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); } From 1ac1933ec113c0bc364c6d6d190693d40f093569 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 2 Apr 2024 16:37:54 -0400 Subject: [PATCH 009/189] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..a092044e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,25 @@ +--- +name: Bug Report +about: Create a bug report to help us improve the product +title: "[BUG] " +labels: '' +assignees: '' + +--- + +### Oqtane Info +Version - 5.1.0 +Render Mode - Static +Interactivity - Server +Database - SQL Server + +### Describe the bug + + +### Expected Behavior + + +### Steps To Reproduce + + +### Anything else? From 45e02590993d291379fd5fda2649a4d9e7a8c31c Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 2 Apr 2024 16:41:52 -0400 Subject: [PATCH 010/189] Update issue templates --- .github/ISSUE_TEMPLATE/enhancement-request.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/enhancement-request.md diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md new file mode 100644 index 00000000..3e0a91b8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement-request.md @@ -0,0 +1,19 @@ +--- +name: Enhancement Request +about: 'Suggest a product enhancement ' +title: "[ENH] " +labels: '' +assignees: '' + +--- + +### Oqtane Info +Version - 5.1.0 +Render Mode - Static +Interactivity - Server +Database - SQL Server + +### Describe the enhancement + + +### Anything else? From 578b7b0512922166d1dce1640b9424403d8a5e51 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 2 Apr 2024 16:43:43 -0400 Subject: [PATCH 011/189] Update issue templates --- .github/ISSUE_TEMPLATE/bug-report.md | 3 ++- .github/ISSUE_TEMPLATE/enhancement-request.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index a092044e..e16dcee6 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -8,7 +8,8 @@ assignees: '' --- ### Oqtane Info -Version - 5.1.0 + +Version - #.#.# Render Mode - Static Interactivity - Server Database - SQL Server diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md index 3e0a91b8..c9890f6d 100644 --- a/.github/ISSUE_TEMPLATE/enhancement-request.md +++ b/.github/ISSUE_TEMPLATE/enhancement-request.md @@ -8,7 +8,8 @@ assignees: '' --- ### Oqtane Info -Version - 5.1.0 + +Version - #.#.# Render Mode - Static Interactivity - Server Database - SQL Server From 4c08a527be6f5659439d136eba16bfc034aac610 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 3 Apr 2024 09:21:13 +0800 Subject: [PATCH 012/189] Fix #3625: add the clear logs function. --- Oqtane.Client/Modules/Admin/Logs/Index.razor | 106 ++++++++++-------- .../Resources/Modules/Admin/Logs/Index.resx | 9 ++ .../Services/Interfaces/ILogService.cs | 7 ++ Oqtane.Client/Services/LogService.cs | 5 + Oqtane.Server/Controllers/LogController.cs | 15 +++ .../Repository/Interfaces/ILogRepository.cs | 2 +- Oqtane.Server/Repository/LogRepository.cs | 6 +- Oqtane.Server/wwwroot/css/app.css | 3 + 8 files changed, 104 insertions(+), 49 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index cf2f7cbc..8d180f13 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -86,47 +86,48 @@ else

- + + } @code { - private string _level = "-"; - private string _function = "-"; - private string _rows = "10"; - private int _page = 1; - private List _logs; - private int _retention = 30; + private string _level = "-"; + private string _function = "-"; + private string _rows = "10"; + private int _page = 1; + private List _logs; + private int _retention = 30; - public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}"; - public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; + public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}"; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; - protected override async Task OnParametersSetAsync() - { - try - { - if (UrlParameters.ContainsKey("level")) - { - _level = UrlParameters["level"]; - } - if (UrlParameters.ContainsKey("function")) - { - _function = UrlParameters["function"]; - } - if (UrlParameters.ContainsKey("rows")) - { - _rows = UrlParameters["rows"]; - } - if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page)) - { - _page = page; - } + protected override async Task OnParametersSetAsync() + { + try + { + if (UrlParameters.ContainsKey("level")) + { + _level = UrlParameters["level"]; + } + if (UrlParameters.ContainsKey("function")) + { + _function = UrlParameters["function"]; + } + if (UrlParameters.ContainsKey("rows")) + { + _rows = UrlParameters["rows"]; + } + if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page)) + { + _page = page; + } await GetLogs(); - var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30")); + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + _retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30")); } catch (Exception ex) { @@ -213,22 +214,37 @@ else return classname; } - private async Task SaveSiteSettings() - { - try - { - var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true); + private async Task SaveSiteSettings() + { + try + { + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); - AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); - AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); - } - } + AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); + AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); + } + } + + private async Task ClearLogs() + { + try + { + await LogService.ClearLogsAsync(PageState.Site.SiteId); + await GetLogs(); + StateHasChanged(); + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Clearing Logs {Error}", ex.Message); + AddModuleMessage(Localizer["Error.ClearLogs"], MessageType.Error); + } + } private void OnPageChange(int page) { diff --git a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx index dfd55880..f464d6fe 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx @@ -210,4 +210,13 @@ Settings Saved Successfully + + Clear Logs + + + Are you sure you wish to clear all the logs? + + + Error Clearing Logs + \ No newline at end of file diff --git a/Oqtane.Client/Services/Interfaces/ILogService.cs b/Oqtane.Client/Services/Interfaces/ILogService.cs index 2a821359..dea788bc 100644 --- a/Oqtane.Client/Services/Interfaces/ILogService.cs +++ b/Oqtane.Client/Services/Interfaces/ILogService.cs @@ -29,6 +29,13 @@ namespace Oqtane.Services /// Task GetLogAsync(int logId); + /// + /// Clear the entire logs of the given site. + /// + /// + /// + Task ClearLogsAsync(int siteId); + /// /// Creates a new log entry /// diff --git a/Oqtane.Client/Services/LogService.cs b/Oqtane.Client/Services/LogService.cs index e575694e..9369f90d 100644 --- a/Oqtane.Client/Services/LogService.cs +++ b/Oqtane.Client/Services/LogService.cs @@ -35,6 +35,11 @@ namespace Oqtane.Services return await GetJsonAsync($"{Apiurl}/{logId}"); } + public async Task ClearLogsAsync(int siteId) + { + await DeleteAsync($"{Apiurl}?siteid={siteId}&includeErrors=true"); + } + public async Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args) { await Log(null, pageId, moduleId, userId, category, feature, function, level, exception, message, args); diff --git a/Oqtane.Server/Controllers/LogController.cs b/Oqtane.Server/Controllers/LogController.cs index 34fc5b7c..490c7563 100644 --- a/Oqtane.Server/Controllers/LogController.cs +++ b/Oqtane.Server/Controllers/LogController.cs @@ -76,5 +76,20 @@ namespace Oqtane.Controllers HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } + + [HttpDelete] + [Authorize(Roles = RoleNames.Admin)] + public void Delete(string siteId, bool includeErrors) + { + if (int.TryParse(siteId, out int parsedSiteId) && parsedSiteId == _alias.SiteId) + { + _logs.DeleteLogs(parsedSiteId, 0, includeErrors); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Log Delete Attempt {SiteId}", siteId); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } } } diff --git a/Oqtane.Server/Repository/Interfaces/ILogRepository.cs b/Oqtane.Server/Repository/Interfaces/ILogRepository.cs index 918785f2..d30da106 100644 --- a/Oqtane.Server/Repository/Interfaces/ILogRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ILogRepository.cs @@ -8,6 +8,6 @@ namespace Oqtane.Repository IEnumerable GetLogs(int siteId, string level, string function, int rows); Log GetLog(int logId); void AddLog(Log log); - int DeleteLogs(int siteId, int age); + int DeleteLogs(int siteId, int age, bool includeErrors = false); } } diff --git a/Oqtane.Server/Repository/LogRepository.cs b/Oqtane.Server/Repository/LogRepository.cs index 6c388f15..6c767e43 100644 --- a/Oqtane.Server/Repository/LogRepository.cs +++ b/Oqtane.Server/Repository/LogRepository.cs @@ -53,20 +53,20 @@ namespace Oqtane.Repository db.SaveChanges(); } - public int DeleteLogs(int siteId, int age) + public int DeleteLogs(int siteId, int age, bool includeErrors = false) { using var db = _dbContextFactory.CreateDbContext(); // delete logs in batches of 100 records var count = 0; var purgedate = DateTime.UtcNow.AddDays(-age); - var logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) + var logs = db.Log.Where(item => item.SiteId == siteId && (includeErrors || item.Level != "Error") && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); while (logs.Count > 0) { count += logs.Count; db.Log.RemoveRange(logs); db.SaveChanges(); - logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) + logs = db.Log.Where(item => item.SiteId == siteId && (includeErrors || item.Level != "Error") && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); } return count; diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index 863d10d2..f5a65a80 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -35,6 +35,9 @@ app { } /* Action Dialog */ +.app-actiondialog{ + position: absolute; +} .app-actiondialog .modal { position: fixed; /* Stay in place */ z-index: 9999; /* Sit on top */ From 757a39a75efc10c876f91ca4570513f7aae796a9 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 3 Apr 2024 22:27:39 +0800 Subject: [PATCH 013/189] update code by review result. --- .../Resources/Modules/Admin/Logs/Index.resx | 2 +- Oqtane.Client/Services/LogService.cs | 2 +- Oqtane.Server/Controllers/LogController.cs | 4 ++-- .../Repository/Interfaces/ILogRepository.cs | 3 ++- Oqtane.Server/Repository/LogRepository.cs | 20 ++++++++++++++++--- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx index f464d6fe..27b3c4c7 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx @@ -210,7 +210,7 @@ Settings Saved Successfully - + Clear Logs diff --git a/Oqtane.Client/Services/LogService.cs b/Oqtane.Client/Services/LogService.cs index 9369f90d..26c37038 100644 --- a/Oqtane.Client/Services/LogService.cs +++ b/Oqtane.Client/Services/LogService.cs @@ -37,7 +37,7 @@ namespace Oqtane.Services public async Task ClearLogsAsync(int siteId) { - await DeleteAsync($"{Apiurl}?siteid={siteId}&includeErrors=true"); + await DeleteAsync($"{Apiurl}?siteid={siteId}"); } public async Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args) diff --git a/Oqtane.Server/Controllers/LogController.cs b/Oqtane.Server/Controllers/LogController.cs index 490c7563..c35bf6bf 100644 --- a/Oqtane.Server/Controllers/LogController.cs +++ b/Oqtane.Server/Controllers/LogController.cs @@ -79,11 +79,11 @@ namespace Oqtane.Controllers [HttpDelete] [Authorize(Roles = RoleNames.Admin)] - public void Delete(string siteId, bool includeErrors) + public void Delete(string siteId) { if (int.TryParse(siteId, out int parsedSiteId) && parsedSiteId == _alias.SiteId) { - _logs.DeleteLogs(parsedSiteId, 0, includeErrors); + _logs.ClearLogs(parsedSiteId); } else { diff --git a/Oqtane.Server/Repository/Interfaces/ILogRepository.cs b/Oqtane.Server/Repository/Interfaces/ILogRepository.cs index d30da106..623e3a2d 100644 --- a/Oqtane.Server/Repository/Interfaces/ILogRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ILogRepository.cs @@ -8,6 +8,7 @@ namespace Oqtane.Repository IEnumerable GetLogs(int siteId, string level, string function, int rows); Log GetLog(int logId); void AddLog(Log log); - int DeleteLogs(int siteId, int age, bool includeErrors = false); + int DeleteLogs(int siteId, int age); + void ClearLogs(int siteId); } } diff --git a/Oqtane.Server/Repository/LogRepository.cs b/Oqtane.Server/Repository/LogRepository.cs index 6c767e43..81d5813f 100644 --- a/Oqtane.Server/Repository/LogRepository.cs +++ b/Oqtane.Server/Repository/LogRepository.cs @@ -53,23 +53,37 @@ namespace Oqtane.Repository db.SaveChanges(); } - public int DeleteLogs(int siteId, int age, bool includeErrors = false) + public int DeleteLogs(int siteId, int age) { using var db = _dbContextFactory.CreateDbContext(); // delete logs in batches of 100 records var count = 0; var purgedate = DateTime.UtcNow.AddDays(-age); - var logs = db.Log.Where(item => item.SiteId == siteId && (includeErrors || item.Level != "Error") && item.LogDate < purgedate) + var logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); while (logs.Count > 0) { count += logs.Count; db.Log.RemoveRange(logs); db.SaveChanges(); - logs = db.Log.Where(item => item.SiteId == siteId && (includeErrors || item.Level != "Error") && item.LogDate < purgedate) + logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate) .OrderBy(item => item.LogDate).Take(100).ToList(); } return count; } + + public void ClearLogs(int siteId) + { + using var db = _dbContextFactory.CreateDbContext(); + var getLogsForDelete = () => db.Log.Where(item => item.SiteId == siteId).Take(100).ToList(); + // delete logs in batches of 100 records + var logs = getLogsForDelete(); + while (logs.Count > 0) + { + db.Log.RemoveRange(logs); + db.SaveChanges(); + logs = getLogsForDelete(); + } + } } } From 7b95db4d13e6b2225815e97133412ddad638caf1 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 4 Apr 2024 11:58:05 -0400 Subject: [PATCH 014/189] modify #4099 - fix localization and use Delete rather than Clear in API methods for consistency with rest of framework --- Oqtane.Client/Modules/Admin/Logs/Index.razor | 10 +++++----- .../Resources/Modules/Admin/Logs/Index.resx | 17 ++++++++++------- .../Services/Interfaces/ILogService.cs | 2 +- Oqtane.Client/Services/LogService.cs | 2 +- Oqtane.Server/Controllers/LogController.cs | 2 +- .../Repository/Interfaces/ILogRepository.cs | 1 - Oqtane.Server/Repository/LogRepository.cs | 18 ++---------------- 7 files changed, 20 insertions(+), 32 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index 8d180f13..a8cfeb70 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -87,7 +87,7 @@ else
- + } @@ -231,18 +231,18 @@ else } } - private async Task ClearLogs() + private async Task DeleteLogs() { try { - await LogService.ClearLogsAsync(PageState.Site.SiteId); + await LogService.DeleteLogsAsync(PageState.Site.SiteId); await GetLogs(); StateHasChanged(); } catch (Exception ex) { - await logger.LogError(ex, "Error Clearing Logs {Error}", ex.Message); - AddModuleMessage(Localizer["Error.ClearLogs"], MessageType.Error); + await logger.LogError(ex, "Error Deleting Logs {Error}", ex.Message); + AddModuleMessage(Localizer["Error.DeleteLogs"], MessageType.Error); } } diff --git a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx index 27b3c4c7..230dd9a4 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Logs/Index.resx @@ -1,4 +1,4 @@ - + Exe - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 5.1.0 + 5.1.1 1 14.2 @@ -65,15 +65,15 @@ - - + + - + - - - + + + diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index e2762ec7..49cf9bd5 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index ebd0b4ed..cfaef73d 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 5.1.0 + 5.1.1 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v5.1.0/Oqtane.Framework.5.1.0.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/download/v5.1.1/Oqtane.Framework.5.1.1.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 6e783ae2..93c2942e 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index bc41fd15..ac34a811 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index d2958199..fe7d85bd 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 21103c06..ed93995d 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Install.zip" -Force \ No newline at end of file diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 762ca550..c5c23da0 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.0.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Upgrade.zip" -Force \ No newline at end of file diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 6685f7b6..7fe28c3d 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 5fa002a2..3d8d4a78 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index e538ef9d..a7d1173e 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 = "5.1.0"; - 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"; + public static readonly string Version = "5.1.1"; + 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"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 231694c5..4554897c 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net8.0 Exe - 5.1.0 + 5.1.1 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/v5.1.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 https://github.com/oqtane/oqtane.framework Git Oqtane From cfce2bdbd981f844ac30305c89f3fb4d5b06d57e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 11 Apr 2024 15:26:55 -0400 Subject: [PATCH 028/189] move RichTextEditor script registration to Body --- Oqtane.Client/Modules/Controls/RichTextEditor.razor | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 77967c9f..d17d71c2 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -146,9 +146,9 @@ public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" }, - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" }, - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" } + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body }, + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body }, + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body } }; protected override void OnParametersSet() From 4a20fad4e594422d215dcd52c6793048a904fe05 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Fri, 12 Apr 2024 12:21:44 +0200 Subject: [PATCH 029/189] InitializeTokenReplace not setting the correct PackageReference For completeness. --- Oqtane.Server/Controllers/ModuleDefinitionController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 291f183c..2176c9f3 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -365,8 +365,8 @@ namespace Oqtane.Controllers { { "FrameworkVersion", moduleDefinition.Version }, { "ClientReference", $"" }, - { "ServerReference", $"" }, - { "SharedReference", $"" }, + { "ServerReference", $"" }, + { "SharedReference", $"" }, }; }); } From 5954fb91befa7e35a396ddf3bf2479d4f2f5dd0e Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 12 Apr 2024 21:56:49 +0800 Subject: [PATCH 030/189] Fix #4121: avoid nested square bracket issue. --- Oqtane.Server/Infrastructure/TokenReplace.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Infrastructure/TokenReplace.cs b/Oqtane.Server/Infrastructure/TokenReplace.cs index 3c7e8a8c..d2b40db4 100644 --- a/Oqtane.Server/Infrastructure/TokenReplace.cs +++ b/Oqtane.Server/Infrastructure/TokenReplace.cs @@ -93,6 +93,7 @@ namespace Oqtane.Infrastructure } var result = new StringBuilder(); + source = source.Replace("[[", "[$_["); //avoid nested square bracket issue. foreach (Match match in this.TokenizerRegex.Matches(source)) { var key = match.Result("${key}"); @@ -126,7 +127,7 @@ namespace Oqtane.Infrastructure result.Append(match.Result("${text}")); } } - + result.Replace("[$_", "["); //restore the changes. return result.ToString(); } From 8f00730189640eb344fdd1c9b110f2514e30e358 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 12 Apr 2024 13:25:44 -0400 Subject: [PATCH 031/189] fix #4134 - RichTextEditor scenarios --- .../Modules/Controls/RichTextEditor.razor | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index d17d71c2..6da97020 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -109,12 +109,12 @@ private bool _richfilemanager = false; private FileManager _fileManager; private string _richhtml = string.Empty; - private string _originalrichhtml = string.Empty; private bool _rawfilemanager = false; private string _rawhtml = string.Empty; private string _originalrawhtml = string.Empty; private string _message = string.Empty; private string _activetab = "Rich"; + private bool _contentchanged = false; [Parameter] public string Content { get; set; } @@ -156,7 +156,7 @@ _richhtml = Content; _rawhtml = Content; _originalrawhtml = _rawhtml; // preserve for comparison later - _originalrichhtml = ""; + _contentchanged = true; if (!AllowRichText) { @@ -181,24 +181,32 @@ Placeholder, Theme, DebugLevel); + + await interop.LoadEditorContent(_editorElement, _richhtml); + + _initialized = true; } - - if (_initialized) + else { - if (!_richfilemanager) // do not override the content when the file manager is displayed + if (_initialized) { - await interop.LoadEditorContent(_editorElement, _richhtml); - } - - if (string.IsNullOrEmpty(_originalrichhtml)) - { - // preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor as it may have changed) - _originalrichhtml = await interop.GetHtml(_editorElement); + if (_contentchanged) + { + // reload if content passed to component has changed + await interop.LoadEditorContent(_editorElement, _richhtml); + } + else + { + var richhtml = await interop.GetHtml(_editorElement); + if (richhtml != _richhtml) + { + await interop.LoadEditorContent(_editorElement, richhtml); + } + } + _contentchanged = false; } } } - - _initialized = true; // ensures that the rich text editor is created before trying to access its methods } public void CloseRichFileManager() @@ -224,23 +232,17 @@ } else { - var richhtml = ""; if (AllowRichText) { - // return rich text content if it has changed + // return rich text content var interop = new RichTextEditorInterop(JSRuntime); - richhtml = await interop.GetHtml(_editorElement); + return await interop.GetHtml(_editorElement); } - // rich text value will only be blank if AllowRichText is disabled or the JS Interop method failed - if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml) && !string.IsNullOrEmpty(_originalrichhtml)) - { - return richhtml; - } - else - { + else + { // return original raw html content - return _originalrawhtml; - } + return _originalrawhtml; + } } } From 39dff1ea7cc7ee119ddc36f510ad0e8d385b6d73 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 12 Apr 2024 14:58:14 -0400 Subject: [PATCH 032/189] more changes for #4134 - ensure HTML content is preserved --- .../Modules/Controls/RichTextEditor.razor | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 6da97020..0850325d 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -109,6 +109,7 @@ private bool _richfilemanager = false; private FileManager _fileManager; private string _richhtml = string.Empty; + private string _originalrichhtml = string.Empty; private bool _rawfilemanager = false; private string _rawhtml = string.Empty; private string _originalrawhtml = string.Empty; @@ -156,6 +157,7 @@ _richhtml = Content; _rawhtml = Content; _originalrawhtml = _rawhtml; // preserve for comparison later + _originalrichhtml = ""; _contentchanged = true; if (!AllowRichText) @@ -184,6 +186,9 @@ await interop.LoadEditorContent(_editorElement, _richhtml); + // preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor as it may have changed) + _originalrichhtml = await interop.GetHtml(_editorElement); + _initialized = true; } else @@ -194,6 +199,7 @@ { // reload if content passed to component has changed await interop.LoadEditorContent(_editorElement, _richhtml); + _originalrichhtml = await interop.GetHtml(_editorElement); } else { @@ -232,18 +238,24 @@ } else { + var richhtml = ""; + if (AllowRichText) { - // return rich text content var interop = new RichTextEditorInterop(JSRuntime); - return await interop.GetHtml(_editorElement); + richhtml = await interop.GetHtml(_editorElement); + } + + if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + { + return richhtml; } else { - // return original raw html content - return _originalrawhtml; + // return original raw html content + return _originalrawhtml; } - } + } } public async Task InsertRichImage() From bc978a91e37effcb9a964810c9c4cc60f6856b8f Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 12 Apr 2024 15:10:47 -0400 Subject: [PATCH 033/189] convert Quill's empty content to empty string --- Oqtane.Client/Modules/Controls/RichTextEditor.razor | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 0850325d..ce7e4736 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -188,7 +188,7 @@ // preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor as it may have changed) _originalrichhtml = await interop.GetHtml(_editorElement); - + _initialized = true; } else @@ -239,7 +239,7 @@ else { var richhtml = ""; - + if (AllowRichText) { var interop = new RichTextEditorInterop(JSRuntime); @@ -248,6 +248,11 @@ if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) { + // convert Quill's empty content to empty string + if (richhtml == "


") + { + richhtml = string.Empty; + } return richhtml; } else From 9d8b1fd99b2ba2fcfc49aee4287ae1112228823e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 15 Apr 2024 08:54:23 -0400 Subject: [PATCH 034/189] fixes for #4134 - Rich Text Editor --- .../Modules/Controls/RichTextEditor.razor | 44 ++++++++++++------- .../Modules/Controls/RichTextEditor.resx | 3 ++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index ce7e4736..ad237acb 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -90,11 +90,11 @@ @if (ReadOnly) { - + } else { - + } } @@ -104,17 +104,23 @@ @code { private bool _initialized = false; + + private RichTextEditorInterop interop; + private FileManager _fileManager; + private string _activetab = "Rich"; + private ElementReference _editorElement; private ElementReference _toolBar; private bool _richfilemanager = false; - private FileManager _fileManager; private string _richhtml = string.Empty; private string _originalrichhtml = string.Empty; + private bool _rawfilemanager = false; + private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N"); private string _rawhtml = string.Empty; private string _originalrawhtml = string.Empty; + private string _message = string.Empty; - private string _activetab = "Rich"; private bool _contentchanged = false; [Parameter] @@ -124,7 +130,7 @@ public bool ReadOnly { get; set; } = false; [Parameter] - public string Placeholder { get; set; } = "Enter Your Content..."; + public string Placeholder { get; set; } [Parameter] public bool AllowFileManagement { get; set; } = true; @@ -152,13 +158,22 @@ new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body } }; + protected override void OnInitialized() + { + interop = new RichTextEditorInterop(JSRuntime); + if (string.IsNullOrEmpty(Placeholder)) + { + Placeholder = Localizer["Placeholder"]; + } + } + protected override void OnParametersSet() { _richhtml = Content; _rawhtml = Content; _originalrawhtml = _rawhtml; // preserve for comparison later _originalrichhtml = ""; - _contentchanged = true; + _contentchanged = true; // identifies when Content parameter has changed if (!AllowRichText) { @@ -172,8 +187,6 @@ if (AllowRichText) { - var interop = new RichTextEditorInterop(JSRuntime); - if (firstRender) { await interop.CreateEditor( @@ -186,7 +199,7 @@ await interop.LoadEditorContent(_editorElement, _richhtml); - // preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor as it may have changed) + // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified) _originalrichhtml = await interop.GetHtml(_editorElement); _initialized = true; @@ -197,21 +210,24 @@ { if (_contentchanged) { - // reload if content passed to component has changed + // reload editor if Content passed to component has changed await interop.LoadEditorContent(_editorElement, _richhtml); _originalrichhtml = await interop.GetHtml(_editorElement); } else { + // preserve changed content on re-render event var richhtml = await interop.GetHtml(_editorElement); if (richhtml != _richhtml) { - await interop.LoadEditorContent(_editorElement, richhtml); + _richhtml = richhtml; + await interop.LoadEditorContent(_editorElement, _richhtml); } } - _contentchanged = false; } } + + _contentchanged = false; } } @@ -242,7 +258,6 @@ if (AllowRichText) { - var interop = new RichTextEditorInterop(JSRuntime); richhtml = await interop.GetHtml(_editorElement); } @@ -271,7 +286,6 @@ var file = _fileManager.GetFile(); if (file != null) { - var interop = new RichTextEditorInterop(JSRuntime); await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name)); _richhtml = await interop.GetHtml(_editorElement); _richfilemanager = false; @@ -297,7 +311,7 @@ if (file != null) { var interop = new Interop(JSRuntime); - int pos = await interop.GetCaretPosition("rawhtmleditor"); + int pos = await interop.GetCaretPosition(_rawhtmlid); var image = "\"""; _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); _rawfilemanager = false; diff --git a/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx index 5ac2f720..4e2b64c5 100644 --- a/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx +++ b/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx @@ -126,4 +126,7 @@ You Must Select An Image To Insert + + Enter Your Content... +
\ No newline at end of file From 4f25b7bbbe6b353eccd95b5412a0f89c558dde36 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 16 Apr 2024 08:02:59 -0400 Subject: [PATCH 035/189] fix SiteRouter issue when running on .NET MAUI --- Oqtane.Client/UI/SiteRouter.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 967492d3..43acc3a6 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -171,9 +171,9 @@ visitorId = PageState.VisitorId; } - if (PageState.RenderMode == RenderModes.Interactive) + if (PageState != null && PageState.RenderMode == RenderModes.Interactive) { - // process any sync events (for synchrozing the client application with the server) + // process any sync events (for synchronizing the client application with the server) var sync = await SyncService.GetSyncEventsAsync(lastsyncdate); lastsyncdate = sync.SyncDate; if (sync.SyncEvents.Any()) From 2a0399b98da1a8c4618fd42f6792d82746002db2 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 16 Apr 2024 12:36:31 -0400 Subject: [PATCH 036/189] include .NET MAUI CORS policy for static files, add support for [wwwroot] in content --- Oqtane.Server/Startup.cs | 16 +++++++++++++--- Oqtane.Shared/Shared/Utilities.cs | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 8180ea9b..0a83d275 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -22,6 +22,7 @@ using Oqtane.UI; using OqtaneSSR.Extensions; using Microsoft.AspNetCore.Components.Authorization; using Oqtane.Providers; +using Microsoft.AspNetCore.Cors.Infrastructure; namespace Oqtane { @@ -135,7 +136,7 @@ namespace Oqtane { // allow .NET MAUI client cross origin calls policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0") - .AllowAnyHeader().AllowCredentials(); + .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); @@ -169,7 +170,7 @@ namespace Oqtane } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ILogger logger) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger logger) { if (!string.IsNullOrEmpty(_configureServicesErrors)) { @@ -198,7 +199,16 @@ namespace Oqtane app.UseOqtaneLocalization(); app.UseHttpsRedirection(); - app.UseStaticFiles(); + app.UseStaticFiles(new StaticFileOptions + { + ServeUnknownFileTypes = true, + OnPrepareResponse = (ctx) => + { + var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy) + .ConfigureAwait(false).GetAwaiter().GetResult(); + corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response); + } + }); app.UseExceptionMiddleWare(); app.UseTenantResolution(); app.UseJwtAuthorization(); diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 00cfb3f6..b9e364c3 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -149,6 +149,7 @@ namespace Oqtane.Shared break; case "render": content = content.Replace(Constants.FileUrl, alias?.BaseUrl + aliasUrl + Constants.FileUrl); + content = content.Replace("[wwwroot]", alias?.BaseUrl + aliasUrl + "/"); // legacy content = content.Replace("[siteroot]", UrlCombine("Content", "Tenants", alias.TenantId.ToString(), "Sites", alias.SiteId.ToString())); content = content.Replace(Constants.ContentUrl, alias.Path + Constants.ContentUrl); From b815d945d95cc7efeb9762094d29cd7a19bd0418 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 16 Apr 2024 13:04:25 -0400 Subject: [PATCH 037/189] fix SiteMap path issue --- Oqtane.Server/Pages/Sitemap.cshtml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Pages/Sitemap.cshtml.cs b/Oqtane.Server/Pages/Sitemap.cshtml.cs index 7ba00eb4..c79957c3 100644 --- a/Oqtane.Server/Pages/Sitemap.cshtml.cs +++ b/Oqtane.Server/Pages/Sitemap.cshtml.cs @@ -63,7 +63,7 @@ namespace Oqtane.Pages { pageurl += (page.Url.StartsWith("/") ? "" : "/") + page.Url; } - sitemap.Add(new Sitemap { Url = rooturl + pageurl, ModifiedOn = DateTime.UtcNow }); + sitemap.Add(new Sitemap { Url = pageurl, ModifiedOn = DateTime.UtcNow }); foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) { From 4d58ee2162846651fbd56625247dd15f743745ae Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 16 Apr 2024 13:46:55 -0400 Subject: [PATCH 038/189] Update README.md --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0a56d241..116e1206 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[5.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0) was released on Mar 27, 2024 and is a major release providing Static Server Rendering support for Blazor in .NET 8. This release includes 263 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) was released on Apr 16, 2024 and is primarily a stabilization release, which includes a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) @@ -18,15 +18,15 @@ Please note that this project is owned by the .NET Foundation and is governed by **Using Version 5:** -- Install **[.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. +- Install **[.NET 8.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. -- Install the latest edition (v17.8 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**. +- Install the latest edition (v17.9 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**. - Clone the Oqtane dev branch source code to your local system. - Open the **Oqtane.sln** solution file. -- **Important:** Build the solution. +- **Important:** Rebuild the entire solution before running it. - Make sure you specify Oqtane.Server as the Startup Project @@ -63,8 +63,8 @@ Backlog (TBD) - [ ] Folder Providers - [ ] Generative AI Integration -5.1.1 (Apr 2024) -- [ ] Stabilization improvements +[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) (Apr 16, 2024) +- [x] Stabilization improvements [5.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0) (Mar 27, 2024) - [x] Migration to the new unified Blazor approach in .NET 8 (ie. blazor.web.js) @@ -249,6 +249,8 @@ Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalke # Release Announcements +[Oqtane 5.1](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8) + [Oqtane 5.0](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8) [Oqtane 4.0](https://www.oqtane.org/blog/!/63/announcing-oqtane-4-0-for-net-7) From 976ad5fcee445d107c375f093a7ae1a4ccb327e3 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 16 Apr 2024 13:47:24 -0400 Subject: [PATCH 039/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 116e1206..5cae70ad 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) was released on Apr 16, 2024 and is primarily a stabilization release, which includes a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) was released on Apr 16, 2024 and is primarily a stabilization release, including a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) From 430e6163285828b8ed6561a0bb59e7916ea96514 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 18 Apr 2024 18:43:14 -0400 Subject: [PATCH 040/189] fix #4150 - remove Add Existing Module option when managing personalized pages --- .../Themes/Controls/Theme/ControlPanelInteractive.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 5c53f36f..fe8c0128 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -95,7 +95,10 @@ @if (_moduleType == "new") { From 09293f7d9a1561596b580663a49f5925e662fa90 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 20 Apr 2024 16:56:32 +0800 Subject: [PATCH 041/189] Fix #4158: insert image into correct position. --- .../Modules/Controls/RichTextEditor.razor | 20 ++++++++++--------- .../Modules/Controls/RichTextEditorInterop.cs | 16 +++++++++++++-- Oqtane.Server/wwwroot/js/quill-interop.js | 10 ++++++---- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index ad237acb..bada030c 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -122,6 +122,7 @@ private string _message = string.Empty; private bool _contentchanged = false; + private int _editorIndex; [Parameter] public string Content { get; set; } @@ -275,18 +276,18 @@ // return original raw html content return _originalrawhtml; } - } - } + } + } - public async Task InsertRichImage() - { - _message = string.Empty; - if (_richfilemanager) - { - var file = _fileManager.GetFile(); + public async Task InsertRichImage() + { + _message = string.Empty; + if (_richfilemanager) + { + var file = _fileManager.GetFile(); if (file != null) { - await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name)); + await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex); _richhtml = await interop.GetHtml(_editorElement); _richfilemanager = false; } @@ -297,6 +298,7 @@ } else { + _editorIndex = await interop.GetCurrentCursor(_editorElement); _richfilemanager = true; } StateHasChanged(); diff --git a/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs b/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs index 6765cad9..338f240a 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs +++ b/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs @@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls } } - public Task InsertImage(ElementReference quillElement, string imageUrl, string altText) + public ValueTask GetCurrentCursor(ElementReference quillElement) + { + try + { + return _jsRuntime.InvokeAsync("Oqtane.RichTextEditor.getCurrentCursor", quillElement); + } + catch + { + return new ValueTask(Task.FromResult(0)); + } + } + + public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex) { try { _jsRuntime.InvokeAsync( "Oqtane.RichTextEditor.insertQuillImage", - quillElement, imageUrl, altText); + quillElement, imageUrl, altText, editorIndex); return Task.CompletedTask; } catch diff --git a/Oqtane.Server/wwwroot/js/quill-interop.js b/Oqtane.Server/wwwroot/js/quill-interop.js index e5610a9e..4598d7aa 100644 --- a/Oqtane.Server/wwwroot/js/quill-interop.js +++ b/Oqtane.Server/wwwroot/js/quill-interop.js @@ -35,13 +35,15 @@ Oqtane.RichTextEditor = { enableQuillEditor: function (editorElement, mode) { editorElement.__quill.enable(mode); }, - insertQuillImage: function (quillElement, imageURL, altText) { - var Delta = Quill.import('delta'); - editorIndex = 0; - + getCurrentCursor: function (quillElement) { + var editorIndex = 0; if (quillElement.__quill.getSelection() !== null) { editorIndex = quillElement.__quill.getSelection().index; } + return editorIndex; + }, + insertQuillImage: function (quillElement, imageURL, altText, editorIndex) { + var Delta = Quill.import('delta'); return quillElement.__quill.updateContents( new Delta() From 8958b61fdd65ab25a2c6235dba9156e415303270 Mon Sep 17 00:00:00 2001 From: Ikuo Ohba Date: Sun, 21 Apr 2024 20:44:41 +0900 Subject: [PATCH 042/189] Datetime formatting issue --- Oqtane.Client/Services/VisitorService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Services/VisitorService.cs b/Oqtane.Client/Services/VisitorService.cs index 9febe7f9..ae2e46c1 100644 --- a/Oqtane.Client/Services/VisitorService.cs +++ b/Oqtane.Client/Services/VisitorService.cs @@ -18,7 +18,7 @@ namespace Oqtane.Services public async Task> GetVisitorsAsync(int siteId, DateTime fromDate) { - List visitors = await GetJsonAsync>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("dd-MMM-yyyy")}"); + List visitors = await GetJsonAsync>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("yyyy-MM-dd")}"); return visitors.OrderByDescending(item => item.VisitedOn).ToList(); } From 904d39beacc4b3cd2b553250e07eadaee508cf70 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 14:08:09 +0200 Subject: [PATCH 043/189] Rouge TD in table Deleted a rouge TD in the themes index table --- Oqtane.Client/Modules/Admin/Themes/Index.razor | 1 - 1 file changed, 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 3a7d64ae..4e65f693 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -63,7 +63,6 @@ else } - } From 7b8e7ac5c23fffd3fb0cbd573ba11acb33a42ebc Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 17:14:09 +0200 Subject: [PATCH 044/189] Fix for #4168 Resx entry for Module Settings Permissions tab --- Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index 722fa2f1..b872c727 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -177,4 +177,7 @@ Expiry Date: + + Permissions + \ No newline at end of file From b5ebcc3e0770fec9aae6b9d1df767ade55731f11 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 17:46:13 +0200 Subject: [PATCH 045/189] Update Index.razor --- Oqtane.Client/Modules/Admin/Pages/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Index.razor b/Oqtane.Client/Modules/Admin/Pages/Index.razor index f1b9e9b4..391c6c42 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Index.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Index.razor @@ -17,7 +17,7 @@ @SharedLocalizer["Name"] - + @(new string('-', context.Level * 2))@(context.Name) From 294f511b9a5c17918a7f0f1efe4ade2fc7f98a6c Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 18:27:01 +0200 Subject: [PATCH 046/189] Missing parameters #4172 #4173 --- Oqtane.Client/Modules/Admin/Profiles/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Users/Index.razor | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Profiles/Index.razor b/Oqtane.Client/Modules/Admin/Profiles/Index.razor index 587fa452..6152062d 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Index.razor @@ -22,7 +22,7 @@ else @Localizer["Order"] - + @context.Name @context.Title diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index ed050ac7..a04be4c3 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -32,13 +32,13 @@ else - + - + @context.User.Username @context.User.DisplayName From ead954ddaaa5874b806fccd9396c245b7379e76a Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 19:18:02 +0200 Subject: [PATCH 047/189] Missing Parameters #4174 #4175 --- Oqtane.Client/Modules/Admin/Logs/Index.razor | 2 +- Oqtane.Client/Modules/Admin/Visitors/Index.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Logs/Index.razor b/Oqtane.Client/Modules/Admin/Logs/Index.razor index 6932f784..dfb0cf8d 100644 --- a/Oqtane.Client/Modules/Admin/Logs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Logs/Index.razor @@ -63,7 +63,7 @@ else @Localizer["Function"] - + @context.LogDate @context.Level @context.Feature diff --git a/Oqtane.Client/Modules/Admin/Visitors/Index.razor b/Oqtane.Client/Modules/Admin/Visitors/Index.razor index 61487cf3..39b14006 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Index.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Index.razor @@ -43,7 +43,7 @@ else @Localizer["Created"] - + @context.IPAddress @if (context.UserId != null) diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx index 98f8530c..694119f3 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Index.resx @@ -147,4 +147,7 @@ Title + + Detail + \ No newline at end of file From 708d473b475204cd0b4bca05bc19bf9925c963f4 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 22 Apr 2024 20:19:33 +0200 Subject: [PATCH 048/189] Missing Parameter #4176 --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index 2d3b80a7..485f027f 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -50,7 +50,7 @@ else   - + @if (context.AssemblyName != Constants.ClientId) { From 6162244730c5e70011363e09666ec28d81470b8c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Apr 2024 17:09:18 -0400 Subject: [PATCH 049/189] fix #4165 - missing slash in subfolder sites --- Oqtane.Shared/Shared/Utilities.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index b9e364c3..4fc04e7f 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -44,9 +44,10 @@ namespace Oqtane.Shared string querystring = ""; string fragment = ""; + if (!string.IsNullOrEmpty(path)) path = "/" + path; + if (!string.IsNullOrEmpty(parameters)) { - // parse parameters (string urlparameters, querystring, fragment) = ParseParameters(parameters); if (!string.IsNullOrEmpty(urlparameters)) { From cfb128acb8d286777459ca5ece7f5f6d037223d7 Mon Sep 17 00:00:00 2001 From: vnetonline Date: Tue, 23 Apr 2024 15:22:02 +1000 Subject: [PATCH 050/189] [ENH] - Add Prerender IModuleControl property (similar to RenderMode) #4178 --- Oqtane.Client/Modules/ModuleBase.cs | 2 ++ Oqtane.Client/UI/ModuleInstance.razor | 2 +- Oqtane.Client/UI/RenderModeBoundary.razor | 2 +- Oqtane.Client/UI/SiteRouter.razor | 1 + Oqtane.Shared/Interfaces/IModuleControl.cs | 5 +++++ Oqtane.Shared/Models/Module.cs | 2 ++ 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index fee2d4e5..2d5410a3 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -52,6 +52,8 @@ namespace Oqtane.Modules public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default + public virtual bool Prerender { get { return true; } } // prerender at server by default + // url parameters public virtual string UrlParametersTemplate { get; set; } diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index 5d429954..53258cea 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -7,7 +7,7 @@ } else { - + } @code { diff --git a/Oqtane.Client/UI/RenderModeBoundary.razor b/Oqtane.Client/UI/RenderModeBoundary.razor index e4d6873e..d3a11055 100644 --- a/Oqtane.Client/UI/RenderModeBoundary.razor +++ b/Oqtane.Client/UI/RenderModeBoundary.razor @@ -10,7 +10,7 @@ { @if (ModuleType != null) { - @((MarkupString)$"") + @((MarkupString)$"") @DynamicComponent @if (_progressIndicator) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 43acc3a6..13c35b17 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -476,6 +476,7 @@ // retrieve module component resources var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; module.RenderMode = moduleobject.RenderMode; + module.Prerender = moduleobject.Prerender; page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); if (action.ToLower() == "settings" && module.ModuleDefinition != null) diff --git a/Oqtane.Shared/Interfaces/IModuleControl.cs b/Oqtane.Shared/Interfaces/IModuleControl.cs index 1d442931..b425a311 100644 --- a/Oqtane.Shared/Interfaces/IModuleControl.cs +++ b/Oqtane.Shared/Interfaces/IModuleControl.cs @@ -35,5 +35,10 @@ namespace Oqtane.Modules /// Specifies the required render mode for the module control ie. Static,Interactive /// string RenderMode { get; } + + /// + /// Specifies the prerender mode for the moudle control ie: true or false + /// + bool Prerender { get; } } } diff --git a/Oqtane.Shared/Models/Module.cs b/Oqtane.Shared/Models/Module.cs index bae7a0ca..a875e31b 100644 --- a/Oqtane.Shared/Models/Module.cs +++ b/Oqtane.Shared/Models/Module.cs @@ -117,6 +117,8 @@ namespace Oqtane.Models public bool UseAdminContainer { get; set; } [NotMapped] public string RenderMode{ get; set; } + [NotMapped] + public bool Prerender { get; set; } #endregion From adfd0d5c183e456a271ad813b57fdab17a8723df Mon Sep 17 00:00:00 2001 From: Ikuo Ohba Date: Tue, 23 Apr 2024 21:27:13 +0900 Subject: [PATCH 051/189] Fix MicroService And Controller --- Oqtane.Client/Services/VisitorService.cs | 3 ++- Oqtane.Server/Controllers/VisitorController.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Services/VisitorService.cs b/Oqtane.Client/Services/VisitorService.cs index ae2e46c1..b29e9c3c 100644 --- a/Oqtane.Client/Services/VisitorService.cs +++ b/Oqtane.Client/Services/VisitorService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Oqtane.Documentation; using Oqtane.Shared; using System; +using System.Globalization; namespace Oqtane.Services { @@ -18,7 +19,7 @@ namespace Oqtane.Services public async Task> GetVisitorsAsync(int siteId, DateTime fromDate) { - List visitors = await GetJsonAsync>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("yyyy-MM-dd")}"); + List visitors = await GetJsonAsync>($"{Apiurl}?siteid={siteId}&fromdate={fromDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}"); return visitors.OrderByDescending(item => item.VisitedOn).ToList(); } diff --git a/Oqtane.Server/Controllers/VisitorController.cs b/Oqtane.Server/Controllers/VisitorController.cs index 46bf8935..224aaa5f 100644 --- a/Oqtane.Server/Controllers/VisitorController.cs +++ b/Oqtane.Server/Controllers/VisitorController.cs @@ -8,6 +8,7 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using System.Net; using System; +using System.Globalization; namespace Oqtane.Controllers { @@ -33,7 +34,7 @@ namespace Oqtane.Controllers int SiteId; if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) { - return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate)); + return _visitors.GetVisitors(SiteId, DateTime.ParseExact(fromdate, "yyyy-MM-dd", CultureInfo.InvariantCulture)); } else { From 791cc70b094a4f01c241e91cba58edd9d4feb12b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 23 Apr 2024 12:54:44 -0400 Subject: [PATCH 052/189] replace form with link in AdminContainer --- Oqtane.Client/Themes/AdminContainer.razor | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Oqtane.Client/Themes/AdminContainer.razor b/Oqtane.Client/Themes/AdminContainer.razor index 60deaa59..7aec6816 100644 --- a/Oqtane.Client/Themes/AdminContainer.razor +++ b/Oqtane.Client/Themes/AdminContainer.razor @@ -1,6 +1,5 @@ @namespace Oqtane.Themes @inherits ContainerBase -@inject NavigationManager NavigationManager
@@ -267,13 +264,13 @@ protected string _moduleId { get; private set; } = "-"; protected string _moduleType { get; private set; } = "new"; protected string _moduleDefinitionName { get; private set; } = "-"; - protected string _copyMode { get; private set; } = "-"; protected string _title { get; private set; } = ""; protected string _containerType { get; private set; } = ""; protected int _location { get; private set; } = int.MaxValue; protected string _visibility { get; private set; } = "view"; protected string _message { get; private set; } = ""; + protected string _copyModuleMessage { get; private set; } = ""; private string settingCategory = "CP-category"; private string settingPane = "CP-pane"; @@ -358,13 +355,26 @@ StateHasChanged(); } + private void ModuleIdChanged() + { + _copyModuleMessage = string.Empty; + if(_moduleId != "-") + { + var module = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(_moduleId)); + if (module != null && !module.ModuleDefinition.IsPortable) + { + _copyModuleMessage = $"
{Localizer["Message.Module.NotPortable"]}
"; + } + } + } + private async Task AddModule() { if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) { - if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-" && _copyMode != "-")) + if ((_moduleType == "new" && _moduleDefinitionName != "-") || (_moduleType != "new" && _moduleId != "-")) { - var newModuleId = int.Parse(_moduleId); + var newModuleId = _moduleId != "-" ? int.Parse(_moduleId) : 0; if (_moduleType == "new") { Module module = new Module(); @@ -377,7 +387,7 @@ module = await ModuleService.AddModuleAsync(module); newModuleId = module.ModuleId; } - else if(_copyMode == "copy") + else if (_moduleType == "exsiting.copy") { var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId)); module.ModuleId = 0; @@ -433,8 +443,7 @@ } else { - var error = _moduleType == "new" ? Localizer["Message.Require.ModuleSelect"] : Localizer["Message.Require.ModuleAndCopyModeSelect"]; - _message = $"
{error}
"; + _message = $"
{Localizer["Message.Require.ModuleSelect"]}
"; } } else From 2e7c3167f5fdba65c6c923b091e0c84d75215d0a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 29 Apr 2024 14:58:30 -0400 Subject: [PATCH 071/189] refactor #4198 - copy existing module --- .../Controls/ControlPanelInteractive.resx | 19 +--------- .../Theme/ControlPanelInteractive.razor | 37 ++++++++----------- .../HtmlText/Manager/HtmlTextManager.cs | 8 ++-- 3 files changed, 22 insertions(+), 42 deletions(-) diff --git a/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx b/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx index 6a8302d7..15c567fc 100644 --- a/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx +++ b/Oqtane.Client/Resources/Themes/Controls/ControlPanelInteractive.resx @@ -154,7 +154,7 @@ Not Authorized - You Must Select A Module. + You Must Select A Module Module Management: @@ -162,15 +162,6 @@ Select Module - - Select Copy Mode - - - Sync - - - Copy - Page Management: @@ -207,13 +198,7 @@ Top - - You Must Select A Module And The Copy Mode. - - - The Module's Content Will Not Be Synchronized. - Copy Existing Module - + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index d33b1d21..aa281da2 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -94,12 +94,12 @@
- @if (PageState.Page.UserId == null) { - - + + } @if (_moduleType == "new") @@ -143,21 +143,20 @@ } else { - @foreach (Page p in _pages) { } - @foreach (Module module in _modules) { } - @((MarkupString)_copyModuleMessage) }
@@ -270,7 +269,6 @@ protected int _location { get; private set; } = int.MaxValue; protected string _visibility { get; private set; } = "view"; protected string _message { get; private set; } = ""; - protected string _copyModuleMessage { get; private set; } = ""; private string settingCategory = "CP-category"; private string settingPane = "CP-pane"; @@ -341,6 +339,13 @@ StateHasChanged(); } + private void ModuleTypeChanged(ChangeEventArgs e) + { + _moduleType = (string)e.Value; + _pageId = "-"; + _moduleId = "-"; + } + private void PageChanged(ChangeEventArgs e) { _pageId = (string)e.Value; @@ -348,26 +353,14 @@ { _modules = PageState.Modules .Where(module => module.PageId == int.Parse(_pageId) && - UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList)) + UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) && + (_moduleType == "add" || module.ModuleDefinition.IsPortable)) .ToList(); } _moduleId = "-"; StateHasChanged(); } - private void ModuleIdChanged() - { - _copyModuleMessage = string.Empty; - if(_moduleId != "-") - { - var module = _modules.FirstOrDefault(item => item.ModuleId == int.Parse(_moduleId)); - if (module != null && !module.ModuleDefinition.IsPortable) - { - _copyModuleMessage = $"
{Localizer["Message.Module.NotPortable"]}
"; - } - } - } - private async Task AddModule() { if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) @@ -387,7 +380,7 @@ module = await ModuleService.AddModuleAsync(module); newModuleId = module.ModuleId; } - else if (_moduleType == "exsiting.copy") + else if (_moduleType == "copy") { var module = await ModuleService.GetModuleAsync(int.Parse(_moduleId)); module.ModuleId = 0; diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 959f4d07..6c9293fb 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -7,6 +7,7 @@ using Oqtane.Repository; using Oqtane.Shared; using Oqtane.Migrations.Framework; using Oqtane.Documentation; +using System.Linq; // ReSharper disable ConvertToUsingDeclaration @@ -29,10 +30,11 @@ namespace Oqtane.Modules.HtmlText.Manager public string ExportModule(Module module) { string content = ""; - var htmlText = _htmlText.GetHtmlText(module.ModuleId); - if (htmlText != null) + var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId); + if (htmltexts != null && htmltexts.Any()) { - content = WebUtility.HtmlEncode(htmlText.Content); + var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First(); + content = WebUtility.HtmlEncode(htmltext.Content); } return content; } From 378b81b13bb1e11d565d7afe3451fbbfe15ea268 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 30 Apr 2024 08:17:53 +0200 Subject: [PATCH 072/189] Fix for missing parameters and Resx values Issue #4202 Issue #4203 --- Oqtane.Client/Modules/Admin/Roles/Index.razor | 4 ++-- Oqtane.Client/Modules/Admin/UrlMappings/Index.razor | 2 +- Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Roles/Index.razor b/Oqtane.Client/Modules/Admin/Roles/Index.razor index 6b0e4610..44ffe0c3 100644 --- a/Oqtane.Client/Modules/Admin/Roles/Index.razor +++ b/Oqtane.Client/Modules/Admin/Roles/Index.razor @@ -20,9 +20,9 @@ else @SharedLocalizer["Name"] - + - + @context.Name diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor index 6ed8c769..bae495fb 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor @@ -37,7 +37,7 @@ else @Localizer["Requested"] - + @context.Url diff --git a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx index 4ee3cbfe..188c09e3 100644 --- a/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/UrlMappings/Index.resx @@ -159,4 +159,7 @@ Url + + Edit + \ No newline at end of file From 2b709ad0949e03c4a004e508a20da2b389919ebd Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Tue, 30 Apr 2024 13:14:58 +0200 Subject: [PATCH 073/189] Update README.md, move history to docs moved release history and old announcements to the docs --- README.md | 193 +----------------------------------------------------- 1 file changed, 1 insertion(+), 192 deletions(-) diff --git a/README.md b/README.md index c4b664e9..633a3940 100644 --- a/README.md +++ b/README.md @@ -79,202 +79,11 @@ Backlog (TBD) [5.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.0) (Nov 16, 2023) - [x] Migration to .NET 8 -[4.0.6](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.6) ( Oct 16, 2023 ) -- [x] Stabilization improvements - -[4.0.5](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.5) ( Sep 26, 2023 ) -- [x] Stabilization improvements - -[4.0.4](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.4) ( Sep 25, 2023 ) -- [x] Stabilization improvements -- [x] User Import - -[4.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.3) ( Aug 29, 2023 ) -- [x] Stabilization improvements - -[4.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.2) ( Aug 9, 2023 ) -- [x] Stabilization improvements - -[4.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.1) ( Jul 18, 2023 ) -- [x] Stabilization improvements - -[4.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v4.0.0) ( Jun 26, 2023 ) -- [x] Migration to .NET 7 -- [x] Improved JavaScript, CSS, and Meta support -- [x] Optimized Client Assembly Loading -- [x] Routable Modules (ie. declarative configuration) -- [x] Site Template improvements -- [x] IEventSubscriber interface - -[3.4.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.3) ( May 3, 2023 ) -- [x] Stabilization improvements - -[3.4.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.2) ( Mar 29, 2023 ) -- [x] Stabilization improvements - -[3.4.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.1) ( Mar 13, 2023 ) -- [x] Stabilization improvements - -[3.4.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.4.0) ( Mar 12, 2023 ) -- [x] Permissions performance optimization -- [x] Connection string management improvements -- [x] XML site map generator -- [x] OIDC integration with User Profiles - -[3.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1) ( Jan 14, 2023 ) -- [x] Stabilization improvements - -[3.3.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.0) ( Jan 12, 2023 ) -- [x] Dynamic Authorization Policies -- [x] Entity-Level Permissions -- [x] Extended Module Permissions - -[3.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1) ( Oct 17, 2022 ) -- [x] Stabilization improvements -- [x] Server Event System - -[3.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.0) ( Sep 13, 2022 ) -- [x] .NET MAUI / Blazor Hybrid support -- [x] Upgrade to Bootstrap 5.2 - -[3.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.3) ( Jun 27, 2022 ) -- [x] Stabilization improvements - -[3.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.2) ( May 14, 2022 ) -- [x] Stabilization improvements - -[3.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.1) ( May 3, 2022 ) -- [x] Stabilization improvements - -[3.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.1.0) ( Apr 5, 2022 ) -- [x] User account lockout support -- [x] Two factor authentication support -- [x] Per-site configuration of password complexity, lockout criteria -- [x] External login support via OAuth2 / OpenID Connect -- [x] Support for Single Sign On (SSO) via OpenID Connect -- [x] External client support via Jwt tokens -- [x] Downstream API support via Jwt tokens -- [x] CSS resource hierarchy support -- [x] Site structure/content migration -- [x] Event log notifications -- [x] 404 page handling -- [x] Property change component notifications -- [x] Support for ES6 JavaScript modules - -[3.0.3](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.3) ( Feb 15, 2022 ) -- [x] Url fragment and anchor navigation support -- [x] Meta tag support in page head -- [x] Html/Text content versioning support - -[3.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.2) ( Jan 16, 2022 ) -- [x] Default alias specification, auto alias registration, redirect logic -- [x] Improvements to visitor tracking and url mapping -- [x] Scheduler enhancements for stop/start, weekly and one-time jobs -- [x] Purge job for daily housekeeping of event log and visitors -- [x] Granular security filtering for Settings - -[3.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.1) ( Dec 12, 2021 ) -- [x] Url mapping for broken links, content migration -- [x] Visitor tracking for usage insights, personalization -- [x] User experience improvements in Page and Module management - -[3.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v3.0.0) ( Nov 11, 2021 ) -- [x] Migration to .NET 6 -- [x] Blazor hosting model flexibility per site -- [x] Blazor WebAssembly prerendering support - -[2.3.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.3.1) ( Sep 27, 2021 ) -- [x] Complete UI migration to Bootstrap 5 and HTML5 form validation -- [x] Improve module/theme installation and add support for commercial extensions -- [x] Replace System.Drawing with ImageSharp -- [x] Image resizing service - -[2.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.2.0) ( Jul 6, 2021 ) -- [x] Bootstrap 5 Upgrade -- [x] Package Service integration -- [x] Default and Shared Resource File inclusion -- [x] Startup Error logging -- [x] API Controller Validation and Logging - -[2.1.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.1.0) ( Jun 4, 2021 ) -- [x] Cross Platform Database Support ( ie. LocalDB, SQL Server, SQLite, MySQL, PostgreSQL ) - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964) -- [x] Utilize EF Core Migrations - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964) -- [x] Public Content Folder support -- [x] Multi-tenant Infrastructure improvements -- [x] User Authorization optimization -- [x] Consolidation of Package Management -- [x] Blazor Server Pre-rendering -- [x] Translation Package installation support - -[2.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.2) ( Apr 19, 2021 ) -- [x] Assorted fixes and user experience improvements - -[2.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.1) ( Feb 27, 2021 ) -- [x] Complete Static Localization of Admin UI - -[2.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v2.0.0) ( Nov 11, 2020 ) -- [x] Migration to .NET 5 -- [x] Static Localization ( ie. labels, help text, etc.. ) -- [x] Improved JavaScript Reference Support -- [x] Performance Optimizations -- [x] Developer Productivity Enhancements - -[1.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v1.0.0) ( May 19, 2020 ) -- [x] Migration to .NET Core 3.2 -- [x] Multi-Tenant ( Shared Database & Isolated Database ) -- [x] Modular Architecture -- [x] Headless API with Swagger Support -- [x] Dynamic Page Compositing Model / Site & Page Management -- [x] Authentication / User Management / Profile Management -- [x] Authorization / Roles Management / Granular Permissions -- [x] Dynamic Routing -- [x] Extensibility via Custom Modules -- [x] Extensibility via Custom Themes -- [x] Event Logging / Audit Trail -- [x] Folder / File Management -- [x] Recycle Bin -- [x] Scheduled Jobs ( Background Processing ) -- [x] Notifications / Email Delivery -- [x] Seamless Upgrade Experience -- [x] Progressive Web Application Support -- [x] JavaScript Lazy Loading -- [x] Dynamic CSS/Lazy Loading - -[POC](https://www.oqtane.org/blog/!/7/announcing-oqtane-a-modular-application-framework-for-blazor) ( May 9, 2019 ) -- [x] Initial public release on GitHub -- [x] .NET Core 3.0 +➡️ Full list and older versions can be found in the [docs roadmap](https://docs.oqtane.org/guides/roadmap/index.html) # Background Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalker/) and is inspired by the DotNetNuke web application framework. Initially created as a proof of concept, Oqtane is a native Blazor application written from the ground up using modern .NET Core technology and a Single Page Application (SPA) architecture. It is a modular application framework offering a fully dynamic page compositing model, multi-site support, designer friendly themes, and extensibility via third party modules. -# Release Announcements - -[Oqtane 5.1](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8) - -[Oqtane 5.0](https://www.oqtane.org/blog/!/75/announcing-oqtane-5-0-for-net-8) - -[Oqtane 4.0](https://www.oqtane.org/blog/!/63/announcing-oqtane-4-0-for-net-7) - -[Oqtane 3.4](https://www.oqtane.org/blog/!/56/oqtane-3-4-0-released) - -[Oqtane 3.3](https://www.oqtane.org/blog/!/54/oqtane-3-3-0-released) - -[Oqtane 3.2](https://www.oqtane.org/blog/!/50/oqtane-3-2-for-net-maui-blazor-hybrid) - -[Oqtane 3.1](https://www.oqtane.org/blog/!/41/oqtane-3-1-released) - -[Oqtane 3.0](https://www.oqtane.org/Resources/Blog/PostId/551/announcing-oqtane-30-for-net-6) - -[Oqtane 2.2](https://www.oqtane.org/Resources/Blog/PostId/549/oqtane-22-upgrades-to-bootstrap-5) - -[Oqtane 2.1](https://www.oqtane.org/Resources/Blog/PostId/548/oqtane-21-now-supports-multiple-databases) - -[Oqtane 2.0](https://www.oqtane.org/Resources/Blog/PostId/544/announcing-oqtane-20-for-net-5) - -[Oqtane 1.0](https://www.oqtane.org/Resources/Blog/PostId/540/announcing-oqtane-10-a-modular-application-framework-for-blazor) - -[Oqtane POC](https://www.oqtane.org/Resources/Blog/PostId/520/announcing-oqtane-a-modular-application-framework-for-blazor) - # Reference Implementations [Built On Blazor!](https://builtonblazor.net) - a showcase of sites built on Blazor From 4770daa7c60a3160ef650263d390763f22fd2f2f Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 30 Apr 2024 19:46:10 +0200 Subject: [PATCH 074/189] Fix for Issue #4210 and part #4209 This I will work on on a different Issue --- .../Modules/Admin/Modules/Settings.razor | 2 +- .../Modules/Admin/Modules/Settings.resx | 3 + .../Themes/Controls/ModuleActionsBase.resx | 150 ++++++++++++++++++ .../Controls/Container/ModuleActionsBase.cs | 17 +- 4 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 0031c729..03102dde 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -94,7 +94,7 @@
} - + @if (_permissions != null) {
diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index b872c727..b08997ee 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -180,4 +180,7 @@ Permissions + + Permissions + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx b/Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx new file mode 100644 index 00000000..fe72e3ea --- /dev/null +++ b/Oqtane.Client/Resources/Themes/Controls/ModuleActionsBase.resx @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Delete Module + + + Export Content + + + Import Content + + + Manage Settings + + + Move Down + + + Move To Bottom + + + Move To Top + + + MoveUp + + + Publish Module + + + Unpublish Module + + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs index 3ca24a69..54e01ef4 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs @@ -9,6 +9,8 @@ using Oqtane.Services; using Oqtane.Shared; using Oqtane.UI; using System.Net; +using static System.Runtime.InteropServices.JavaScript.JSType; +using Microsoft.Extensions.Localization; // ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable MemberCanBePrivate.Global @@ -20,6 +22,7 @@ namespace Oqtane.Themes.Controls [Inject] public NavigationManager NavigationManager { get; set; } [Inject] public IPageModuleService PageModuleService { get; set; } [Inject] public IModuleService ModuleService { get; set; } + [Inject] public IStringLocalizer Localizer { get; set; } [Parameter] public PageState PageState { get; set; } [Parameter] public Module ModuleState { get; set; } @@ -37,30 +40,30 @@ namespace Oqtane.Themes.Controls if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) { - actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = "Manage Settings", Action = async (u, m) => await Settings(u, m) }); + actionList.Add(new ActionViewModel { Icon = Icons.Cog, Name = Localizer["ManageSettings"], Action = async (u, m) => await Settings(u, m) }); if (UserSecurity.ContainsRole(ModuleState.PermissionList, PermissionNames.View, RoleNames.Everyone)) { - actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = "Unpublish Module", Action = async (s, m) => await Unpublish(s, m) }); + actionList.Add(new ActionViewModel { Icon = Icons.CircleX, Name = Localizer["UnpublishModule"], Action = async (s, m) => await Unpublish(s, m) }); } else { - actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = "Publish Module", Action = async (s, m) => await Publish(s, m) }); + actionList.Add(new ActionViewModel { Icon = Icons.CircleCheck, Name = Localizer["PublishModule"], Action = async (s, m) => await Publish(s, m) }); } - actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = "Delete Module", Action = async (u, m) => await DeleteModule(u, m) }); + actionList.Add(new ActionViewModel { Icon = Icons.Trash, Name = Localizer["DeleteModule"], Action = async (u, m) => await DeleteModule(u, m) }); if (ModuleState.ModuleDefinition != null && ModuleState.ModuleDefinition.IsPortable) { actionList.Add(new ActionViewModel { Name = "" }); - actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = "Import Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") }); - actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = "Export Content", Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") }); + actionList.Add(new ActionViewModel { Icon = Icons.CloudUpload, Name = Localizer["ImportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Import") }); + actionList.Add(new ActionViewModel { Icon = Icons.CloudDownload, Name = Localizer["ExportContent"], Action = async (u, m) => await EditUrlAsync(u, m.ModuleId, "Export") }); } actionList.Add(new ActionViewModel { Name = "" }); if (ModuleState.PaneModuleIndex > 0) { - actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = "Move To Top", Action = async (s, m) => await MoveTop(s, m) }); + actionList.Add(new ActionViewModel { Icon = Icons.DataTransferUpload, Name = Localizer["MoveToTop"], Action = async (s, m) => await MoveTop(s, m) }); } if (ModuleState.PaneModuleIndex > 0) From 53217b061d9f141be6f60e5bac0425cba8bf1be4 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 30 Apr 2024 20:07:34 +0200 Subject: [PATCH 075/189] ModuleSettings ContainerSettings #4211 ModuleSettings ContainerSettings #4211 --- .../Resources/Modules/Admin/Modules/Settings.resx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index b872c727..3b7fc1dc 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -156,7 +156,7 @@ Module: - + Module Settings @@ -180,4 +180,10 @@ Permissions + + Container Settings + + + Module Settings + \ No newline at end of file From 4ac827b9e872833df24aabdc80683ac634cf67c9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 30 Apr 2024 16:41:24 -0400 Subject: [PATCH 076/189] fix #4206 - validate folder name for duplicates --- Oqtane.Client/Modules/Admin/Files/Edit.razor | 11 +++++++++-- Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index 05990fe4..e026b7b5 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -170,6 +170,7 @@ try { Folder folder; + if (_folderId != -1) { folder = await FolderService.GetFolderAsync(_folderId); @@ -179,8 +180,6 @@ folder = new Folder(); } - folder.SiteId = PageState.Site.SiteId; - if (_parentId == -1) { folder.ParentId = null; @@ -189,7 +188,15 @@ { folder.ParentId = _parentId; } + + // check for duplicate folder names + if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId)) + { + AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning); + return; + } + folder.SiteId = PageState.Site.SiteId; folder.Name = _name; folder.Type = _type; folder.ImageSizes = _imagesizes; diff --git a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx index cd3d71bb..a8d788c6 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Files/Edit.resx @@ -195,4 +195,7 @@ Folder Management + + Folder Name Specified Already Exists In Parent + \ No newline at end of file From 21e2700da509ac1002e8fcce34e23e740a6f371d Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Wed, 1 May 2024 08:33:51 +0200 Subject: [PATCH 077/189] ModuleSettings Title Localized --- Oqtane.Client/Modules/Admin/Modules/Settings.razor | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 0031c729..9044a79c 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -128,7 +128,7 @@ @code { public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; - public override string Title => "Module Settings"; + public override string Title => Localizer["ModuleSettings.Title"]; private ElementReference form; private bool validated = false; @@ -144,7 +144,7 @@ private PermissionGrid _permissionGrid; private Type _moduleSettingsType; private object _moduleSettings; - private string _moduleSettingsTitle = "Module Settings"; + private string _moduleSettingsTitle; private RenderFragment ModuleSettingsComponent { get; set; } private Type _containerSettingsType; private object _containerSettings; @@ -160,6 +160,7 @@ { _module = ModuleState.ModuleDefinition.Name; _title = ModuleState.Title; + _moduleSettingsTitle = Localizer["ModuleSettings.Heading"]; _pane = ModuleState.Pane; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); _containerType = ModuleState.ContainerType; From 3f742f5f8e6cfa76b657dda596958d15d0d8f956 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 1 May 2024 11:52:07 -0400 Subject: [PATCH 078/189] fix RESX file (add missing element) --- Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx | 1 + 1 file changed, 1 insertion(+) diff --git a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx index 1763d5a2..a91aa36e 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Modules/Settings.resx @@ -182,6 +182,7 @@ Permissions + Container Settings From 0de5c043bba22419c640688e500b48aa722b1e8e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 1 May 2024 15:18:36 -0400 Subject: [PATCH 079/189] fix support for Site-level Scripts in Resources --- Oqtane.Client/UI/SiteRouter.razor | 2 +- Oqtane.Server/Components/App.razor | 50 +++++++++++++++--------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index ec1bc0be..9e13eeb8 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -551,7 +551,7 @@ { foreach (var resource in resources) { - if (resource.Level != ResourceLevel.Site) + if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site) { if (resource.Url.StartsWith("~")) { diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 7de887fc..541d08e4 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -176,15 +176,11 @@ CreateJwtToken(alias); } - // include stylesheets to prevent FOUC + // includes resources var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action); ManageStyleSheets(resources); + ManageScripts(resources, alias); - // scripts - if (_renderMode == RenderModes.Static) - { - ManageScripts(resources, alias); - } if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server) { _reconnectScript = CreateReconnectScript(); @@ -567,13 +563,13 @@ var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType)); if (theme != null) { - resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName)); + resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode); } else { // fallback to default Oqtane theme theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme)); - resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName)); + resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode); } var type = Type.GetType(themeType); if (type != null) @@ -581,7 +577,7 @@ var obj = Activator.CreateInstance(type) as IThemeControl; if (obj != null) { - resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace); + resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode); } } @@ -590,7 +586,7 @@ var typename = ""; if (module.ModuleDefinition != null) { - resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName)); + resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); // handle default action if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) @@ -636,7 +632,7 @@ var obj = Activator.CreateInstance(moduletype) as IModuleControl; if (obj != null) { - resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode); if (action.ToLower() == "settings" && module.ModuleDefinition != null) { // settings components are embedded within a framework settings module @@ -644,7 +640,7 @@ if (moduletype != null) { obj = Activator.CreateInstance(moduletype) as IModuleControl; - resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace); + resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode); } } } @@ -657,32 +653,35 @@ { if (module.ModuleDefinition?.Resources != null) { - resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName)); + resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); } } return resources; } - private List AddResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name) + private List AddResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name, string rendermode) { if (resources != null) { foreach (var resource in resources) { - if (resource.Url.StartsWith("~")) + if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site) { - 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; - } + if (resource.Url.StartsWith("~")) + { + 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 - if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) - { - pageresources.Add(resource.Clone(level, name)); + // ensure resource does not exist already + if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower())) + { + pageresources.Add(resource.Clone(level, name)); + } } } } @@ -693,6 +692,7 @@ { if (resources != null) { + // include stylesheets to prevent FOUC string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff"); int count = 0; foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet)) From 4f5b33d8dfede8e95c6314ef78eab5136d81cc17 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Thu, 2 May 2024 09:03:26 +0200 Subject: [PATCH 080/189] Null or empty check for FormatContent Added null or empty check for the content and alias parameters at the beginning of the method. --- Oqtane.Shared/Shared/Utilities.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 61dd5740..5498a87a 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -139,6 +139,9 @@ namespace Oqtane.Shared public static string FormatContent(string content, Alias alias, string operation) { + if (string.IsNullOrEmpty(content) || alias == null) + return content; + var aliasUrl = (alias != null && !string.IsNullOrEmpty(alias.Path)) ? "/" + alias.Path : ""; switch (operation) { From 653352bff0c90297fac201b6d9e9ac0c5dd64091 Mon Sep 17 00:00:00 2001 From: David Montesinos <90258222+mdmontesinos@users.noreply.github.com> Date: Thu, 2 May 2024 10:36:29 +0200 Subject: [PATCH 081/189] Support for IconOnly in ActionDialog open button --- .../Modules/Controls/ActionDialog.razor | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor index 1125d7b0..defb520d 100644 --- a/Oqtane.Client/Modules/Controls/ActionDialog.razor +++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor @@ -35,11 +35,11 @@ { if (Disabled) { - + } else { - + } } } @@ -83,13 +83,13 @@ else { if (Disabled) { - + } else {
- +
} } @@ -101,6 +101,8 @@ else private bool _editmode = false; private bool _authorized = false; private string _iconSpan = string.Empty; + private string _openIconSpan = string.Empty; + private string _openText = string.Empty; [Parameter] public string Header { get; set; } // required @@ -138,6 +140,9 @@ else [Parameter] public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon + [Parameter] + public bool IconOnly { get; set; } // optional - specifies only icon in opening link + [Parameter] public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering @@ -157,6 +162,8 @@ else { Text = Action; } + _openText = Text; + if (string.IsNullOrEmpty(Class)) { Class = "btn btn-success"; @@ -169,11 +176,17 @@ else if (!string.IsNullOrEmpty(IconName)) { + if (IconOnly) + { + _openText = string.Empty; + } + if (!IconName.Contains(" ")) { IconName = "oi oi-" + IconName; } - _iconSpan = $" "; + _openIconSpan = $"{(IconOnly ? "" : " ")}"; + _iconSpan = $" "; } Text = Localize(nameof(Text), Text); From 332e528012f321c42a0e9945503b18d9c055c71c Mon Sep 17 00:00:00 2001 From: Ikuo Ohba Date: Fri, 3 May 2024 21:18:28 +0900 Subject: [PATCH 082/189] Fix #4223 --- Oqtane.Server/Controllers/FileController.cs | 22 ++++++--------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index aeeda0b2..677d922d 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -760,23 +760,13 @@ namespace Oqtane.Controllers { if (!Directory.Exists(folderpath)) { - string path = ""; - var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries); - foreach (string folder in folders) + try { - path = Utilities.PathCombine(path, folder, Path.DirectorySeparatorChar.ToString()); - if (!Directory.Exists(path)) - { - try - { - Directory.CreateDirectory(path); - } - catch (Exception ex) - { - _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Unable To Create Folder {Folder}", path); - } - } + Directory.CreateDirectory(folderpath); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Unable To Create Folder {Folder}", folderpath); } } } From 021d7e5efcc4a76aca2a6b4ea3495d8f0840ced5 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 3 May 2024 13:37:10 -0400 Subject: [PATCH 083/189] fix #4221 - exception in Module Management when a module has been uninstalled (credit @marceloatoledo) --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index 1e0c427c..0081e3e8 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -306,10 +306,9 @@ _languages = _languages.OrderBy(item => item.Name).ToList(); } - // Group modules by PageId - // Get distinct PageIds where modules are present + // get distinct pages where module exists var distinctPageIds = PageState.Modules - .Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false) + .Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false) .Select(md => md.PageId) .Distinct(); From d047d26dbf43f958350d07a39a095d72a9201ee8 Mon Sep 17 00:00:00 2001 From: Ikuo Ohba Date: Sun, 5 May 2024 12:12:40 +0900 Subject: [PATCH 084/189] Reverted and fixed the source code. --- Oqtane.Server/Controllers/FileController.cs | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 677d922d..4f5bcb2f 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -760,13 +760,23 @@ namespace Oqtane.Controllers { if (!Directory.Exists(folderpath)) { - try + string path = folderpath.StartsWith(Path.DirectorySeparatorChar) ? Path.DirectorySeparatorChar.ToString() : string.Empty; + var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; + string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries); + foreach (string folder in folders) { - Directory.CreateDirectory(folderpath); - } - catch (Exception ex) - { - _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Unable To Create Folder {Folder}", folderpath); + path = Utilities.PathCombine(path, folder, Path.DirectorySeparatorChar.ToString()); + if (!Directory.Exists(path)) + { + try + { + Directory.CreateDirectory(path); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "Unable To Create Folder {Folder}", path); + } + } } } } From a7952a4633a5e30a66de5cad00a319b54c9bd518 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 6 May 2024 15:56:05 -0400 Subject: [PATCH 085/189] add shadow-none to page links in pager --- Oqtane.Client/Modules/Controls/Pager.razor | 72 +++++++++++----------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index a37166ec..8e643f17 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -23,16 +23,16 @@ { } @@ -86,16 +86,16 @@ { } @@ -202,16 +202,16 @@ { } @@ -250,16 +250,16 @@ { } From f7895823cbb289fff4bb48d9f8b5e42fb19f1494 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 7 May 2024 13:48:58 -0400 Subject: [PATCH 086/189] fix #4235 - add space above Logout button in Control Panel --- .../Themes/Controls/Theme/ControlPanelInteractive.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index aa281da2..ab76f0bc 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -216,7 +216,7 @@
- +
From bf4052b550a3aa507306992249a9e84c07cf5d1a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 8 May 2024 14:42:39 -0400 Subject: [PATCH 087/189] require AntiForgery on Static Rendered components --- .../Extensions/ComponentEndpointRouteBuilderExtensions.cs | 2 ++ Oqtane.Server/Startup.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/Oqtane.Server/Extensions/ComponentEndpointRouteBuilderExtensions.cs b/Oqtane.Server/Extensions/ComponentEndpointRouteBuilderExtensions.cs index b464e81d..77731577 100644 --- a/Oqtane.Server/Extensions/ComponentEndpointRouteBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/ComponentEndpointRouteBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Routing; using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Antiforgery; namespace OqtaneSSR.Extensions { @@ -23,6 +24,7 @@ namespace OqtaneSSR.Extensions { routeEndpointBuilder.Metadata.Add(new RootComponentMetadata(typeof(App))); routeEndpointBuilder.Metadata.Add(new ComponentTypeMetadata(typeof(App))); + routeEndpointBuilder.Metadata.Add(new RequireAntiforgeryTokenAttribute()); }); } } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 0a83d275..d4bf0161 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -216,6 +216,7 @@ namespace Oqtane app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); + app.UseAntiforgery(); if (_useSwagger) { From 2ae120c87889622e0c7510b2a207e489a32fa3a8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 9 May 2024 14:42:54 -0400 Subject: [PATCH 088/189] add support for Auto Prerendering --- Oqtane.Client/Modules/Admin/Site/Index.razor | 2 +- Oqtane.Client/Modules/ModuleBase.cs | 2 +- .../Resources/Modules/Admin/Site/Index.resx | 2 +- Oqtane.Client/Resources/SharedResources.resx | 3 ++ .../Controls/Container/ModuleActions.razor | 2 +- .../Themes/Controls/Theme/ControlPanel.razor | 2 +- Oqtane.Client/UI/ModuleInstance.razor | 34 +++++++++++++++---- Oqtane.Client/UI/RenderModeBoundary.razor | 1 - Oqtane.Server/Components/App.razor | 2 +- 9 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 56df0b3e..633760e7 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -340,7 +340,7 @@
diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 46fd01db..4c5bb670 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -52,7 +52,7 @@ namespace Oqtane.Modules public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default - public virtual bool? Prerender { get { return null; } } // prerender at server by default + public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden // url parameters public virtual string UrlParametersTemplate { get; set; } diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 4617bd91..a6b10f64 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -277,7 +277,7 @@ UI Component Settings - Specifies if interactive components should prerender their output + Specifies if interactive components should prerender their output. The default is Auto which determines if a component should be prerendered at runtime based on the environment. Prerender? diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index dd4bc841..fd26490e 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -453,4 +453,7 @@ Static + + Auto + \ No newline at end of file diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor b/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor index 749519a6..d2c7f6a1 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor @@ -10,6 +10,6 @@ } else { - + } } diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index 119f7690..c453117b 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -36,7 +36,7 @@ } else { - + } } diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index 37dd6d20..f501523d 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -1,13 +1,17 @@ @namespace Oqtane.UI @inject SiteState SiteState -@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static) +@if (_comment != null) { - -} -else -{ - + @((MarkupString)_comment) + @if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Static) + { + + } + else + { + + } } @code { @@ -20,6 +24,24 @@ else [CascadingParameter] private Module ModuleState { get; set; } + private bool _prerender; + private string _comment; + + protected override void OnParametersSet() + { + _prerender = ModuleState.Prerender ?? (PageState.Site.Prerender && PageState.User == null); + _comment = ""; + } + [Obsolete("AddModuleMessage is deprecated. Use AddModuleMessage in ModuleBase instead.", false)] public void AddModuleMessage(string message, MessageType type) diff --git a/Oqtane.Client/UI/RenderModeBoundary.razor b/Oqtane.Client/UI/RenderModeBoundary.razor index 2d9c8e07..b1f56793 100644 --- a/Oqtane.Client/UI/RenderModeBoundary.razor +++ b/Oqtane.Client/UI/RenderModeBoundary.razor @@ -10,7 +10,6 @@ { @if (ModuleType != null) { - @((MarkupString)$"") @DynamicComponent @if (_progressIndicator) diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 541d08e4..61aad936 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -139,7 +139,7 @@ { _renderMode = site.RenderMode; _runtime = site.Runtime; - _prerender = site.Prerender; + _prerender = site.Prerender && !Context.User.Identity.IsAuthenticated; Route route = new Route(url, alias.Path); var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); From c597b293b8ab04552661cfb86cb940d882b6ac93 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 9 May 2024 15:08:52 -0400 Subject: [PATCH 089/189] modify prerendering UI options --- Oqtane.Client/Modules/Admin/Site/Index.razor | 4 ++-- Oqtane.Client/Resources/Modules/Admin/Site/Index.resx | 2 +- Oqtane.Client/Resources/SharedResources.resx | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 633760e7..4b77c534 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -337,11 +337,11 @@
- +
diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index a6b10f64..45f3d029 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -280,7 +280,7 @@ Specifies if interactive components should prerender their output. The default is Auto which determines if a component should be prerendered at runtime based on the environment.
- Prerender? + Prerendering: The default render mode for the site diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index fd26490e..a07d7295 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -456,4 +456,10 @@ Auto + + Disabled + + + Enabled + \ No newline at end of file From d57c1e7ff0b7ac848fb3867be03c8c51ab31efb6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 10 May 2024 16:28:19 -0400 Subject: [PATCH 090/189] revert prerender changes and change default --- Oqtane.Client/Modules/Admin/Site/Index.razor | 25 ++++++++++++++++--- .../Resources/Modules/Admin/Site/Index.resx | 4 +-- Oqtane.Client/Resources/SharedResources.resx | 3 --- Oqtane.Client/UI/ModuleInstance.razor | 2 +- Oqtane.Server/Components/App.razor | 2 +- .../Infrastructure/DatabaseManager.cs | 8 +++--- 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 4b77c534..a5626a5d 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -319,7 +319,7 @@
- @@ -337,11 +337,11 @@
- +
@@ -572,6 +572,23 @@ } } + private void RenderModeChanged(ChangeEventArgs e) + { + _rendermode = (string)e.Value; + switch (_rendermode) + { + case RenderModes.Interactive: + _prerender = "True"; + break; + case RenderModes.Static: + _prerender = "False"; + break; + case RenderModes.Headless: + _prerender = "False"; + break; + } + } + private async Task SaveSite() { validated = true; diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 45f3d029..babf23e0 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -277,10 +277,10 @@ UI Component Settings
- Specifies if interactive components should prerender their output. The default is Auto which determines if a component should be prerendered at runtime based on the environment. + Specifies if interactive components should prerender their output on the server - Prerendering: + Prerender: The default render mode for the site diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index a07d7295..4161d868 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -453,9 +453,6 @@ Static - - Auto - Disabled diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index f501523d..8dfec04b 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -29,7 +29,7 @@ protected override void OnParametersSet() { - _prerender = ModuleState.Prerender ?? (PageState.Site.Prerender && PageState.User == null); + _prerender = ModuleState.Prerender ?? PageState.Site.Prerender; _comment = " Exe - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 5.1.1 + 5.1.2 1 14.2 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 49cf9bd5..4b9d0a44 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index cfaef73d..d81c1693 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 5.1.1 + 5.1.2 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v5.1.1/Oqtane.Framework.5.1.1.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/download/v5.1.2/Oqtane.Framework.5.1.2.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 93c2942e..7a489cfb 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index ac34a811..ab1deb4d 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index fe7d85bd..2c51fc0e 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index ed93995d..57e2d394 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Install.zip" -Force \ No newline at end of file diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index c5c23da0..0c0d2f56 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.1.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Upgrade.zip" -Force \ No newline at end of file diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 7fe28c3d..3357bee6 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 3d8d4a78..6ef82a5b 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 https://github.com/oqtane/oqtane.framework Git Oqtane diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index a7d1173e..b2b8c837 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 = "5.1.1"; - 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"; + public static readonly string Version = "5.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"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 4554897c..c9b31857 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net8.0 Exe - 5.1.1 + 5.1.2 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/v5.1.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 https://github.com/oqtane/oqtane.framework Git Oqtane From 5169ed494c230400aaa6a61ced92a2ead1416c7d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 20 May 2024 16:54:11 -0400 Subject: [PATCH 108/189] upgrade to .NET 8.0.5 --- Oqtane.Client/Oqtane.Client.csproj | 6 +++--- .../Oqtane.Database.PostgreSQL.csproj | 4 ++-- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Maui/Oqtane.Maui.csproj | 12 ++++++------ Oqtane.Server/Oqtane.Server.csproj | 16 ++++++++-------- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index a600f3b9..884b79a0 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -22,9 +22,9 @@ - - - + + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 06090744..04093e5a 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -34,8 +34,8 @@ - - + + diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index 3ce0dfa6..f742b253 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 4a8f9de0..80e4e18d 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 0181f307..a7a79ea3 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -65,15 +65,15 @@ - - + + - + - - - + + + diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 3357bee6..f661e715 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -33,19 +33,19 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 6ef82a5b..f16dcd9c 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,8 +19,8 @@ - - + + From 6c4e1d1c414cea5308bfb86955ca4f6d42b67dc0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 20 May 2024 16:59:44 -0400 Subject: [PATCH 109/189] update theme and module templates to .NET SDK 8.0.5 --- .../External/Client/[Owner].Module.[Module].Client.csproj | 6 +++--- .../External/Server/[Owner].Module.[Module].Server.csproj | 8 ++++---- .../External/Client/[Owner].Theme.[Theme].Client.csproj | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) 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 285cf9b7..8088f6cf 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,9 +13,9 @@ - - - + + + 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 d80b6abd..1bdfa524 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 e143330b..4a10f700 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 @@ -12,9 +12,9 @@ - - - + + + From 0988a92d8a648e861a29b57b2f3238c6a18e28d3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 20 May 2024 22:12:01 -0400 Subject: [PATCH 110/189] changed terminology from Library to Headless --- .../Themes/Controls/Theme/ControlPanelInteractive.razor | 2 +- Oqtane.Server/Repository/ModuleDefinitionRepository.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 5f9b1b7c..e2319d4c 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -296,7 +296,7 @@ _containerType = PageState.Site.DefaultContainerType; _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList(); - _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Library").ToList(); + _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList(); } } diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 124897e3..f20c3d13 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -406,7 +406,7 @@ namespace Oqtane.Repository moduledefinition = moduleobject.ModuleDefinition; moduledefinition.ModuleDefinitionName = moduletype.Namespace + ", " + moduletype.Assembly.GetName().Name; moduledefinition.AssemblyName = assembly.GetName().Name; - moduledefinition.Categories = "Library"; + moduledefinition.Categories = "Headless"; moduledefinition.PermissionList = new List { new Permission(PermissionNames.Utilize, RoleNames.Host, true) From af0a64965657cbb8bf17911772e84c7c9f4850ee Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 21 May 2024 11:06:42 -0400 Subject: [PATCH 111/189] fix #4279 - remove Theme Settings tab from Add Page UI --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 33 --------------------- 1 file changed, 33 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 1c3bcadc..b4bc912e 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -198,12 +198,6 @@
- @if (_themeSettingsType != null) - { - - @_themeSettingsComponent - - }
@@ -238,9 +232,6 @@ private string _bodycontent; private string _permissions = null; private PermissionGrid _permissionGrid; - private Type _themeSettingsType; - private object _themeSettings; - private RenderFragment _themeSettingsComponent { get; set; } private bool _refresh = false; protected Page _parent = null; protected Dictionary _icons; @@ -281,7 +272,6 @@ } _effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate); _expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate); - ThemeSettings(); _initialized = true; } else @@ -324,7 +314,6 @@ _themetype = (string)e.Value; _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containertype = _containers.First().TypeName; - ThemeSettings(); StateHasChanged(); // if theme chosen is different than default site theme, display warning message to user @@ -334,28 +323,6 @@ } } - private void ThemeSettings() - { - _themeSettingsType = null; - _themeSettingsComponent = null; - var theme = PageState.Site.Themes.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype))); - if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) - { - _themeSettingsType = Type.GetType(theme.ThemeSettingsType); - if (_themeSettingsType != null) - { - _themeSettingsComponent = builder => - { - builder.OpenComponent(0, _themeSettingsType); - builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary); - builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); }); - builder.CloseComponent(); - }; - } - _refresh = true; - } - } - private async Task SavePage() { validated = true; From e30037c4d11716a70e912119a7c6f078c9b2a549 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 23 May 2024 09:44:42 -0400 Subject: [PATCH 112/189] add ability to specify session duration for visitor tracking --- .../Modules/Admin/Visitors/Index.razor | 19 +++- .../Modules/Admin/Visitors/Index.resx | 8 +- Oqtane.Server/Components/App.razor | 97 +++++++++++-------- 3 files changed, 79 insertions(+), 45 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Visitors/Index.razor b/Oqtane.Client/Modules/Admin/Visitors/Index.razor index 39b14006..ea5510c0 100644 --- a/Oqtane.Client/Modules/Admin/Visitors/Index.razor +++ b/Oqtane.Client/Modules/Admin/Visitors/Index.razor @@ -69,14 +69,20 @@ else -
+
+ +
+ +
+
+
- +
@@ -103,7 +109,8 @@ else private int _page = 1; private List _visitors; private string _tracking; - private string _filter = ""; + private int _duration = 5; + private string _filter = ""; private int _retention = 30; private string _correlation = "true"; @@ -128,7 +135,8 @@ else _tracking = PageState.Site.VisitorTracking.ToString(); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - _filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter); + _duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5")); + _filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter); _retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30")); _correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true"); } @@ -179,7 +187,8 @@ else await SiteService.UpdateSiteAsync(site); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true); + settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true); + settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true); settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true); settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); diff --git a/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx index 28bc46d1..37599ccb 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Visitors/Index.resx @@ -184,7 +184,7 @@ Number of days of visitor activity to retain - Retention (Days): + Retention: Indicate if new visitors to this site should be correlated based on their IP Address @@ -192,4 +192,10 @@ Correlate Visitors? + + The duration of a browsing session considered to be a distinct visit (in minutes) + + + Session Duration: + \ No newline at end of file diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 86b2eb40..2ace2726 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -324,14 +324,26 @@ int? userid = Context.User.UserId(); userid = (userid == -1) ? null : userid; - // check if cookie already exists + // get cookie value + var visitorCookieName = Constants.VisitorCookiePrefix + SiteId.ToString(); + var visitorCookieValue = Context.Request.Cookies[visitorCookieName]; + DateTime expiry = DateTime.MinValue; + if (visitorCookieValue.Contains("|")) + { + var values = visitorCookieValue.Split('|'); + int.TryParse(values[0], out _visitorId); + DateTime.TryParse(values[1], out expiry); + } + else // legacy cookie format + { + int.TryParse(visitorCookieValue, out _visitorId); + } + bool setcookie = false; Visitor visitor = null; - bool addcookie = false; - var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString(); - if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId)) + + if (_visitorId <= 0) { // if enabled use IP Address correlation - _visitorId = -1; var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true")); if (correlate) { @@ -339,12 +351,12 @@ if (visitor != null) { _visitorId = visitor.VisitorId; - addcookie = true; + setcookie = true; } } } - if (_visitorId == -1) + if (_visitorId <= 0) { // create new visitor visitor = new Visitor(); @@ -360,52 +372,59 @@ visitor.VisitedOn = DateTime.UtcNow; visitor = VisitorRepository.AddVisitor(visitor); _visitorId = visitor.VisitorId; - addcookie = true; + setcookie = true; } else { - if (visitor == null) + // check expiry + if (DateTime.UtcNow > expiry) { - // get visitor if it was not previously loaded - visitor = VisitorRepository.GetVisitor(_visitorId); - } - if (visitor != null) - { - // update visitor - visitor.IPAddress = _remoteIPAddress; - visitor.UserAgent = useragent; - visitor.Language = language; - visitor.Url = url; - if (!string.IsNullOrEmpty(referrer)) + if (visitor == null) { - visitor.Referrer = referrer; + // get visitor if not previously loaded + visitor = VisitorRepository.GetVisitor(_visitorId); } - if (userid != null) + if (visitor != null) { - visitor.UserId = userid; + // update visitor + visitor.IPAddress = _remoteIPAddress; + visitor.UserAgent = useragent; + visitor.Language = language; + visitor.Url = url; + if (!string.IsNullOrEmpty(referrer)) + { + visitor.Referrer = referrer; + } + if (userid != null) + { + visitor.UserId = userid; + } + visitor.Visits += 1; + visitor.VisitedOn = DateTime.UtcNow; + VisitorRepository.UpdateVisitor(visitor); + setcookie = true; + } + else + { + // remove cookie if visitor does not exist + Context.Response.Cookies.Delete(visitorCookieName); } - visitor.Visits += 1; - visitor.VisitedOn = DateTime.UtcNow; - VisitorRepository.UpdateVisitor(visitor); - } - else - { - // remove cookie if VisitorId does not exist - Context.Response.Cookies.Delete(VisitorCookie); } } - // append cookie - if (addcookie) + // set cookie + if (setcookie) { + expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5"))); + Context.Response.Cookies.Append( - VisitorCookie, - _visitorId.ToString(), + visitorCookieName, + $"{_visitorId}|{expiry}", new CookieOptions() - { - Expires = DateTimeOffset.UtcNow.AddYears(1), - IsEssential = true - } + { + Expires = DateTimeOffset.UtcNow.AddYears(10), + IsEssential = true + } ); } } From dfe530a76416caa6d19f03b4cb9d0f0ec958e227 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 24 May 2024 22:51:34 -0400 Subject: [PATCH 113/189] fix issues when importing SiteTemplates --- Oqtane.Server/Components/App.razor | 2 +- Oqtane.Server/Repository/SiteRepository.cs | 54 ++++++++++++++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 2ace2726..c32e1f97 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -328,7 +328,7 @@ var visitorCookieName = Constants.VisitorCookiePrefix + SiteId.ToString(); var visitorCookieValue = Context.Request.Cookies[visitorCookieName]; DateTime expiry = DateTime.MinValue; - if (visitorCookieValue.Contains("|")) + if (visitorCookieValue != null && visitorCookieValue.Contains("|")) { var values = visitorCookieValue.Split('|'); int.TryParse(values[0], out _visitorId); diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 59f5db3c..39dd43f2 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -165,22 +165,23 @@ namespace Oqtane.Repository if (!serverstate.IsInitialized) { var site = GetSite(alias.SiteId); - - // initialize theme Assemblies - site.Themes = _themeRepository.GetThemes().ToList(); - - // initialize module Assemblies - var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId); - - // execute migrations - var version = ProcessSiteMigrations(alias, site); - version = ProcessPageTemplates(alias, site, moduleDefinitions, version); - if (site.Version != version) + if (site != null) { - site.Version = version; - UpdateSite(site); - } + // initialize theme Assemblies + site.Themes = _themeRepository.GetThemes().ToList(); + // initialize module Assemblies + var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId); + + // execute migrations + var version = ProcessSiteMigrations(alias, site); + version = ProcessPageTemplates(alias, site, moduleDefinitions, version); + if (site.Version != version) + { + site.Version = version; + UpdateSite(site); + } + } serverstate.IsInitialized = true; } } @@ -411,7 +412,7 @@ namespace Oqtane.Repository } else { - parent = pages.FirstOrDefault(item => item.Path.ToLower() == pageTemplate.Parent.ToLower()); + parent = pages.FirstOrDefault(item => item.Path.ToLower() == ((pageTemplate.Parent == "/") ? "" : pageTemplate.Parent.ToLower())); } page.ParentId = (parent != null) ? parent.PageId : null; page.Path = page.Path.ToLower(); @@ -487,7 +488,21 @@ namespace Oqtane.Repository pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order; pageModule.ContainerType = pageTemplateModule.ContainerType; pageModule.IsDeleted = pageTemplateModule.IsDeleted; - pageModule.Module.PermissionList = pageTemplateModule.PermissionList; + pageModule.Module.PermissionList = new List(); + foreach (var permission in pageTemplateModule.PermissionList) + { + pageModule.Module.PermissionList.Add(new Permission + { + SiteId = permission.SiteId, + EntityName = permission.EntityName, + EntityId = permission.EntityId, + PermissionName = permission.PermissionName, + RoleName = permission.RoleName, + UserId = permission.UserId, + IsAuthorized = permission.IsAuthorized + }); + } + //pageModule.Module.PermissionList = pageTemplateModule.PermissionList; pageModule.Module.AllPages = false; pageModule.Module.IsDeleted = false; try @@ -539,8 +554,11 @@ namespace Oqtane.Repository try { var module = _moduleRepository.GetModule(pageModule.ModuleId); - var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); - ((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version); + if (module != null) + { + var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); + ((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version); + } } catch (Exception ex) { From 9a7a53405124c2ac000cc8b33e0f194ffc16e58d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 28 May 2024 07:55:45 -0400 Subject: [PATCH 114/189] introduce Clone method in Permission model --- Oqtane.Server/Repository/SiteRepository.cs | 12 +----------- Oqtane.Shared/Models/Permission.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 39dd43f2..3328461b 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -491,18 +491,8 @@ namespace Oqtane.Repository pageModule.Module.PermissionList = new List(); foreach (var permission in pageTemplateModule.PermissionList) { - pageModule.Module.PermissionList.Add(new Permission - { - SiteId = permission.SiteId, - EntityName = permission.EntityName, - EntityId = permission.EntityId, - PermissionName = permission.PermissionName, - RoleName = permission.RoleName, - UserId = permission.UserId, - IsAuthorized = permission.IsAuthorized - }); + pageModule.Module.PermissionList.Add(permission.Clone(permission)); } - //pageModule.Module.PermissionList = pageTemplateModule.PermissionList; pageModule.Module.AllPages = false; pageModule.Module.IsDeleted = false; try diff --git a/Oqtane.Shared/Models/Permission.cs b/Oqtane.Shared/Models/Permission.cs index 8d411252..1448e039 100644 --- a/Oqtane.Shared/Models/Permission.cs +++ b/Oqtane.Shared/Models/Permission.cs @@ -101,6 +101,20 @@ namespace Oqtane.Models IsAuthorized = isAuthorized; } + public Permission Clone(Permission permission) + { + return new Permission + { + SiteId = permission.SiteId, + EntityName = permission.EntityName, + EntityId = permission.EntityId, + PermissionName = permission.PermissionName, + RoleName = permission.RoleName, + UserId = permission.UserId, + IsAuthorized = permission.IsAuthorized + }; + } + [Obsolete("The Role property is deprecated", false)] [NotMapped] [JsonIgnore] // exclude from API payload From 0224fd6d54c4ff3a786cad394198255f1a758495 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 28 May 2024 15:17:27 -0400 Subject: [PATCH 115/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 633a3940..b7ce5d9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) was released on Apr 16, 2024 and is primarily a stabilization release, including a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) was released on May 28, 2024 and is primarily a stabilization release, including a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) From d511c6334a91dde273560447c33fa8ee5c25579c Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 28 May 2024 15:20:15 -0400 Subject: [PATCH 116/189] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7ce5d9e..390c7660 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Latest Release -[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) was released on May 28, 2024 and is primarily a stabilization release, including a variety of improvements to the Static Server-Side Rendering support for Blazor in .NET 8. This release includes 40 pull requests by 6 different contributors, pushing the total number of project commits all-time to over 5200. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) was released on May 28, 2024 and is primarily a stabilization release. This release includes 68 pull requests by 9 different contributors, pushing the total number of project commits all-time to over 5300. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Foqtane%2Foqtane.framework%2Fmaster%2Fazuredeploy.json) @@ -63,6 +63,9 @@ Backlog (TBD) - [ ] Folder Providers - [ ] Generative AI Integration +[5.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2) (May 28, 2024) +- [x] Stabilization improvements + [5.1.1](https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.1) (Apr 16, 2024) - [x] Stabilization improvements From cf6b7544b0c6d9eb7863c287db85bcc5d88121e2 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 28 May 2024 15:20:37 -0400 Subject: [PATCH 117/189] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 390c7660..66ff163f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Please note that this project is owned by the .NET Foundation and is governed by **Using Version 5:** -- Install **[.NET 8.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. +- Install **[.NET 8.0.5 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)**. - Install the latest edition (v17.9 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**. From 06f0cc70b8e4b4b3ffcc3d08ee7f6a19ce153c56 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 31 May 2024 16:23:36 -0400 Subject: [PATCH 118/189] scalability improvements --- .../Services/Interfaces/ISiteService.cs | 8 + Oqtane.Client/Services/SiteService.cs | 5 + .../Theme/ControlPanelInteractive.razor | 18 +- Oqtane.Client/UI/PageState.cs | 5 +- Oqtane.Client/UI/SiteRouter.razor | 14 +- Oqtane.Server/Components/App.razor | 27 +- Oqtane.Server/Controllers/ModuleController.cs | 1 - Oqtane.Server/Controllers/SiteController.cs | 7 + .../CacheInvalidationEventSubscriber.cs | 8 +- .../SiteTemplates/LoadTestingSiteTemplate.cs | 162 ++++++++++++ Oqtane.Server/Services/SiteService.cs | 233 ++++++++++-------- Oqtane.Shared/Models/Site.cs | 4 +- 12 files changed, 348 insertions(+), 144 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs diff --git a/Oqtane.Client/Services/Interfaces/ISiteService.cs b/Oqtane.Client/Services/Interfaces/ISiteService.cs index 5541d1ba..8095a743 100644 --- a/Oqtane.Client/Services/Interfaces/ISiteService.cs +++ b/Oqtane.Client/Services/Interfaces/ISiteService.cs @@ -46,6 +46,14 @@ namespace Oqtane.Services /// Task DeleteSiteAsync(int siteId); + /// + /// Returns a list of modules + /// + /// + /// + /// + Task> GetModulesAsync(int siteId, int pageId); + [PrivateApi] [Obsolete("This method is deprecated.", false)] void SetAlias(Alias alias); diff --git a/Oqtane.Client/Services/SiteService.cs b/Oqtane.Client/Services/SiteService.cs index 6432ab9e..38dc5dee 100644 --- a/Oqtane.Client/Services/SiteService.cs +++ b/Oqtane.Client/Services/SiteService.cs @@ -41,6 +41,11 @@ namespace Oqtane.Services await DeleteAsync($"{Apiurl}/{siteId}"); } + public async Task> GetModulesAsync(int siteId, int pageId) + { + return await GetJsonAsync>($"{Apiurl}/modules/{siteId}/{pageId}"); + } + [Obsolete("This method is deprecated.", false)] public void SetAlias(Alias alias) { diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index e2319d4c..932e5de0 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -482,27 +482,19 @@ private void Navigate(string location) { - Module module; + int moduleId; switch (location) { case "Admin": // get admin dashboard moduleid - module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); - if (module != null) - { - NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", module.ModuleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery))); - } + moduleId = int.Parse(PageState.Site.Settings[Constants.AdminDashboardModule]); + NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin", moduleId, "Index", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery))); break; case "Add": case "Edit": - string url = ""; // get page management moduleid - module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule); - if (module != null) - { - url = Utilities.EditUrl(PageState.Alias.Path, "admin/pages", module.ModuleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"); - NavigationManager.NavigateTo(url); - } + moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]); + NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}")); break; } } diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index 71a8ccf1..b8a3bffc 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -9,6 +9,7 @@ namespace Oqtane.UI public Alias Alias { get; set; } public Site Site { get; set; } public Page Page { get; set; } + public List Modules { get; set; } public User User { get; set; } public Uri Uri { get; set; } public Route Route { get; set; } @@ -31,10 +32,6 @@ namespace Oqtane.UI { get { return Site.Pages; } } - public List Modules - { - get { return Site.Modules; } - } public List Languages { get { return Site.Languages; } diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 9e13eeb8..5f537d81 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -96,6 +96,7 @@ { Site site = null; Page page = null; + List modules = null; User user = null; var editmode = false; var refresh = false; @@ -273,11 +274,21 @@ } } + // get modules for current page + if (PageState.Modules == null || PageState.Modules.First().PageId != page.PageId) + { + modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId); + } + else + { + modules = PageState.Modules; + } + // load additional metadata for current page page = ProcessPage(page, site, user, SiteState.Alias); // load additional metadata for modules - (page, site.Modules) = ProcessModules(page, site.Modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); + (page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias); // populate page state (which acts as a client-side cache for subsequent requests) _pagestate = new PageState @@ -285,6 +296,7 @@ Alias = SiteState.Alias, Site = site, Page = page, + Modules = modules, User = user, Uri = new Uri(_absoluteUri, UriKind.Absolute), Route = route, diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index c32e1f97..03baf121 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -132,6 +132,7 @@ _renderMode = site.RenderMode; _runtime = site.Runtime; _prerender = site.Prerender; + var modules = new List(); Route route = new Route(url, alias.Path); var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); @@ -156,6 +157,10 @@ { HandlePageNotFound(site, page, route); } + else + { + modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId); + } if (site.VisitorTracking) { @@ -169,7 +174,7 @@ } // includes resources - var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action); + var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action); ManageStyleSheets(resources); ManageScripts(resources, alias); @@ -215,12 +220,13 @@ _language = _language.Replace("c=", ""); } - // create initial PageState + // create initial PageState _pageState = new PageState { Alias = alias, Site = site, Page = page, + Modules = modules, User = null, Uri = new Uri(url, UriKind.Absolute), Route = route, @@ -591,7 +597,7 @@ CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture))); } - private List GetPageResources(Alias alias, Site site, Page page, int moduleid, string action) + private async Task> GetPageResources(Alias alias, Site site, Page page, List modules, int moduleid, string action) { var resources = new List(); @@ -617,7 +623,7 @@ } } - foreach (Module module in site.Modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid)) + foreach (Module module in modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid)) { var typename = ""; if (module.ModuleDefinition != null) @@ -683,13 +689,16 @@ } } - // site level resources for modules in site - var modules = site.Modules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList(); - foreach (var module in modules) + if (site.RenderMode == RenderModes.Interactive) { - if (module.ModuleDefinition?.Resources != null) + // site level resources for modules in site + var sitemodules = await SiteService.GetModulesAsync(site.SiteId, -1); + foreach (var module in sitemodules.GroupBy(item => item.ModuleDefinition?.ModuleDefinitionName).Select(group => group.First()).ToList()) { - resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); + if (module.ModuleDefinition?.Resources != null) + { + resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode); + } } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 56c75eae..a7c09fcb 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -9,7 +9,6 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using System.Net; -using System.Security.Policy; namespace Oqtane.Controllers { diff --git a/Oqtane.Server/Controllers/SiteController.cs b/Oqtane.Server/Controllers/SiteController.cs index 471cc977..33535d42 100644 --- a/Oqtane.Server/Controllers/SiteController.cs +++ b/Oqtane.Server/Controllers/SiteController.cs @@ -81,5 +81,12 @@ namespace Oqtane.Controllers { await _siteService.DeleteSiteAsync(id); } + + // GET api//modules/5/6 + [HttpGet("modules/{siteId}/{pageId}")] + public async Task> GetModules(int siteId, int pageId) + { + return await _siteService.GetModulesAsync(siteId, pageId); + } } } diff --git a/Oqtane.Server/Infrastructure/EventSubscribers/CacheInvalidationEventSubscriber.cs b/Oqtane.Server/Infrastructure/EventSubscribers/CacheInvalidationEventSubscriber.cs index f143c122..5944ed59 100644 --- a/Oqtane.Server/Infrastructure/EventSubscribers/CacheInvalidationEventSubscriber.cs +++ b/Oqtane.Server/Infrastructure/EventSubscribers/CacheInvalidationEventSubscriber.cs @@ -19,12 +19,8 @@ namespace Oqtane.Infrastructure.EventSubscribers // when site entities change (ie. site, pages, modules, etc...) a site refresh event is raised and the site cache item needs to be refreshed if (syncEvent.EntityName == EntityNames.Site && syncEvent.Action == SyncEventActions.Refresh) { - _cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}*", true); - } - // when user is modified (ie. roles) a a site reload event is raised and the site cache item for the user needs to be refreshed - if (syncEvent.EntityName == EntityNames.User && syncEvent.Action == SyncEventActions.Reload) - { - _cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.SiteId}:{syncEvent.EntityId}", true); + _cache.Remove($"site:{syncEvent.TenantId}:{syncEvent.EntityId}"); + _cache.Remove($"modules:{syncEvent.TenantId}:{syncEvent.EntityId}"); } // when a site entity is updated, the hosting model may have changed so the client assemblies cache items need to be refreshed diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs new file mode 100644 index 00000000..11cbc314 --- /dev/null +++ b/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs @@ -0,0 +1,162 @@ +using Oqtane.Models; +using Oqtane.Infrastructure; +using System.Collections.Generic; +using Oqtane.Repository; +using Microsoft.AspNetCore.Hosting; +using Oqtane.Shared; +using System.IO; +using System; + +namespace Oqtane.SiteTemplates +{ + public class LoadTestingSiteTemplate : ISiteTemplate + { + private readonly IWebHostEnvironment _environment; + private readonly ISiteRepository _siteRepository; + private readonly IFolderRepository _folderRepository; + private readonly IFileRepository _fileRepository; + + int _pages = 5; // root level navigation items + int _children = 10; // submenu items + int _hidden = 150; // hidden pages not part of navigation + int _modules = 10; // modules per page + int _panes = 10; // panes per page (22 max in default theme) + + string[] _content = new string[5]; // random content + string[] _panenames = new string[5]; // default theme panes + + public LoadTestingSiteTemplate(IWebHostEnvironment environment, ISiteRepository siteRepository, IFolderRepository folderRepository, IFileRepository fileRepository) + { + _environment = environment; + _siteRepository = siteRepository; + _folderRepository = folderRepository; + _fileRepository = fileRepository; + + _content[0] = "

Lorem ipsum dolor sit amet. Nam atque accusantium vel omnis obcaecati nam magnam fugit aut omnis repudiandae. Non reiciendis inventore sit voluptas vero et modi distinctio qui voluptate corrupti qui neque minima aut culpa rerum? Cum dolorem cupiditate ut voluptatem tempore aut sunt dolor aut facilis veniam. Sit culpa sapiente est consequatur saepe ut dolore quasi id fugit totam non debitis natus ea quis autem?

Et quasi veritatis ad aliquam beatae in voluptatem galisum in sunt ducimus. Sed corporis autem ut voluptatum cumque aut nisi expedita et nulla aliquam qui placeat aperiam? Est blanditiis nostrum et laborum mollitia non consequatur molestiae. Sed iste ducimus est eaque animi 33 autem quaerat.

"; + _content[1] = "

Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ornare aenean euismod elementum nisi quis eleifend quam. Mauris pharetra et ultrices neque ornare aenean euismod. Amet consectetur adipiscing elit duis tristique. Elit scelerisque mauris pellentesque pulvinar pellentesque. Tellus in metus vulputate eu. Risus nec feugiat in fermentum posuere urna nec tincidunt. Porttitor leo a diam sollicitudin tempor id eu. Elit sed vulputate mi sit. Aliquet bibendum enim facilisis gravida. Dictum varius duis at consectetur lorem donec massa sapien. Ipsum nunc aliquet bibendum enim facilisis. In eu mi bibendum neque egestas congue quisque. Eget duis at tellus at. Nunc sed velit dignissim sodales ut. Elementum curabitur vitae nunc sed velit. Lacinia quis vel eros donec ac odio tempor orci. Id volutpat lacus laoreet non curabitur gravida arcu ac tortor. Facilisis mauris sit amet massa vitae tortor.

"; + _content[2] = "

Rhoncus dolor purus non enim praesent elementum facilisis leo. Aenean pharetra magna ac placerat vestibulum lectus mauris. Facilisis leo vel fringilla est ullamcorper eget nulla facilisi. Elementum nibh tellus molestie nunc. Id leo in vitae turpis massa sed elementum. Feugiat in ante metus dictum at tempor. Elit sed vulputate mi sit amet mauris commodo quis imperdiet. Non quam lacus suspendisse faucibus interdum posuere lorem ipsum. Mi quis hendrerit dolor magna eget est lorem. Integer malesuada nunc vel risus commodo viverra maecenas accumsan. Odio ut enim blandit volutpat. Nec sagittis aliquam malesuada bibendum arcu vitae elementum. Volutpat maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Egestas integer eget aliquet nibh. Pellentesque habitant morbi tristique senectus et. Fermentum leo vel orci porta non pulvinar neque. Ut sem nulla pharetra diam sit amet. Proin nibh nisl condimentum id venenatis.

"; + _content[3] = "

Pellentesque habitant morbi tristique senectus. In metus vulputate eu scelerisque felis imperdiet proin fermentum. Dolor purus non enim praesent. Sed adipiscing diam donec adipiscing tristique risus nec feugiat. Elementum nisi quis eleifend quam adipiscing. Dapibus ultrices in iaculis nunc sed augue lacus viverra. Enim blandit volutpat maecenas volutpat. Ut placerat orci nulla pellentesque dignissim enim sit amet venenatis. Eget gravida cum sociis natoque penatibus et magnis dis parturient. In hac habitasse platea dictumst quisque sagittis purus. Scelerisque varius morbi enim nunc faucibus a pellentesque sit. Pretium viverra suspendisse potenti nullam ac tortor vitae purus. Sagittis orci a scelerisque purus semper eget duis at tellus. Sed vulputate mi sit amet mauris commodo. Tincidunt praesent semper feugiat nibh sed. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Elementum eu facilisis sed odio morbi quis. Mauris in aliquam sem fringilla ut morbi tincidunt augue. Diam quam nulla porttitor massa id neque.

"; + _content[4] = "

Est nemo nemo hic quisquam dolores et dignissimos voluptas? Est adipisci quos et omnis dolorum qui galisum vero ut galisum accusantium vel accusamus odit ab fuga voluptatem. At officiis illo quo quia modi eos dicta dolor sed voluptates quis id eligendi distinctio sed ratione consequatur. Et voluptatibus consequatur quo voluptatem architecto est sapiente voluptas.

"; + + _panenames = (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").Split(','); + } + + public string Name + { + get { return "Load Testing Site Template"; } + } + + public List CreateSite(Site site) + { + List _pageTemplates = new List(); + + // pages + for (int page = 1; page <= _pages; page++) + { + _pageTemplates.Add(new PageTemplate + { + Name = $"Page{page}", + Parent = "", + Path = (page == 1) ? "/" : $"page{page}", + Order = (page * 2) - 1 + 10, + Icon = "oi oi-home", + IsNavigation = true, + IsClickable = true, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = GetPageTemplateModules() + }); + + for (int child = 1; child <= _children; child++) + { + _pageTemplates.Add(new PageTemplate + { + Name = $"Child{child}", + Parent = (page == 1) ? "/" : $"page{page}", + Path = (page == 1) ? "" : $"page{page}/child{child}", + Order = (child * 2) - 1, + Icon = "oi oi-caret-right", + IsNavigation = true, + IsClickable = true, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = GetPageTemplateModules() + }); + } + } + + // hidden pages + for (int hidden = 1; hidden <= _hidden; hidden++) + { + _pageTemplates.Add(new PageTemplate + { + Name = $"Hidden{hidden}", + Parent = "", + Path = $"hidden{hidden}", + Order = (hidden * 2) - 1 + 10, + Icon = "", + IsNavigation = false, + IsClickable = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = GetPageTemplateModules() + }); + } + + if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"))) + { + string folderpath = Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", site.TenantId.ToString(), "Sites", site.SiteId.ToString(), Path.DirectorySeparatorChar.ToString()); + Directory.CreateDirectory(folderpath); + if (!System.IO.File.Exists(Path.Combine(folderpath, "logo-white.png"))) + { + System.IO.File.Copy(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"), Path.Combine(folderpath, "logo-white.png")); + } + Folder folder = _folderRepository.GetFolder(site.SiteId, ""); + Oqtane.Models.File file = _fileRepository.AddFile(new Oqtane.Models.File { FolderId = folder.FolderId, Name = "logo-white.png", Extension = "png", Size = 8192, ImageHeight = 80, ImageWidth = 250 }); + site.LogoFileId = file.FileId; + _siteRepository.UpdateSite(site); + } + + return _pageTemplates; + } + + private List GetPageTemplateModules() + { + Random rnd = new Random(); + var _pageTemplateModules = new List(); + for (int module = 1; module <= _modules; module++) + { + _pageTemplateModules.Add(new PageTemplateModule + { + ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", + Title = $"Module{module}", + Pane = _panenames[rnd.Next(0, _panes - 1)], + Order = (module* 2) - 1, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = _content[rnd.Next(0, 4)] + }); + } + return _pageTemplateModules; + } + } +} diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index b46c9b80..2c9c1786 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -62,35 +62,22 @@ namespace Oqtane.Services public async Task GetSiteAsync(int siteId) { - if (!_accessor.HttpContext.User.Identity.IsAuthenticated) + var site = await _cache.GetOrCreateAsync($"site:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => { - // unauthenticated - return await _cache.GetOrCreateAsync($"site:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => - { - entry.SlidingExpiration = TimeSpan.FromMinutes(30); - return await GetSite(siteId); - }, true); - } - else // authenticated + entry.SlidingExpiration = TimeSpan.FromMinutes(30); + return await GetSite(siteId); + }); + + var pages = new List(); + foreach (Page page in site.Pages) { - // is only in registered users role - cache by role - if (_accessor.HttpContext.User.IsOnlyInRole(RoleNames.Registered)) + if (!page.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, page.PermissionList))) { - return await _cache.GetOrCreateAsync($"site:{_accessor.HttpContext.GetAlias().SiteKey}:{RoleNames.Registered}", async entry => - { - entry.SlidingExpiration = TimeSpan.FromMinutes(30); - return await GetSite(siteId); - }, true); - } - else // cache by user - { - return await _cache.GetOrCreateAsync($"site:{_accessor.HttpContext.GetAlias().SiteKey}:{_accessor.HttpContext.User.UserId()}", async entry => - { - entry.SlidingExpiration = TimeSpan.FromMinutes(30); - return await GetSite(siteId); - }, true); + pages.Add(page); } } + + return site; } private async Task GetSite(int siteid) @@ -115,62 +102,17 @@ namespace Oqtane.Services site.Pages = new List(); foreach (Page page in _pages.GetPages(site.SiteId)) { - if (!page.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, page.PermissionList) && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, page.PermissionList))) - { - page.Settings = settings.Where(item => item.EntityId == page.PageId) - .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, page.PermissionList)) - .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); - site.Pages.Add(page); - } + page.Settings = settings.Where(item => item.EntityId == page.PageId) + .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, page.PermissionList)) + .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); + site.Pages.Add(page); } - site.Pages = GetPagesHierarchy(site.Pages); - // modules - List moduledefinitions = _moduleDefinitions.GetModuleDefinitions(site.SiteId).ToList(); - settings = _settings.GetSettings(EntityNames.Module).ToList(); - site.Modules = new List(); - foreach (PageModule pagemodule in _pageModules.GetPageModules(site.SiteId).Where(pm => !pm.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, pm.Module.PermissionList))) - { - if (Utilities.IsPageModuleVisible(pagemodule.EffectiveDate, pagemodule.ExpiryDate) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, pagemodule.Module.PermissionList)) - { - Module module = new Module - { - SiteId = pagemodule.Module.SiteId, - ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName, - AllPages = pagemodule.Module.AllPages, - PermissionList = pagemodule.Module.PermissionList, - CreatedBy = pagemodule.Module.CreatedBy, - CreatedOn = pagemodule.Module.CreatedOn, - ModifiedBy = pagemodule.Module.ModifiedBy, - ModifiedOn = pagemodule.Module.ModifiedOn, - DeletedBy = pagemodule.DeletedBy, - DeletedOn = pagemodule.DeletedOn, - IsDeleted = pagemodule.IsDeleted, - - PageModuleId = pagemodule.PageModuleId, - ModuleId = pagemodule.ModuleId, - PageId = pagemodule.PageId, - Title = pagemodule.Title, - Pane = pagemodule.Pane, - Order = pagemodule.Order, - ContainerType = pagemodule.ContainerType, - EffectiveDate = pagemodule.EffectiveDate, - ExpiryDate = pagemodule.ExpiryDate, - - ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)), - - Settings = settings - .Where(item => item.EntityId == pagemodule.ModuleId) - .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, pagemodule.Module.PermissionList)) - .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue) - }; - - site.Modules.Add(module); - } - } - - site.Modules = site.Modules.OrderBy(item => item.PageId).ThenBy(item => item.Pane).ThenBy(item => item.Order).ToList(); + // framework modules + var modules = await GetModulesAsync(site.SiteId); + site.Settings.Add(Constants.AdminDashboardModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule).ModuleId.ToString()); + site.Settings.Add(Constants.PageManagementModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule).ModuleId.ToString()); // languages site.Languages = _languages.GetLanguages(site.SiteId).ToList(); @@ -191,6 +133,46 @@ namespace Oqtane.Services return site; } + private static List GetPagesHierarchy(List pages) + { + List hierarchy = new List(); + Action, Page> getPath = null; + getPath = (pageList, page) => + { + IEnumerable children; + int level; + if (page == null) + { + level = -1; + children = pages.Where(item => item.ParentId == null); + } + else + { + level = page.Level; + children = pages.Where(item => item.ParentId == page.PageId); + } + foreach (Page child in children) + { + child.Level = level + 1; + child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation); + hierarchy.Add(child); + getPath(pageList, child); + } + }; + pages = pages.OrderBy(item => item.Order).ToList(); + getPath(pages, null); + + // add any non-hierarchical items to the end of the list + foreach (Page page in pages) + { + if (hierarchy.Find(item => item.PageId == page.PageId) == null) + { + hierarchy.Add(page); + } + } + return hierarchy; + } + public async Task AddSiteAsync(Site site) { if (_accessor.HttpContext.User.IsInRole(RoleNames.Host)) @@ -256,44 +238,79 @@ namespace Oqtane.Services } } - private static List GetPagesHierarchy(List pages) + public async Task> GetModulesAsync(int siteId, int pageId) { - List hierarchy = new List(); - Action, Page> getPath = null; - getPath = (pageList, page) => + var sitemodules = await _cache.GetOrCreateAsync($"modules:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => { - IEnumerable children; - int level; - if (page == null) - { - level = -1; - children = pages.Where(item => item.ParentId == null); - } - else - { - level = page.Level; - children = pages.Where(item => item.ParentId == page.PageId); - } - foreach (Page child in children) - { - child.Level = level + 1; - child.HasChildren = pages.Any(item => item.ParentId == child.PageId && !item.IsDeleted && item.IsNavigation); - hierarchy.Add(child); - getPath(pageList, child); - } - }; - pages = pages.OrderBy(item => item.Order).ToList(); - getPath(pages, null); + entry.SlidingExpiration = TimeSpan.FromMinutes(30); + return await GetModulesAsync(siteId); + }); - // add any non-hierarchical items to the end of the list - foreach (Page page in pages) + var modules = new List(); + foreach (Module module in sitemodules.Where(item => (item.PageId == pageId || pageId == -1) && !item.IsDeleted && _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.View, item.PermissionList))) { - if (hierarchy.Find(item => item.PageId == page.PageId) == null) + if (Utilities.IsPageModuleVisible(module.EffectiveDate, module.ExpiryDate) || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, module.PermissionList)) { - hierarchy.Add(page); + modules.Add(module); } } - return hierarchy; + return modules; + } + + public async Task> GetModulesAsync(int siteId) + { + return await _cache.GetOrCreateAsync($"modules:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => + { + entry.SlidingExpiration = TimeSpan.FromMinutes(30); + return await GetModules(siteId); + }); + } + + private async Task> GetModules(int siteId) + { + await Task.Yield(); // force method to async + + List moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList(); + var settings = _settings.GetSettings(EntityNames.Module).ToList(); + var modules = new List(); + + foreach (PageModule pagemodule in _pageModules.GetPageModules(siteId)) + { + Module module = new Module + { + SiteId = pagemodule.Module.SiteId, + ModuleDefinitionName = pagemodule.Module.ModuleDefinitionName, + AllPages = pagemodule.Module.AllPages, + PermissionList = pagemodule.Module.PermissionList, + CreatedBy = pagemodule.Module.CreatedBy, + CreatedOn = pagemodule.Module.CreatedOn, + ModifiedBy = pagemodule.Module.ModifiedBy, + ModifiedOn = pagemodule.Module.ModifiedOn, + DeletedBy = pagemodule.DeletedBy, + DeletedOn = pagemodule.DeletedOn, + IsDeleted = pagemodule.IsDeleted, + + PageModuleId = pagemodule.PageModuleId, + ModuleId = pagemodule.ModuleId, + PageId = pagemodule.PageId, + Title = pagemodule.Title, + Pane = pagemodule.Pane, + Order = pagemodule.Order, + ContainerType = pagemodule.ContainerType, + EffectiveDate = pagemodule.EffectiveDate, + ExpiryDate = pagemodule.ExpiryDate, + + ModuleDefinition = _moduleDefinitions.FilterModuleDefinition(moduledefinitions.Find(item => item.ModuleDefinitionName == pagemodule.Module.ModuleDefinitionName)), + + Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) + .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(_accessor.HttpContext.User, PermissionNames.Edit, pagemodule.Module.PermissionList)) + .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue) + }; + + modules.Add(module); + } + + return modules.OrderBy(item => item.PageId).ThenBy(item => item.Pane).ThenBy(item => item.Order).ToList(); } [Obsolete("This method is deprecated.", false)] diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index 0a77642b..3dfa5691 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -126,8 +126,8 @@ namespace Oqtane.Models [NotMapped] public List Pages { get; set; } - [NotMapped] - public List Modules { get; set; } + //[NotMapped] + //public List Modules { get; set; } [NotMapped] public List Languages { get; set; } From e5567f2f46bef141a40374f36de7c52dcc6e46fb Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 31 May 2024 16:51:27 -0400 Subject: [PATCH 119/189] remove LoadTestingSiteTemplate --- .../SiteTemplates/LoadTestingSiteTemplate.cs | 162 ------------------ 1 file changed, 162 deletions(-) delete mode 100644 Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs deleted file mode 100644 index 11cbc314..00000000 --- a/Oqtane.Server/Infrastructure/SiteTemplates/LoadTestingSiteTemplate.cs +++ /dev/null @@ -1,162 +0,0 @@ -using Oqtane.Models; -using Oqtane.Infrastructure; -using System.Collections.Generic; -using Oqtane.Repository; -using Microsoft.AspNetCore.Hosting; -using Oqtane.Shared; -using System.IO; -using System; - -namespace Oqtane.SiteTemplates -{ - public class LoadTestingSiteTemplate : ISiteTemplate - { - private readonly IWebHostEnvironment _environment; - private readonly ISiteRepository _siteRepository; - private readonly IFolderRepository _folderRepository; - private readonly IFileRepository _fileRepository; - - int _pages = 5; // root level navigation items - int _children = 10; // submenu items - int _hidden = 150; // hidden pages not part of navigation - int _modules = 10; // modules per page - int _panes = 10; // panes per page (22 max in default theme) - - string[] _content = new string[5]; // random content - string[] _panenames = new string[5]; // default theme panes - - public LoadTestingSiteTemplate(IWebHostEnvironment environment, ISiteRepository siteRepository, IFolderRepository folderRepository, IFileRepository fileRepository) - { - _environment = environment; - _siteRepository = siteRepository; - _folderRepository = folderRepository; - _fileRepository = fileRepository; - - _content[0] = "

Lorem ipsum dolor sit amet. Nam atque accusantium vel omnis obcaecati nam magnam fugit aut omnis repudiandae. Non reiciendis inventore sit voluptas vero et modi distinctio qui voluptate corrupti qui neque minima aut culpa rerum? Cum dolorem cupiditate ut voluptatem tempore aut sunt dolor aut facilis veniam. Sit culpa sapiente est consequatur saepe ut dolore quasi id fugit totam non debitis natus ea quis autem?

Et quasi veritatis ad aliquam beatae in voluptatem galisum in sunt ducimus. Sed corporis autem ut voluptatum cumque aut nisi expedita et nulla aliquam qui placeat aperiam? Est blanditiis nostrum et laborum mollitia non consequatur molestiae. Sed iste ducimus est eaque animi 33 autem quaerat.

"; - _content[1] = "

Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ornare aenean euismod elementum nisi quis eleifend quam. Mauris pharetra et ultrices neque ornare aenean euismod. Amet consectetur adipiscing elit duis tristique. Elit scelerisque mauris pellentesque pulvinar pellentesque. Tellus in metus vulputate eu. Risus nec feugiat in fermentum posuere urna nec tincidunt. Porttitor leo a diam sollicitudin tempor id eu. Elit sed vulputate mi sit. Aliquet bibendum enim facilisis gravida. Dictum varius duis at consectetur lorem donec massa sapien. Ipsum nunc aliquet bibendum enim facilisis. In eu mi bibendum neque egestas congue quisque. Eget duis at tellus at. Nunc sed velit dignissim sodales ut. Elementum curabitur vitae nunc sed velit. Lacinia quis vel eros donec ac odio tempor orci. Id volutpat lacus laoreet non curabitur gravida arcu ac tortor. Facilisis mauris sit amet massa vitae tortor.

"; - _content[2] = "

Rhoncus dolor purus non enim praesent elementum facilisis leo. Aenean pharetra magna ac placerat vestibulum lectus mauris. Facilisis leo vel fringilla est ullamcorper eget nulla facilisi. Elementum nibh tellus molestie nunc. Id leo in vitae turpis massa sed elementum. Feugiat in ante metus dictum at tempor. Elit sed vulputate mi sit amet mauris commodo quis imperdiet. Non quam lacus suspendisse faucibus interdum posuere lorem ipsum. Mi quis hendrerit dolor magna eget est lorem. Integer malesuada nunc vel risus commodo viverra maecenas accumsan. Odio ut enim blandit volutpat. Nec sagittis aliquam malesuada bibendum arcu vitae elementum. Volutpat maecenas volutpat blandit aliquam etiam erat velit scelerisque in. Egestas integer eget aliquet nibh. Pellentesque habitant morbi tristique senectus et. Fermentum leo vel orci porta non pulvinar neque. Ut sem nulla pharetra diam sit amet. Proin nibh nisl condimentum id venenatis.

"; - _content[3] = "

Pellentesque habitant morbi tristique senectus. In metus vulputate eu scelerisque felis imperdiet proin fermentum. Dolor purus non enim praesent. Sed adipiscing diam donec adipiscing tristique risus nec feugiat. Elementum nisi quis eleifend quam adipiscing. Dapibus ultrices in iaculis nunc sed augue lacus viverra. Enim blandit volutpat maecenas volutpat. Ut placerat orci nulla pellentesque dignissim enim sit amet venenatis. Eget gravida cum sociis natoque penatibus et magnis dis parturient. In hac habitasse platea dictumst quisque sagittis purus. Scelerisque varius morbi enim nunc faucibus a pellentesque sit. Pretium viverra suspendisse potenti nullam ac tortor vitae purus. Sagittis orci a scelerisque purus semper eget duis at tellus. Sed vulputate mi sit amet mauris commodo. Tincidunt praesent semper feugiat nibh sed. Condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Elementum eu facilisis sed odio morbi quis. Mauris in aliquam sem fringilla ut morbi tincidunt augue. Diam quam nulla porttitor massa id neque.

"; - _content[4] = "

Est nemo nemo hic quisquam dolores et dignissimos voluptas? Est adipisci quos et omnis dolorum qui galisum vero ut galisum accusantium vel accusamus odit ab fuga voluptatem. At officiis illo quo quia modi eos dicta dolor sed voluptates quis id eligendi distinctio sed ratione consequatur. Et voluptatibus consequatur quo voluptatem architecto est sapiente voluptas.

"; - - _panenames = (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").Split(','); - } - - public string Name - { - get { return "Load Testing Site Template"; } - } - - public List CreateSite(Site site) - { - List _pageTemplates = new List(); - - // pages - for (int page = 1; page <= _pages; page++) - { - _pageTemplates.Add(new PageTemplate - { - Name = $"Page{page}", - Parent = "", - Path = (page == 1) ? "/" : $"page{page}", - Order = (page * 2) - 1 + 10, - Icon = "oi oi-home", - IsNavigation = true, - IsClickable = true, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = GetPageTemplateModules() - }); - - for (int child = 1; child <= _children; child++) - { - _pageTemplates.Add(new PageTemplate - { - Name = $"Child{child}", - Parent = (page == 1) ? "/" : $"page{page}", - Path = (page == 1) ? "" : $"page{page}/child{child}", - Order = (child * 2) - 1, - Icon = "oi oi-caret-right", - IsNavigation = true, - IsClickable = true, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = GetPageTemplateModules() - }); - } - } - - // hidden pages - for (int hidden = 1; hidden <= _hidden; hidden++) - { - _pageTemplates.Add(new PageTemplate - { - Name = $"Hidden{hidden}", - Parent = "", - Path = $"hidden{hidden}", - Order = (hidden * 2) - 1 + 10, - Icon = "", - IsNavigation = false, - IsClickable = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = GetPageTemplateModules() - }); - } - - if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"))) - { - string folderpath = Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", site.TenantId.ToString(), "Sites", site.SiteId.ToString(), Path.DirectorySeparatorChar.ToString()); - Directory.CreateDirectory(folderpath); - if (!System.IO.File.Exists(Path.Combine(folderpath, "logo-white.png"))) - { - System.IO.File.Copy(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"), Path.Combine(folderpath, "logo-white.png")); - } - Folder folder = _folderRepository.GetFolder(site.SiteId, ""); - Oqtane.Models.File file = _fileRepository.AddFile(new Oqtane.Models.File { FolderId = folder.FolderId, Name = "logo-white.png", Extension = "png", Size = 8192, ImageHeight = 80, ImageWidth = 250 }); - site.LogoFileId = file.FileId; - _siteRepository.UpdateSite(site); - } - - return _pageTemplates; - } - - private List GetPageTemplateModules() - { - Random rnd = new Random(); - var _pageTemplateModules = new List(); - for (int module = 1; module <= _modules; module++) - { - _pageTemplateModules.Add(new PageTemplateModule - { - ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", - Title = $"Module{module}", - Pane = _panenames[rnd.Next(0, _panes - 1)], - Order = (module* 2) - 1, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = _content[rnd.Next(0, 4)] - }); - } - return _pageTemplateModules; - } - } -} From 83f329d93c2966490c1dcb5fb150044d1798e012 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 1 Jun 2024 09:08:43 +0800 Subject: [PATCH 120/189] Fix Add Page Issue --- Oqtane.Client/UI/SiteRouter.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 5f537d81..79e2bef6 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -275,7 +275,7 @@ } // get modules for current page - if (PageState.Modules == null || PageState.Modules.First().PageId != page.PageId) + if (PageState.Modules == null || (PageState.Modules.Any() && PageState.Modules.First().PageId != page.PageId)) { modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId); } From 694cda0e99f1b71bd6c238dadadeaa8ede7cf73c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 3 Jun 2024 07:42:22 -0400 Subject: [PATCH 121/189] changes as a result of #4299 related to PageState.Modules --- Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor | 3 ++- Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor | 5 ++++- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 2 +- Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor | 2 +- .../Themes/Controls/Theme/ControlPanelInteractive.razor | 6 +++--- Oqtane.Client/UI/Pane.razor | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index 0081e3e8..0a8803bf 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -307,7 +307,8 @@ } // get distinct pages where module exists - var distinctPageIds = PageState.Modules + var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); + var distinctPageIds = modules .Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false) .Select(md => md.PageId) .Distinct(); diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor index 485f027f..89b10381 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Index.razor @@ -1,6 +1,7 @@ @namespace Oqtane.Modules.Admin.ModuleDefinitions @inherits ModuleBase @inject NavigationManager NavigationManager +@inject IModuleService ModuleService @inject IModuleDefinitionService ModuleDefinitionService @inject IPackageService PackageService @inject IStringLocalizer Localizer @@ -70,7 +71,7 @@ else } - @if (context.AssemblyName == Constants.ClientId || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) + @if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null) { @SharedLocalizer["Yes"] } @@ -99,6 +100,7 @@ else } @code { + private List _modules; private List _allModuleDefinitions; private List _moduleDefinitions; private List _packages; @@ -111,6 +113,7 @@ else { try { + _modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId); _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList(); await LoadModuleDefinitions(); diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 59e386b1..334d9661 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -415,7 +415,7 @@ _permissions = _page.PermissionList; // page modules - _pageModules = PageState.Modules.Where(m => m.PageId == _page.PageId).ToList(); + _pageModules = PageState.Modules; // audit _createdby = _page.CreatedBy; diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index c453117b..17279974 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -73,7 +73,7 @@ } else { - foreach (var module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId)) + foreach (var module in PageState.Modules) { if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, module.PermissionList)) { diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 932e5de0..d091327c 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -346,13 +346,13 @@ _moduleId = "-"; } - private void PageChanged(ChangeEventArgs e) + private async Task PageChanged(ChangeEventArgs e) { _pageId = (string)e.Value; if (_pageId != "-") { - _modules = PageState.Modules - .Where(module => module.PageId == int.Parse(_pageId) && + _modules = await ModuleService.GetModulesAsync(PageState.Page.SiteId); + _modules = _modules.Where(module => module.PageId == int.Parse(_pageId) && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) && (_moduleType == "add" || module.ModuleDefinition.IsPortable)) .ToList(); diff --git a/Oqtane.Client/UI/Pane.razor b/Oqtane.Client/UI/Pane.razor index 606a83b9..70226c2e 100644 --- a/Oqtane.Client/UI/Pane.razor +++ b/Oqtane.Client/UI/Pane.razor @@ -43,7 +43,7 @@ else DynamicComponent = builder => { - foreach (Module module in PageState.Modules.Where(item => item.PageId == PageState.Page.PageId)) + foreach (Module module in PageState.Modules) { // set renderid - this allows the framework to determine which components should be rendered when PageState changes if (module.RenderId != PageState.RenderId) From 9d85ca07f47c9ee5abad20190868a720e66b771d Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 3 Jun 2024 21:19:42 +0800 Subject: [PATCH 122/189] #4303: add search function. --- .../Modules/SearchResults/Index.razor | 147 ++++++++++++ .../Modules/SearchResults/ModuleInfo.cs | 25 ++ .../Services/ISearchResultsService.cs | 13 ++ .../Services/SearchResultsService.cs | 23 ++ .../Modules/SearchResults/Settings.razor | 46 ++++ .../Modules/SearchResults/Index.resx | 156 +++++++++++++ .../Modules/SearchResults/Settings.resx | 123 ++++++++++ .../Resources/Themes/Controls/Search.resx | 126 +++++++++++ .../Themes/BlazorTheme/Themes/Default.razor | 4 +- .../Themes/Controls/Theme/Search.razor | 57 +++++ .../Themes/OqtaneTheme/Themes/Default.razor | 7 +- .../OqtaneServiceCollectionExtensions.cs | 4 + .../Infrastructure/Jobs/SearchIndexJob.cs | 88 +++++++ .../SiteTemplates/DefaultSiteTemplate.cs | 24 ++ .../Search/ModuleSearchIndexManager.cs | 127 +++++++++++ .../Search/ModuleSearchResultManager.cs | 69 ++++++ .../Managers/Search/PageSearchIndexManager.cs | 94 ++++++++ .../Search/PageSearchResultManager.cs | 46 ++++ .../Managers/Search/SearchIndexManagerBase.cs | 34 +++ .../SearchDocumentEntityBuilder.cs | 64 ++++++ .../SearchDocumentPropertyEntityBuilder.cs | 40 ++++ .../SearchDocumentTagEntityBuilder.cs | 37 +++ .../Tenant/05020001_AddSearchTables.cs | 42 ++++ .../HtmlText/Manager/HtmlTextManager.cs | 34 ++- .../Controllers/SearchResultsController.cs | 43 ++++ .../Services/SearchResultsService.cs | 36 +++ .../SearchResults/Startup/ServerStartup.cs | 24 ++ .../Providers/DatabaseSearchProvider.cs | 202 +++++++++++++++++ .../Repository/Context/TenantDBContext.cs | 3 + .../Interfaces/ISearchDocumentRepository.cs | 17 ++ .../Repository/SearchDocumentRepository.cs | 136 +++++++++++ Oqtane.Server/Services/SearchService.cs | 214 ++++++++++++++++++ .../Oqtane.Modules.SearchResults/Module.css | 3 + .../Oqtane.Themes.OqtaneTheme/Theme.css | 4 + Oqtane.Server/wwwroot/css/app.css | 14 ++ Oqtane.Shared/Enums/SearchSortDirections.cs | 10 + Oqtane.Shared/Enums/SearchSortFields.cs | 11 + Oqtane.Shared/Interfaces/IModuleSearch.cs | 14 ++ .../Interfaces/ISearchIndexManager.cs | 20 ++ Oqtane.Shared/Interfaces/ISearchProvider.cs | 26 +++ .../Interfaces/ISearchResultManager.cs | 14 ++ Oqtane.Shared/Interfaces/ISearchService.cs | 15 ++ Oqtane.Shared/Models/SearchDocument.cs | 45 ++++ .../Models/SearchDocumentProperty.cs | 17 ++ Oqtane.Shared/Models/SearchDocumentTag.cs | 14 ++ Oqtane.Shared/Models/SearchQuery.cs | 37 +++ Oqtane.Shared/Models/SearchResult.cs | 11 + Oqtane.Shared/Models/SearchResults.cs | 11 + Oqtane.Shared/Shared/Constants.cs | 16 ++ Oqtane.Shared/Shared/SearchUtils.cs | 95 ++++++++ 50 files changed, 2478 insertions(+), 4 deletions(-) create mode 100644 Oqtane.Client/Modules/SearchResults/Index.razor create mode 100644 Oqtane.Client/Modules/SearchResults/ModuleInfo.cs create mode 100644 Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs create mode 100644 Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs create mode 100644 Oqtane.Client/Modules/SearchResults/Settings.razor create mode 100644 Oqtane.Client/Resources/Modules/SearchResults/Index.resx create mode 100644 Oqtane.Client/Resources/Modules/SearchResults/Settings.resx create mode 100644 Oqtane.Client/Resources/Themes/Controls/Search.resx create mode 100644 Oqtane.Client/Themes/Controls/Theme/Search.razor create mode 100644 Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs create mode 100644 Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs create mode 100644 Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs create mode 100644 Oqtane.Server/Managers/Search/PageSearchIndexManager.cs create mode 100644 Oqtane.Server/Managers/Search/PageSearchResultManager.cs create mode 100644 Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs create mode 100644 Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs create mode 100644 Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs create mode 100644 Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs create mode 100644 Oqtane.Server/Providers/DatabaseSearchProvider.cs create mode 100644 Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs create mode 100644 Oqtane.Server/Repository/SearchDocumentRepository.cs create mode 100644 Oqtane.Server/Services/SearchService.cs create mode 100644 Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css create mode 100644 Oqtane.Shared/Enums/SearchSortDirections.cs create mode 100644 Oqtane.Shared/Enums/SearchSortFields.cs create mode 100644 Oqtane.Shared/Interfaces/IModuleSearch.cs create mode 100644 Oqtane.Shared/Interfaces/ISearchIndexManager.cs create mode 100644 Oqtane.Shared/Interfaces/ISearchProvider.cs create mode 100644 Oqtane.Shared/Interfaces/ISearchResultManager.cs create mode 100644 Oqtane.Shared/Interfaces/ISearchService.cs create mode 100644 Oqtane.Shared/Models/SearchDocument.cs create mode 100644 Oqtane.Shared/Models/SearchDocumentProperty.cs create mode 100644 Oqtane.Shared/Models/SearchDocumentTag.cs create mode 100644 Oqtane.Shared/Models/SearchQuery.cs create mode 100644 Oqtane.Shared/Models/SearchResult.cs create mode 100644 Oqtane.Shared/Models/SearchResults.cs create mode 100644 Oqtane.Shared/Shared/SearchUtils.cs diff --git a/Oqtane.Client/Modules/SearchResults/Index.razor b/Oqtane.Client/Modules/SearchResults/Index.razor new file mode 100644 index 00000000..8ae0c087 --- /dev/null +++ b/Oqtane.Client/Modules/SearchResults/Index.razor @@ -0,0 +1,147 @@ +@using Oqtane.Modules.SearchResults.Services +@namespace Oqtane.Modules.SearchResults +@inherits ModuleBase +@inject ISearchResultsService SearchResultsService +@inject IStringLocalizer Localizer + +
+
+
+
+ @Localizer["SearchPrefix"] + + +
+
+
+
+
+ @if (_loading) + { +
+ } + else + { + @if (_searchResults != null && _searchResults.Results != null) + { + if (_searchResults.Results.Any()) + { + + +
+

@context.Title

+
@context.Url
+

@((MarkupString)context.Snippet)

+
+
+
+ } + else + { + + } + } +
+ } +
+
+
+@code { + private SearchSortDirections _searchSortDirection = SearchSortDirections.Descending; //default sort by + private SearchSortFields _searchSortField = SearchSortFields.Relevance; + private string _keywords; + private bool _loading; + private SearchResults _searchResults; + private int _currentPage = 0; + private int _pageSize = Constants.SearchDefaultPageSize; + private int _displayPages = 7; + + protected override async Task OnInitializedAsync() + { + if (ModuleState.Settings.ContainsKey("PageSize")) + { + _pageSize = int.Parse(ModuleState.Settings["PageSize"]); + } + + if (PageState.QueryString.ContainsKey("s")) + { + _keywords = PageState.QueryString["s"]; + } + + if (PageState.QueryString.ContainsKey("p")) + { + _currentPage = Convert.ToInt32(PageState.QueryString["p"]); + if (_currentPage < 1) + { + _currentPage = 1; + } + } + + if (!string.IsNullOrEmpty(_keywords)) + { + await PerformSearch(); + } + } + + private async Task KeywordsChanged(KeyboardEventArgs e) + { + if (e.Code == "Enter" || e.Code == "NumpadEnter") + { + if (!string.IsNullOrEmpty(_keywords)) + { + await Search(); + } + } + } + + private async Task Search() + { + if (string.IsNullOrEmpty(_keywords)) + { + AddModuleMessage(Localizer["MissingKeywords"], MessageType.Warning); + } + else + { + ClearModuleMessage(); + + + _currentPage = 0; + await PerformSearch(); + } + } + + private async Task PerformSearch() + { + _loading = true; + StateHasChanged(); + + var searchQuery = new SearchQuery + { + SiteId = PageState.Site.SiteId, + Alias = PageState.Alias, + User = PageState.User, + Keywords = _keywords, + SortDirection = _searchSortDirection, + SortField = _searchSortField, + PageIndex = 0, + PageSize = int.MaxValue + }; + + _searchResults = await SearchResultsService.SearchAsync(PageState.ModuleId, searchQuery); + + _loading = false; + StateHasChanged(); + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/SearchResults/ModuleInfo.cs b/Oqtane.Client/Modules/SearchResults/ModuleInfo.cs new file mode 100644 index 00000000..ae4bc7d2 --- /dev/null +++ b/Oqtane.Client/Modules/SearchResults/ModuleInfo.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Oqtane.Documentation; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Modules.SearchResults +{ + [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + public class ModuleInfo : IModule + { + public ModuleDefinition ModuleDefinition => new ModuleDefinition + { + Name = "Search Results", + Description = "Display Search Results", + Version = "1.0.0", + ServerManagerType = "", + ReleaseVersions = "1.0.0", + SettingsType = "Oqtane.Modules.SearchResults.Settings, Oqtane.Client", + Resources = new List() + { + new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } + } + }; + } +} diff --git a/Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs b/Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs new file mode 100644 index 00000000..2f1c1308 --- /dev/null +++ b/Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Documentation; +using Oqtane.Models; + +namespace Oqtane.Modules.SearchResults.Services +{ + [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + public interface ISearchResultsService + { + Task SearchAsync(int moduleId, SearchQuery searchQuery); + } +} diff --git a/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs b/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs new file mode 100644 index 00000000..3b241ea4 --- /dev/null +++ b/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Documentation; +using Oqtane.Models; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Modules.SearchResults.Services +{ + [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + public class SearchResultsService : ServiceBase, ISearchResultsService, IClientService + { + public SearchResultsService(HttpClient http, SiteState siteState) : base(http, siteState) {} + + private string ApiUrl => CreateApiUrl("SearchResults"); + + public async Task SearchAsync(int moduleId, SearchQuery searchQuery) + { + return await PostJsonAsync(CreateAuthorizationPolicyUrl(ApiUrl, EntityNames.Module, moduleId), searchQuery); + } + } +} diff --git a/Oqtane.Client/Modules/SearchResults/Settings.razor b/Oqtane.Client/Modules/SearchResults/Settings.razor new file mode 100644 index 00000000..b38d2946 --- /dev/null +++ b/Oqtane.Client/Modules/SearchResults/Settings.razor @@ -0,0 +1,46 @@ +@namespace Oqtane.Modules.SearchResults +@inherits ModuleBase +@inject ISettingService SettingService +@implements Oqtane.Interfaces.ISettingsControl +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+ +
+ +
+
+
+ +@code { + private string resourceType = "Oqtane.Modules.SearchResults.Settings, Oqtane.Client"; // for localization + private string _pageSize; + + protected override void OnInitialized() + { + try + { + _pageSize = SettingService.GetSetting(ModuleState.Settings, "PageSize", Constants.SearchDefaultPageSize.ToString()); + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } + + public async Task UpdateSettings() + { + try + { + var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); + settings = SettingService.SetSetting(settings, "PageSize", _pageSize); + await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } +} diff --git a/Oqtane.Client/Resources/Modules/SearchResults/Index.resx b/Oqtane.Client/Resources/Modules/SearchResults/Index.resx new file mode 100644 index 00000000..bd07f3f8 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/SearchResults/Index.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ascending + + + Descending + + + Please provide the search keywords. + + + Modification Time + + + No results found. + + + Relevance + + + Search + + + Search + + + Search: + + + Sort Direction + + + Sort By + + + Title + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/SearchResults/Settings.resx b/Oqtane.Client/Resources/Modules/SearchResults/Settings.resx new file mode 100644 index 00000000..1c48707d --- /dev/null +++ b/Oqtane.Client/Resources/Modules/SearchResults/Settings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Page Size + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Themes/Controls/Search.resx b/Oqtane.Client/Resources/Themes/Controls/Search.resx new file mode 100644 index 00000000..c2d2b4ee --- /dev/null +++ b/Oqtane.Client/Resources/Themes/Controls/Search.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Search + + + Search + + \ No newline at end of file diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index 553d97d1..2e0da4d3 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -9,7 +9,9 @@
diff --git a/Oqtane.Client/Themes/Controls/Theme/Search.razor b/Oqtane.Client/Themes/Controls/Theme/Search.razor new file mode 100644 index 00000000..dbd28b40 --- /dev/null +++ b/Oqtane.Client/Themes/Controls/Theme/Search.razor @@ -0,0 +1,57 @@ +@namespace Oqtane.Themes.Controls +@using System.Net +@using Microsoft.AspNetCore.Http +@inherits ThemeControlBase +@inject IStringLocalizer Localizer +@inject NavigationManager NavigationManager +@inject IHttpContextAccessor HttpContext + +@if (_searchResultsPage != null) +{ + +
+ + + +
+
+} + + + +@code { + private const string SearchResultPagePath = "search-results"; + + private Page _searchResultsPage; + private string _keywords = ""; + + [Parameter] + public string CssClass { get; set; } + + protected override void OnInitialized() + { + _searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath); + } + + protected override void OnParametersSet() + { + } + + private void PerformSearch() + { + var keywords = HttpContext.HttpContext.Request.Form["keywords"]; + if (!string.IsNullOrEmpty(keywords) && _searchResultsPage != null) + { + var url = NavigateUrl(_searchResultsPage.Path, $"s={keywords}"); + NavigationManager.NavigateTo(url); + } + } +} + + diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index 261597aa..936c823e 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -6,7 +6,12 @@
diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 19eb4cbe..a6612866 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -21,6 +21,7 @@ using Oqtane.Infrastructure; using Oqtane.Infrastructure.Interfaces; using Oqtane.Managers; using Oqtane.Modules; +using Oqtane.Providers; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Services; @@ -97,6 +98,8 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services; } @@ -131,6 +134,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // managers services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs new file mode 100644 index 00000000..e6b7f216 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Infrastructure +{ + public class SearchIndexJob : HostedServiceBase + { + public SearchIndexJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) + { + Name = "Search Index Job"; + Frequency = "m"; // run every minute. + Interval = 1; + IsEnabled = true; + } + + public override string ExecuteJob(IServiceProvider provider) + { + // get services + var siteRepository = provider.GetRequiredService(); + var settingRepository = provider.GetRequiredService(); + var logRepository = provider.GetRequiredService(); + var searchService = provider.GetRequiredService(); + + var sites = siteRepository.GetSites().ToList(); + var logs = new StringBuilder(); + + foreach (var site in sites) + { + var startTime = GetSearchStartTime(site.SiteId, settingRepository); + logs.AppendLine($"Search: Begin index site: {site.Name}
"); + var currentTime = DateTime.UtcNow; + + searchService.IndexContent(site.SiteId, startTime, logNote => + { + logs.AppendLine(logNote); + }, handleError => + { + logs.AppendLine(handleError); + }); + + UpdateSearchStartTime(site.SiteId, currentTime, settingRepository); + + logs.AppendLine($"Search: End index site: {site.Name}
"); + } + + return logs.ToString(); + } + + private DateTime? GetSearchStartTime(int siteId, ISettingRepository settingRepository) + { + var setting = settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchIndexStartTimeSettingName); + if(setting == null) + { + return null; + } + + return Convert.ToDateTime(setting.SettingValue); + } + + private void UpdateSearchStartTime(int siteId, DateTime startTime, ISettingRepository settingRepository) + { + var setting = settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchIndexStartTimeSettingName); + if (setting == null) + { + setting = new Setting + { + EntityName = EntityNames.Site, + EntityId = siteId, + SettingName = Constants.SearchIndexStartTimeSettingName, + SettingValue = Convert.ToString(startTime), + }; + + settingRepository.AddSetting(setting); + } + else + { + setting.SettingValue = Convert.ToString(startTime); + settingRepository.UpdateSetting(setting); + } + } + } +} diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs index 7cf27808..99bb8aaa 100644 --- a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs +++ b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs @@ -133,6 +133,30 @@ namespace Oqtane.SiteTemplates } } }); + _pageTemplates.Add(new PageTemplate + { + Name = "Search Results", + Parent = "", + Order = 7, + Path = "search-results", + Icon = "oi oi-magnifying-glass", + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List { + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + } + } + } + }); if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"))) { diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs new file mode 100644 index 00000000..fec739d2 --- /dev/null +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Oqtane.Interfaces; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Managers.Search +{ + public class ModuleSearchIndexManager : SearchIndexManagerBase + { + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private readonly IPageModuleRepository _pageModuleRepostory; + private readonly IPageRepository _pageRepository; + + public ModuleSearchIndexManager( + IServiceProvider serviceProvider, + IPageModuleRepository pageModuleRepostory, + ILogger logger, + IPageRepository pageRepository) + : base(serviceProvider) + { + _serviceProvider = serviceProvider; + _logger = logger; + _pageModuleRepostory = pageModuleRepostory; + _pageRepository = pageRepository; + } + + public override string Name => Constants.ModuleSearchIndexManagerName; + + public override int Priority => Constants.ModuleSearchIndexManagerPriority; + + public override int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError) + { + var pageModules = _pageModuleRepostory.GetPageModules(siteId).DistinctBy(i => i.ModuleId); + var searchDocuments = new List(); + + foreach(var pageModule in pageModules) + { + var module = pageModule.Module; + if (module.ModuleDefinition.ServerManagerType != "") + { + _logger.LogDebug($"Search: Begin index module {module.ModuleId}."); + var type = Type.GetType(module.ModuleDefinition.ServerManagerType); + if (type?.GetInterface("IModuleSearch") != null) + { + try + { + var moduleSearch = (IModuleSearch)ActivatorUtilities.CreateInstance(_serviceProvider, type); + var documents = moduleSearch.GetSearchDocuments(module, startTime.GetValueOrDefault(DateTime.MinValue)); + if(documents != null) + { + foreach(var document in documents) + { + SaveModuleMetaData(document, pageModule); + + searchDocuments.Add(document); + } + } + + } + catch(Exception ex) + { + _logger.LogError(ex, $"Search: Index module {module.ModuleId} failed."); + handleError($"Search: Index module {module.ModuleId} failed: {ex.Message}"); + } + } + _logger.LogDebug($"Search: End index module {module.ModuleId}."); + } + } + + processSearchDocuments(searchDocuments); + + return searchDocuments.Count; + } + + private void SaveModuleMetaData(SearchDocument document, PageModule pageModule) + { + + document.EntryId = pageModule.ModuleId; + document.IndexerName = Name; + document.SiteId = pageModule.Module.SiteId; + document.LanguageCode = string.Empty; + + if(document.ModifiedTime == DateTime.MinValue) + { + document.ModifiedTime = pageModule.ModifiedOn; + } + + if (string.IsNullOrEmpty(document.AdditionalContent)) + { + document.AdditionalContent = string.Empty; + } + + var page = _pageRepository.GetPage(pageModule.PageId); + + if (string.IsNullOrEmpty(document.Url) && page != null) + { + document.Url = page.Url; + } + + if (string.IsNullOrEmpty(document.Title) && page != null) + { + document.Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name; + } + + if (document.Properties == null) + { + document.Properties = new List(); + } + + if(!document.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + { + document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() }); + } + + if (!document.Properties.Any(i => i.Name == Constants.SearchModuleIdPropertyName)) + { + document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() }); + } + } + } +} diff --git a/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs new file mode 100644 index 00000000..55789c23 --- /dev/null +++ b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Security; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Managers.Search +{ + public class ModuleSearchResultManager : ISearchResultManager + { + public string Name => Constants.ModuleSearchIndexManagerName; + + private readonly IServiceProvider _serviceProvider; + + public ModuleSearchResultManager( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public string GetUrl(SearchResult searchResult, SearchQuery searchQuery) + { + var pageRepository = _serviceProvider.GetRequiredService(); + var pageIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + if(!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId)) + { + var page = pageRepository.GetPage(pageId); + if (page != null) + { + return $"{searchQuery.Alias.Protocol}{searchQuery.Alias.Name}{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}"; + } + } + + return string.Empty; + } + + public bool Visible(SearchDocument searchResult, SearchQuery searchQuery) + { + var pageIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + var moduleIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchModuleIdPropertyName)?.Value ?? string.Empty; + if (!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId) + && !string.IsNullOrEmpty(moduleIdValue) && int.TryParse(moduleIdValue, out int moduleId)) + { + return CanViewPage(pageId, searchQuery.User) && CanViewModule(moduleId, searchQuery.User); + } + + return false; + } + + private bool CanViewModule(int moduleId, User user) + { + var moduleRepository = _serviceProvider.GetRequiredService(); + var module = moduleRepository.GetModule(moduleId); + return module != null && !module.IsDeleted && UserSecurity.IsAuthorized(user, PermissionNames.View, module.PermissionList); + } + + private bool CanViewPage(int pageId, User user) + { + var pageRepository = _serviceProvider.GetRequiredService(); + var page = pageRepository.GetPage(pageId); + + return page != null && !page.IsDeleted && UserSecurity.IsAuthorized(user, PermissionNames.View, page.PermissionList) + && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList)); + } + } +} diff --git a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs new file mode 100644 index 00000000..68c4af02 --- /dev/null +++ b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Reflection.Metadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Oqtane.Interfaces; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Managers.Search +{ + public class PageSearchIndexManager : SearchIndexManagerBase + { + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private readonly IPageRepository _pageRepository; + + public PageSearchIndexManager( + IServiceProvider serviceProvider, + ILogger logger, + IPageRepository pageRepository) + : base(serviceProvider) + { + _serviceProvider = serviceProvider; + _logger = logger; + _pageRepository = pageRepository; + } + + public override string Name => Constants.PageSearchIndexManagerName; + + public override int Priority => Constants.PageSearchIndexManagerPriority; + + public override int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError) + { + var startTimeValue = startTime.GetValueOrDefault(DateTime.MinValue); + var pages = _pageRepository.GetPages(siteId).Where(i => i.ModifiedOn >= startTimeValue); + var searchDocuments = new List(); + + foreach(var page in pages) + { + try + { + if(IsSystemPage(page)) + { + continue; + } + + var document = new SearchDocument + { + EntryId = page.PageId, + IndexerName = Name, + SiteId = page.SiteId, + LanguageCode = string.Empty, + ModifiedTime = page.ModifiedOn, + AdditionalContent = string.Empty, + Url = page.Url ?? string.Empty, + Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name, + Description = string.Empty, + Body = $"{page.Name} {page.Title}" + }; + + if (document.Properties == null) + { + document.Properties = new List(); + } + + if (!document.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + { + document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() }); + } + + searchDocuments.Add(document); + } + catch(Exception ex) + { + _logger.LogError(ex, $"Search: Index page {page.PageId} failed."); + handleError($"Search: Index page {page.PageId} failed: {ex.Message}"); + } + } + + processSearchDocuments(searchDocuments); + + return searchDocuments.Count; + } + + private bool IsSystemPage(Models.Page page) + { + return page.Path.Contains("admin") || page.Path == "login" || page.Path == "register" || page.Path == "profile"; + } + } +} diff --git a/Oqtane.Server/Managers/Search/PageSearchResultManager.cs b/Oqtane.Server/Managers/Search/PageSearchResultManager.cs new file mode 100644 index 00000000..fd5456a4 --- /dev/null +++ b/Oqtane.Server/Managers/Search/PageSearchResultManager.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Security; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Managers.Search +{ + public class PageSearchResultManager : ISearchResultManager + { + public string Name => Constants.PageSearchIndexManagerName; + + private readonly IServiceProvider _serviceProvider; + + public PageSearchResultManager( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public string GetUrl(SearchResult searchResult, SearchQuery searchQuery) + { + var pageRepository = _serviceProvider.GetRequiredService(); + var page = pageRepository.GetPage(searchResult.EntryId); + if (page != null) + { + return $"{searchQuery.Alias.Protocol}{searchQuery.Alias.Name}{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}"; + } + + return string.Empty; + } + + public bool Visible(SearchDocument searchResult, SearchQuery searchQuery) + { + var pageRepository = _serviceProvider.GetRequiredService(); + var page = pageRepository.GetPage(searchResult.EntryId); + + return page != null && !page.IsDeleted + && UserSecurity.IsAuthorized(searchQuery.User, PermissionNames.View, page.PermissionList) + && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(searchQuery.User, PermissionNames.Edit, page.PermissionList)); + } + } +} diff --git a/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs new file mode 100644 index 00000000..0ecb2880 --- /dev/null +++ b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Services; +using Oqtane.Shared; + +namespace Oqtane.Managers.Search +{ + public abstract class SearchIndexManagerBase : ISearchIndexManager + { + private readonly IServiceProvider _serviceProvider; + + public SearchIndexManagerBase(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public abstract int Priority { get; } + + public abstract string Name { get; } + + public abstract int IndexDocuments(int siteId, DateTime? startDate, Action> processSearchDocuments, Action handleError); + + public virtual bool IsIndexEnabled(int siteId) + { + var settingName = string.Format(Constants.SearchIndexManagerEnabledSettingFormat, Name); + var settingRepository = _serviceProvider.GetRequiredService(); + var setting = settingRepository.GetSetting(EntityNames.Site, siteId, settingName); + return setting == null || setting.SettingValue == "true"; + } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs new file mode 100644 index 00000000..2f5b6d8d --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchDocumentEntityBuilder : AuditableBaseEntityBuilder + { + private const string _entityTableName = "SearchDocument"; + private readonly PrimaryKey _primaryKey = new("PK_SearchDocument", x => x.SearchDocumentId); + + public SearchDocumentEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override SearchDocumentEntityBuilder BuildTable(ColumnsBuilder table) + { + SearchDocumentId = AddAutoIncrementColumn(table, "SearchDocumentId"); + EntryId = AddIntegerColumn(table, "EntryId"); + IndexerName = AddStringColumn(table, "IndexerName", 50); + SiteId = AddIntegerColumn(table, "SiteId"); + Title = AddStringColumn(table, "Title", 255); + Description = AddMaxStringColumn(table, "Description"); + Body = AddMaxStringColumn(table, "Body"); + Url = AddStringColumn(table, "Url", 255); + ModifiedTime = AddDateTimeColumn(table, "ModifiedTime"); + IsActive = AddBooleanColumn(table, "IsActive"); + AdditionalContent = AddMaxStringColumn(table, "AdditionalContent"); + LanguageCode = AddStringColumn(table, "LanguageCode", 20); + + AddAuditableColumns(table); + + return this; + } + + public OperationBuilder SearchDocumentId { get; private set; } + + public OperationBuilder EntryId { get; private set; } + + public OperationBuilder IndexerName { get; private set; } + + public OperationBuilder SiteId { get; private set; } + + public OperationBuilder Title { get; private set; } + + public OperationBuilder Description { get; private set; } + + public OperationBuilder Body { get; private set; } + + public OperationBuilder Url { get; private set; } + + public OperationBuilder ModifiedTime { get; private set; } + + public OperationBuilder IsActive { get; private set; } + + public OperationBuilder AdditionalContent { get; private set; } + + public OperationBuilder LanguageCode { get; private set; } + + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs new file mode 100644 index 00000000..a591aec2 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchDocumentPropertyEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchDocumentProperty"; + private readonly PrimaryKey _primaryKey = new("PK_SearchDocumentProperty", x => x.PropertyId); + private readonly ForeignKey _searchDocumentForeignKey = new("FK_SearchDocumentProperty_SearchDocument", x => x.SearchDocumentId, "SearchDocument", "SearchDocumentId", ReferentialAction.Cascade); + + public SearchDocumentPropertyEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + + ForeignKeys.Add(_searchDocumentForeignKey); + } + + protected override SearchDocumentPropertyEntityBuilder BuildTable(ColumnsBuilder table) + { + PropertyId = AddAutoIncrementColumn(table, "PropertyId"); + SearchDocumentId = AddIntegerColumn(table, "SearchDocumentId"); + Name = AddStringColumn(table, "Name", 50); + Value = AddStringColumn(table, "Value", 50); + + return this; + } + + public OperationBuilder PropertyId { get; private set; } + + public OperationBuilder SearchDocumentId { get; private set; } + + public OperationBuilder Name { get; private set; } + + public OperationBuilder Value { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs new file mode 100644 index 00000000..bd864f22 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchDocumentTagEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchDocumentTag"; + private readonly PrimaryKey _primaryKey = new("PK_SearchDocumentTag", x => x.TagId); + private readonly ForeignKey _searchDocumentForeignKey = new("FK_SearchDocumentTag_SearchDocument", x => x.SearchDocumentId, "SearchDocument", "SearchDocumentId", ReferentialAction.Cascade); + + public SearchDocumentTagEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + + ForeignKeys.Add(_searchDocumentForeignKey); + } + + protected override SearchDocumentTagEntityBuilder BuildTable(ColumnsBuilder table) + { + TagId = AddAutoIncrementColumn(table, "TagId"); + SearchDocumentId = AddIntegerColumn(table, "SearchDocumentId"); + Tag = AddStringColumn(table, "Tag", 50); + + return this; + } + + public OperationBuilder TagId { get; private set; } + + public OperationBuilder SearchDocumentId { get; private set; } + + public OperationBuilder Tag { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs new file mode 100644 index 00000000..f32a8137 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs @@ -0,0 +1,42 @@ +using System; +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.05.02.00.01")] + public class AddSearchTables : MultiDatabaseMigration + { + public AddSearchTables(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var searchDocumentEntityBuilder = new SearchDocumentEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentEntityBuilder.Create(); + + var searchDocumentPropertyEntityBuilder = new SearchDocumentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentPropertyEntityBuilder.Create(); + + var searchDocumentTagEntityBuilder = new SearchDocumentTagEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentTagEntityBuilder.Create(); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var searchDocumentPropertyEntityBuilder = new SearchDocumentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentPropertyEntityBuilder.Drop(); + + var searchDocumentTagEntityBuilder = new SearchDocumentTagEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentTagEntityBuilder.Drop(); + + var searchDocumentEntityBuilder = new SearchDocumentEntityBuilder(migrationBuilder, ActiveDatabase); + searchDocumentEntityBuilder.Drop(); + } + } +} diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 6c9293fb..8cabf391 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -8,20 +8,29 @@ using Oqtane.Shared; using Oqtane.Migrations.Framework; using Oqtane.Documentation; using System.Linq; +using Oqtane.Interfaces; +using System.Collections.Generic; +using System; // ReSharper disable ConvertToUsingDeclaration namespace Oqtane.Modules.HtmlText.Manager { [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")] - public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable + public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, IModuleSearch { + private readonly IServiceProvider _serviceProvider; private readonly IHtmlTextRepository _htmlText; private readonly IDBContextDependencies _DBContextDependencies; private readonly ISqlRepository _sqlRepository; - public HtmlTextManager(IHtmlTextRepository htmlText, IDBContextDependencies DBContextDependencies, ISqlRepository sqlRepository) + public HtmlTextManager( + IServiceProvider serviceProvider, + IHtmlTextRepository htmlText, + IDBContextDependencies DBContextDependencies, + ISqlRepository sqlRepository) { + _serviceProvider = serviceProvider; _htmlText = htmlText; _DBContextDependencies = DBContextDependencies; _sqlRepository = sqlRepository; @@ -39,6 +48,27 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } + public IList GetSearchDocuments(Module module, DateTime startDate) + { + var searchDocuments = new List(); + + var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId); + if (htmltexts != null && htmltexts.Any(i => i.CreatedOn >= startDate)) + { + var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First(); + + searchDocuments.Add(new SearchDocument + { + Title = module.Title, + Description = string.Empty, + Body = SearchUtils.Clean(htmltext.Content, true), + ModifiedTime = htmltext.ModifiedOn + }); + } + + return searchDocuments; + } + public void ImportModule(Module module, string content, string version) { content = WebUtility.HtmlDecode(content); diff --git a/Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs b/Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs new file mode 100644 index 00000000..d134b1ae --- /dev/null +++ b/Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs @@ -0,0 +1,43 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Oqtane.Controllers; +using Oqtane.Documentation; +using Oqtane.Enums; +using Oqtane.Infrastructure; +using Oqtane.Modules.SearchResults.Services; +using Oqtane.Shared; + +namespace Oqtane.Modules.SearchResults.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + public class SearchResultsController : ModuleControllerBase + { + private readonly ISearchResultsService _searchResultsService; + + public SearchResultsController(ISearchResultsService searchResultsService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor) + { + _searchResultsService = searchResultsService; + } + + [HttpPost] + [Authorize(Policy = PolicyNames.ViewModule)] + public async Task Post([FromBody] Models.SearchQuery searchQuery) + { + try + { + return await _searchResultsService.SearchAsync(AuthEntityId(EntityNames.Module), searchQuery); + } + catch(Exception ex) + { + _logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Fetch search results failed.", searchQuery); + HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return null; + } + } + } +} diff --git a/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs b/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs new file mode 100644 index 00000000..3823bac6 --- /dev/null +++ b/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Oqtane.Documentation; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Services; + +namespace Oqtane.Modules.SearchResults.Services +{ + [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + public class ServerSearchResultsService : ISearchResultsService, ITransientService + { + private readonly ILogManager _logger; + private readonly IHttpContextAccessor _accessor; + private readonly Alias _alias; + private readonly ISearchService _searchService; + + public ServerSearchResultsService( + ITenantManager tenantManager, + ILogManager logger, + IHttpContextAccessor accessor, + ISearchService searchService) + { + _logger = logger; + _accessor = accessor; + _alias = tenantManager.GetAlias(); + _searchService = searchService; + } + + public async Task SearchAsync(int moduleId, SearchQuery searchQuery) + { + var results = await _searchService.SearchAsync(searchQuery); + return results; + } + } +} diff --git a/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs b/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs new file mode 100644 index 00000000..25d2d15f --- /dev/null +++ b/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Oqtane.Infrastructure; +using Oqtane.Modules.SearchResults.Services; + +namespace Oqtane.Modules.SearchResults.Startup +{ + public class ServerStartup : IServerStartup + { + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + } + + public void ConfigureMvc(IMvcBuilder mvcBuilder) + { + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + } + } +} diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs new file mode 100644 index 00000000..ac2ed576 --- /dev/null +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Services; +using Oqtane.Shared; +using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource; + +namespace Oqtane.Providers +{ + public class DatabaseSearchProvider : ISearchProvider + { + private readonly ISearchDocumentRepository _searchDocumentRepository; + + private const float TitleBoost = 100f; + private const float DescriptionBoost = 10f; + private const float BodyBoost = 10f; + private const float AdditionalContentBoost = 5f; + + public string Name => Constants.DefaultSearchProviderName; + + public DatabaseSearchProvider(ISearchDocumentRepository searchDocumentRepository) + { + _searchDocumentRepository = searchDocumentRepository; + } + + public void Commit() + { + } + + public void DeleteDocument(string id) + { + _searchDocumentRepository.DeleteSearchDocument(id); + } + + public bool Optimize() + { + return true; + } + + public void ResetIndex() + { + _searchDocumentRepository.DeleteAllSearchDocuments(); + } + + public void SaveDocument(SearchDocument document, bool autoCommit = false) + { + //remove exist document + _searchDocumentRepository.DeleteSearchDocument(document.IndexerName, document.EntryId); + + _searchDocumentRepository.AddSearchDocument(document); + } + + public async Task SearchAsync(SearchQuery searchQuery, Func validateFunc) + { + var totalResults = 0; + + var documents = await _searchDocumentRepository.GetSearchDocumentsAsync(searchQuery); + + //convert the search documents to search results. + var results = documents + .Where(i => validateFunc(i, searchQuery)) + .Select(i => ConvertToSearchResult(i, searchQuery)); + + if (searchQuery.SortDirection == SearchSortDirections.Descending) + { + switch (searchQuery.SortField) + { + case SearchSortFields.Relevance: + results = results.OrderByDescending(i => i.Score).ThenByDescending(i => i.ModifiedTime); + break; + case SearchSortFields.Title: + results = results.OrderByDescending(i => i.Title).ThenByDescending(i => i.ModifiedTime); + break; + default: + results = results.OrderByDescending(i => i.ModifiedTime); + break; + } + } + else + { + switch (searchQuery.SortField) + { + case SearchSortFields.Relevance: + results = results.OrderBy(i => i.Score).ThenByDescending(i => i.ModifiedTime); + break; + case SearchSortFields.Title: + results = results.OrderBy(i => i.Title).ThenByDescending(i => i.ModifiedTime); + break; + default: + results = results.OrderBy(i => i.ModifiedTime); + break; + } + } + + //remove duplicated results based on page id for Page and Module types + results = results.DistinctBy(i => + { + if (i.IndexerName == Constants.PageSearchIndexManagerName || i.IndexerName == Constants.ModuleSearchIndexManagerName) + { + var pageId = i.Properties.FirstOrDefault(p => p.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + return !string.IsNullOrEmpty(pageId) ? pageId : i.UniqueKey; + } + else + { + return i.UniqueKey; + } + }); + + totalResults = results.Count(); + + return new SearchResults + { + Results = results.Skip(searchQuery.PageIndex * searchQuery.PageSize).Take(searchQuery.PageSize).ToList(), + TotalResults = totalResults + }; + } + + private SearchResult ConvertToSearchResult(SearchDocument searchDocument, SearchQuery searchQuery) + { + var searchResult = new SearchResult() + { + SearchDocumentId = searchDocument.SearchDocumentId, + SiteId = searchDocument.SiteId, + IndexerName = searchDocument.IndexerName, + EntryId = searchDocument.EntryId, + Title = searchDocument.Title, + Description = searchDocument.Description, + Body = searchDocument.Body, + Url = searchDocument.Url, + ModifiedTime = searchDocument.ModifiedTime, + Tags = searchDocument.Tags, + Properties = searchDocument.Properties, + Snippet = BuildSnippet(searchDocument, searchQuery), + Score = CalculateScore(searchDocument, searchQuery) + }; + + return searchResult; + } + + private float CalculateScore(SearchDocument searchDocument, SearchQuery searchQuery) + { + var score = 0f; + foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + { + score += Regex.Matches(searchDocument.Title, keyword, RegexOptions.IgnoreCase).Count * TitleBoost; + score += Regex.Matches(searchDocument.Description, keyword, RegexOptions.IgnoreCase).Count * DescriptionBoost; + score += Regex.Matches(searchDocument.Body, keyword, RegexOptions.IgnoreCase).Count * BodyBoost; + score += Regex.Matches(searchDocument.AdditionalContent, keyword, RegexOptions.IgnoreCase).Count * AdditionalContentBoost; + } + + return score / 100; + } + + private string BuildSnippet(SearchDocument searchDocument, SearchQuery searchQuery) + { + var content = $"{searchDocument.Title} {searchDocument.Description} {searchDocument.Body}"; + var snippet = string.Empty; + foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + { + if (!string.IsNullOrWhiteSpace(keyword) && content.Contains(keyword, StringComparison.OrdinalIgnoreCase)) + { + var start = content.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) - 20; + var prefix = "..."; + var suffix = "..."; + if (start <= 0) + { + start = 0; + prefix = string.Empty; + } + + var length = searchQuery.BodySnippetLength; + if (start + length >= content.Length) + { + length = content.Length - start; + suffix = string.Empty; + } + + snippet = $"{prefix}{content.Substring(start, length)}{suffix}"; + break; + + + } + } + + if (string.IsNullOrEmpty(snippet)) + { + snippet = content.Substring(0, searchQuery.BodySnippetLength); + } + + foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + { + snippet = Regex.Replace(snippet, $"({keyword})", $"$1", RegexOptions.IgnoreCase); + } + + return snippet; + } + } +} diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index 2efad3f7..cbe57631 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -29,5 +29,8 @@ namespace Oqtane.Repository public virtual DbSet Language { get; set; } public virtual DbSet Visitor { get; set; } public virtual DbSet UrlMapping { get; set; } + public virtual DbSet SearchDocument { get; set; } + public virtual DbSet SearchDocumentProperty { get; set; } + public virtual DbSet SearchDocumentTag { get; set; } } } diff --git a/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs b/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs new file mode 100644 index 00000000..6128c40c --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface ISearchDocumentRepository + { + Task> GetSearchDocumentsAsync(SearchQuery searchQuery); + SearchDocument AddSearchDocument(SearchDocument searchDocument); + void DeleteSearchDocument(int searchDocumentId); + void DeleteSearchDocument(string indexerName, int entryId); + void DeleteSearchDocument(string uniqueKey); + void DeleteAllSearchDocuments(); + } +} diff --git a/Oqtane.Server/Repository/SearchDocumentRepository.cs b/Oqtane.Server/Repository/SearchDocumentRepository.cs new file mode 100644 index 00000000..bdceb52e --- /dev/null +++ b/Oqtane.Server/Repository/SearchDocumentRepository.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Repository +{ + public class SearchDocumentRepository : ISearchDocumentRepository + { + private readonly IDbContextFactory _dbContextFactory; + + public SearchDocumentRepository(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public async Task> GetSearchDocumentsAsync(SearchQuery searchQuery) + { + using var db = _dbContextFactory.CreateDbContext(); + var documents = db.SearchDocument.AsNoTracking() + .Include(i => i.Properties) + .Include(i => i.Tags) + .Where(i => i.SiteId == searchQuery.SiteId); + + if (searchQuery.Sources != null && searchQuery.Sources.Any()) + { + documents = documents.Where(i => searchQuery.Sources.Contains(i.IndexerName)); + } + + if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) + { + documents = documents.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); + } + + if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) + { + documents = documents.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); + } + + if (searchQuery.Tags != null && searchQuery.Tags.Any()) + { + foreach (var tag in searchQuery.Tags) + { + documents = documents.Where(i => i.Tags.Any(t => t.Tag == tag)); + } + } + + if (searchQuery.Properties != null && searchQuery.Properties.Any()) + { + foreach (var property in searchQuery.Properties) + { + documents = documents.Where(i => i.Properties.Any(p => p.Name == property.Key && p.Value == property.Value)); + } + } + + var filteredDocuments = new List(); + if (!string.IsNullOrEmpty(searchQuery.Keywords)) + { + foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + { + filteredDocuments.AddRange(await documents.Where(i => i.Title.Contains(keyword) || i.Description.Contains(keyword) || i.Body.Contains(keyword)).ToListAsync()); + } + } + + return filteredDocuments.DistinctBy(i => i.UniqueKey); + } + + public SearchDocument AddSearchDocument(SearchDocument searchDocument) + { + using var context = _dbContextFactory.CreateDbContext(); + context.SearchDocument.Add(searchDocument); + + if(searchDocument.Properties != null && searchDocument.Properties.Any()) + { + foreach(var property in searchDocument.Properties) + { + property.SearchDocumentId = searchDocument.SearchDocumentId; + context.SearchDocumentProperty.Add(property); + } + } + + if (searchDocument.Tags != null && searchDocument.Tags.Any()) + { + foreach (var tag in searchDocument.Tags) + { + tag.SearchDocumentId = searchDocument.SearchDocumentId; + context.SearchDocumentTag.Add(tag); + } + } + + context.SaveChanges(); + + return searchDocument; + } + + public void DeleteSearchDocument(int searchDocumentId) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchDocument = db.SearchDocument.Find(searchDocumentId); + db.SearchDocument.Remove(searchDocument); + db.SaveChanges(); + } + + public void DeleteSearchDocument(string indexerName, int entryId) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchDocument = db.SearchDocument.FirstOrDefault(i => i.IndexerName == indexerName && i.EntryId == entryId); + if(searchDocument != null) + { + db.SearchDocument.Remove(searchDocument); + db.SaveChanges(); + } + } + + public void DeleteSearchDocument(string uniqueKey) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchDocument = db.SearchDocument.FirstOrDefault(i => (i.IndexerName + ":" + i.EntryId) == uniqueKey); + if (searchDocument != null) + { + db.SearchDocument.Remove(searchDocument); + db.SaveChanges(); + } + } + + public void DeleteAllSearchDocuments() + { + using var db = _dbContextFactory.CreateDbContext(); + db.SearchDocument.RemoveRange(db.SearchDocument); + db.SaveChanges(); + } + } +} diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs new file mode 100644 index 00000000..caa9fce6 --- /dev/null +++ b/Oqtane.Server/Services/SearchService.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Oqtane.Infrastructure; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + public class SearchService : ISearchService + { + private readonly IServiceProvider _serviceProvider; + private readonly ITenantManager _tenantManager; + private readonly IAliasRepository _aliasRepository; + private readonly ISettingRepository _settingRepository; + private readonly ILogger _logger; + private readonly IMemoryCache _cache; + + public SearchService( + IServiceProvider serviceProvider, + ITenantManager tenantManager, + IAliasRepository aliasRepository, + ISettingRepository settingRepository, + ILogger logger, + IMemoryCache cache) + { + _tenantManager = tenantManager; + _aliasRepository = aliasRepository; + _settingRepository = settingRepository; + _serviceProvider = serviceProvider; + _logger = logger; + _cache = cache; + } + + public void IndexContent(int siteId, DateTime? startTime, Action logNote, Action handleError) + { + var searchEnabled = SearchEnabled(siteId); + if(!searchEnabled) + { + logNote($"Search: Search is disabled on site {siteId}.
"); + return; + } + + _logger.LogDebug($"Search: Start Index Content of {siteId}, Start Time: {startTime.GetValueOrDefault(DateTime.MinValue)}"); + + var searchProvider = GetSearchProvider(siteId); + + SetTenant(siteId); + + if (startTime == null) + { + searchProvider.ResetIndex(); + } + + var searchIndexManagers = GetSearchIndexManagers(m => { }); + foreach (var searchIndexManager in searchIndexManagers) + { + if (!searchIndexManager.IsIndexEnabled(siteId)) + { + logNote($"Search: Ignore indexer {searchIndexManager.Name} because it's disabled.
"); + } + else + { + _logger.LogDebug($"Search: Begin Index {searchIndexManager.Name}"); + + var count = searchIndexManager.IndexDocuments(siteId, startTime, SaveIndexDocuments, handleError); + logNote($"Search: Indexer {searchIndexManager.Name} processed {count} documents.
"); + + _logger.LogDebug($"Search: End Index {searchIndexManager.Name}"); + } + } + } + + public async Task SearchAsync(SearchQuery searchQuery) + { + var searchProvider = GetSearchProvider(searchQuery.SiteId); + var searchResults = await searchProvider.SearchAsync(searchQuery, HasViewPermission); + + //generate the document url if it's not set. + foreach (var result in searchResults.Results) + { + if(string.IsNullOrEmpty(result.Url)) + { + result.Url = GetDocumentUrl(result, searchQuery); + } + } + + return searchResults; + } + + private ISearchProvider GetSearchProvider(int siteId) + { + var providerName = GetSearchProviderSetting(siteId); + var searchProviders = _serviceProvider.GetServices(); + var provider = searchProviders.FirstOrDefault(i => i.Name == providerName); + if(provider == null) + { + provider = searchProviders.FirstOrDefault(i => i.Name == Constants.DefaultSearchProviderName); + } + + return provider; + } + + private string GetSearchProviderSetting(int siteId) + { + var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchProviderSettingName); + if(!string.IsNullOrEmpty(setting?.SettingValue)) + { + return setting.SettingValue; + } + + return Constants.DefaultSearchProviderName; + } + + private bool SearchEnabled(int siteId) + { + var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchEnabledSettingName); + if (!string.IsNullOrEmpty(setting?.SettingValue)) + { + return bool.TryParse(setting.SettingValue, out bool enabled) && enabled; + } + + return true; + } + + private void SetTenant(int siteId) + { + var alias = _aliasRepository.GetAliases().OrderBy(i => i.SiteId).ThenByDescending(i => i.IsDefault).FirstOrDefault(i => i.SiteId == siteId); + _tenantManager.SetAlias(alias); + } + + private IList GetSearchIndexManagers(Action initManager) + { + var managers = new List(); + var managerTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => typeof(ISearchIndexManager).IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract); + + foreach (var type in managerTypes) + { + var manager = (ISearchIndexManager)ActivatorUtilities.CreateInstance(_serviceProvider, type); + initManager(manager); + managers.Add(manager); + } + + return managers.OrderBy(i => i.Priority).ToList(); + } + + private IList GetSearchResultManagers() + { + var managers = new List(); + var managerTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => typeof(ISearchResultManager).IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract); + + foreach (var type in managerTypes) + { + var manager = (ISearchResultManager)ActivatorUtilities.CreateInstance(_serviceProvider, type); + managers.Add(manager); + } + + return managers.ToList(); + } + + private void SaveIndexDocuments(IList searchDocuments) + { + if(searchDocuments.Any()) + { + var searchProvider = GetSearchProvider(searchDocuments.First().SiteId); + + foreach (var searchDocument in searchDocuments) + { + try + { + searchProvider.SaveDocument(searchDocument); + } + catch(Exception ex) + { + _logger.LogError(ex, $"Search: Save search document {searchDocument.UniqueKey} failed."); + } + } + + //commit the index changes + searchProvider.Commit(); + } + } + + private bool HasViewPermission(SearchDocument searchDocument, SearchQuery searchQuery) + { + var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == searchDocument.IndexerName); + if (searchResultManager != null) + { + return searchResultManager.Visible(searchDocument, searchQuery); + } + return true; + } + + private string GetDocumentUrl(SearchResult result, SearchQuery searchQuery) + { + var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == result.IndexerName); + if(searchResultManager != null) + { + return searchResultManager.GetUrl(result, searchQuery); + } + + return string.Empty; + } + } +} diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css new file mode 100644 index 00000000..71194e38 --- /dev/null +++ b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css @@ -0,0 +1,3 @@ +.search-result-container ul.pagination li label, .search-result-container ul.dropdown-menu li label { + cursor: pointer; +} \ No newline at end of file diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css index 1b9ea409..bbb65908 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css @@ -79,6 +79,10 @@ body { top: -2px; } +.app-search input{ + width: auto; +} + .navbar-toggler { background-color: rgba(255, 255, 255, 0.1); margin: .5rem; diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index f5a65a80..941ff5f1 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -235,3 +235,17 @@ app { .app-form-inline { display: inline-block; } +.app-search{ + display: inline-block; + position: relative; +} +.app-search input + button{ + background: none; + border: none; + position: absolute; + right: 0; + top: 0; +} +.app-search input + button .oi{ + top: 0; +} \ No newline at end of file diff --git a/Oqtane.Shared/Enums/SearchSortDirections.cs b/Oqtane.Shared/Enums/SearchSortDirections.cs new file mode 100644 index 00000000..62c7f08d --- /dev/null +++ b/Oqtane.Shared/Enums/SearchSortDirections.cs @@ -0,0 +1,10 @@ +using System; + +namespace Oqtane.Shared +{ + public enum SearchSortDirections + { + Ascending, + Descending + } +} diff --git a/Oqtane.Shared/Enums/SearchSortFields.cs b/Oqtane.Shared/Enums/SearchSortFields.cs new file mode 100644 index 00000000..c8e16175 --- /dev/null +++ b/Oqtane.Shared/Enums/SearchSortFields.cs @@ -0,0 +1,11 @@ +using System; + +namespace Oqtane.Shared +{ + public enum SearchSortFields + { + Relevance, + Title, + LastModified + } +} diff --git a/Oqtane.Shared/Interfaces/IModuleSearch.cs b/Oqtane.Shared/Interfaces/IModuleSearch.cs new file mode 100644 index 00000000..af94a300 --- /dev/null +++ b/Oqtane.Shared/Interfaces/IModuleSearch.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Interfaces +{ + public interface IModuleSearch + { + public IList GetSearchDocuments(Module module, DateTime startTime); + } +} diff --git a/Oqtane.Shared/Interfaces/ISearchIndexManager.cs b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs new file mode 100644 index 00000000..9a322ab5 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Oqtane.Models; + +namespace Oqtane.Services +{ + public interface ISearchIndexManager + { + int Priority { get; } + + string Name { get; } + + bool IsIndexEnabled(int siteId); + + int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError); + } +} diff --git a/Oqtane.Shared/Interfaces/ISearchProvider.cs b/Oqtane.Shared/Interfaces/ISearchProvider.cs new file mode 100644 index 00000000..e1e9fb16 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ISearchProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Services +{ + public interface ISearchProvider + { + string Name { get; } + + void SaveDocument(SearchDocument document, bool autoCommit = false); + + void DeleteDocument(string id); + + Task SearchAsync(SearchQuery searchQuery, Func validateFunc); + + bool Optimize(); + + void Commit(); + + void ResetIndex(); + } +} diff --git a/Oqtane.Shared/Interfaces/ISearchResultManager.cs b/Oqtane.Shared/Interfaces/ISearchResultManager.cs new file mode 100644 index 00000000..2dc459b0 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ISearchResultManager.cs @@ -0,0 +1,14 @@ +using System; +using Oqtane.Models; + +namespace Oqtane.Services +{ + public interface ISearchResultManager + { + string Name { get; } + + bool Visible(SearchDocument searchResult, SearchQuery searchQuery); + + string GetUrl(SearchResult searchResult, SearchQuery searchQuery); + } +} diff --git a/Oqtane.Shared/Interfaces/ISearchService.cs b/Oqtane.Shared/Interfaces/ISearchService.cs new file mode 100644 index 00000000..0f125c65 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ISearchService.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Services +{ + public interface ISearchService + { + void IndexContent(int siteId, DateTime? startTime, Action logNote, Action handleError); + + Task SearchAsync(SearchQuery searchQuery); + } +} diff --git a/Oqtane.Shared/Models/SearchDocument.cs b/Oqtane.Shared/Models/SearchDocument.cs new file mode 100644 index 00000000..67161b3b --- /dev/null +++ b/Oqtane.Shared/Models/SearchDocument.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; +namespace Oqtane.Models +{ + public class SearchDocument : ModelBase + { + public int SearchDocumentId { get; set; } + + [NotMapped] + public string UniqueKey => $"{IndexerName}:{EntryId}"; + + public int EntryId { get; set; } + + public string IndexerName { get; set; } + + public int SiteId { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + public string Body { get; set; } + + public string Url { get; set; } + + public DateTime ModifiedTime { get; set; } + + public bool IsActive { get; set; } + + public string AdditionalContent { get; set; } + + public string LanguageCode { get; set; } + + public IList Tags { get; set; } + + public IList Properties { get; set; } + + public override string ToString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/Oqtane.Shared/Models/SearchDocumentProperty.cs b/Oqtane.Shared/Models/SearchDocumentProperty.cs new file mode 100644 index 00000000..4febae43 --- /dev/null +++ b/Oqtane.Shared/Models/SearchDocumentProperty.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; + +namespace Oqtane.Models +{ + public class SearchDocumentProperty + { + [Key] + public int PropertyId { get; set; } + + public int SearchDocumentId { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchDocumentTag.cs b/Oqtane.Shared/Models/SearchDocumentTag.cs new file mode 100644 index 00000000..ee929efc --- /dev/null +++ b/Oqtane.Shared/Models/SearchDocumentTag.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Oqtane.Models +{ + public class SearchDocumentTag + { + [Key] + public int TagId { get; set; } + + public int SearchDocumentId { get; set; } + + public string Tag { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchQuery.cs b/Oqtane.Shared/Models/SearchQuery.cs new file mode 100644 index 00000000..f148252d --- /dev/null +++ b/Oqtane.Shared/Models/SearchQuery.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Oqtane.Shared; + +namespace Oqtane.Models +{ + public class SearchQuery + { + public int SiteId { get; set; } + + public Alias Alias { get; set; } + + public User User { get; set; } + + public string Keywords { get; set; } + + public IList Sources { get; set; } = new List(); + + public DateTime BeginModifiedTimeUtc { get; set; } + + public DateTime EndModifiedTimeUtc { get; set; } + + public IList Tags { get; set; } = new List(); + + public IDictionary Properties { get; set; } = new Dictionary(); + + public int PageIndex { get; set; } + + public int PageSize { get; set; } + + public SearchSortFields SortField { get; set; } + + public SearchSortDirections SortDirection { get; set; } + + public int BodySnippetLength { get; set;} = 255; + } +} diff --git a/Oqtane.Shared/Models/SearchResult.cs b/Oqtane.Shared/Models/SearchResult.cs new file mode 100644 index 00000000..d13cafa1 --- /dev/null +++ b/Oqtane.Shared/Models/SearchResult.cs @@ -0,0 +1,11 @@ +namespace Oqtane.Models +{ + public class SearchResult : SearchDocument + { + public float Score { get; set; } + + public string DisplayScore { get; set; } + + public string Snippet { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchResults.cs b/Oqtane.Shared/Models/SearchResults.cs new file mode 100644 index 00000000..5642468c --- /dev/null +++ b/Oqtane.Shared/Models/SearchResults.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Oqtane.Models +{ + public class SearchResults + { + public IList Results { get; set; } + + public int TotalResults { get; set; } + } +} diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index b2b8c837..f1a14cd4 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -77,6 +77,22 @@ namespace Oqtane.Shared public static readonly string VisitorCookiePrefix = "APP_VISITOR_"; + public const string SearchIndexManagerEnabledSettingFormat = "SearchIndexManager_{0}_Enabled"; + public const string SearchIndexStartTimeSettingName = "SearchIndex_StartTime"; + public const string SearchResultManagersCacheName = "SearchResultManagers"; + public const int SearchDefaultPageSize = 10; + public const string SearchPageIdPropertyName = "PageId"; + public const string SearchModuleIdPropertyName = "ModuleId"; + public const string DefaultSearchProviderName = "Database"; + public const string SearchProviderSettingName = "SearchProvider"; + public const string SearchEnabledSettingName = "SearchEnabled"; + + public const string ModuleSearchIndexManagerName = "Module"; + public const string PageSearchIndexManagerName = "Page"; + + public const int PageSearchIndexManagerPriority = 100; + public const int ModuleSearchIndexManagerPriority = 200; + // Obsolete constants const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; diff --git a/Oqtane.Shared/Shared/SearchUtils.cs b/Oqtane.Shared/Shared/SearchUtils.cs new file mode 100644 index 00000000..b7116d4a --- /dev/null +++ b/Oqtane.Shared/Shared/SearchUtils.cs @@ -0,0 +1,95 @@ +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; + +namespace Oqtane.Shared +{ + public sealed class SearchUtils + { + private const string PunctuationMatch = "[~!#\\$%\\^&*\\(\\)-+=\\{\\[\\}\\]\\|;:\\x22'<,>\\.\\?\\\\\\t\\r\\v\\f\\n]"; + private static readonly Regex _stripWhiteSpaceRegex = new Regex("\\s+", RegexOptions.Compiled); + private static readonly Regex _stripTagsRegex = new Regex("<[^<>]*>", RegexOptions.Compiled); + private static readonly Regex _afterRegEx = new Regex(PunctuationMatch + "\\s", RegexOptions.Compiled); + private static readonly Regex _beforeRegEx = new Regex("\\s" + PunctuationMatch, RegexOptions.Compiled); + + public static string Clean(string html, bool removePunctuation) + { + if (string.IsNullOrWhiteSpace(html)) + { + return string.Empty; + } + + if (html.Contains("<")) + { + html = WebUtility.HtmlDecode(html); + } + + html = StripTags(html, true); + html = WebUtility.HtmlDecode(html); + + if (removePunctuation) + { + html = StripPunctuation(html, true); + html = StripWhiteSpace(html, true); + } + + return html; + } + + public static IList GetKeywordsList(string keywords) + { + var keywordsList = new List(); + if(!string.IsNullOrEmpty(keywords)) + { + foreach (var keyword in keywords.Split(' ')) + { + if (!string.IsNullOrWhiteSpace(keyword.Trim())) + { + keywordsList.Add(keyword.Trim()); + } + } + } + + return keywordsList; + } + + private static string StripTags(string html, bool retainSpace) + { + return _stripTagsRegex.Replace(html, retainSpace ? " " : string.Empty); + } + + private static string StripPunctuation(string html, bool retainSpace) + { + if (string.IsNullOrWhiteSpace(html)) + { + return string.Empty; + } + + string retHTML = html + " "; + + var repString = retainSpace ? " " : string.Empty; + while (_beforeRegEx.IsMatch(retHTML)) + { + retHTML = _beforeRegEx.Replace(retHTML, repString); + } + + while (_afterRegEx.IsMatch(retHTML)) + { + retHTML = _afterRegEx.Replace(retHTML, repString); + } + + return retHTML.Trim('"'); + } + + private static string StripWhiteSpace(string html, bool retainSpace) + { + if (string.IsNullOrWhiteSpace(html)) + { + return string.Empty; + } + + return _stripWhiteSpaceRegex.Replace(html, retainSpace ? " " : string.Empty); + } + } +} From 7f970d489f414ef99e2d35b788f2794348fc786b Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Jun 2024 17:32:31 +0800 Subject: [PATCH 123/189] refactoring the code. --- .../{ => Admin}/SearchResults/Index.razor | 66 +++---- .../{ => Admin}/SearchResults/ModuleInfo.cs | 9 +- .../{ => Admin}/SearchResults/Settings.razor | 10 +- .../Services/SearchResultsService.cs | 23 --- .../{ => Admin}/SearchResults/Index.resx | 0 .../{ => Admin}/SearchResults/Settings.resx | 0 .../Interfaces}/ISearchResultsService.cs | 4 +- .../Services/SearchResultsService.cs | 23 +++ .../Themes/Controls/Theme/Search.razor | 6 +- .../Controllers/SearchResultsController.cs | 16 +- .../OqtaneServiceCollectionExtensions.cs | 3 +- .../Infrastructure/Jobs/SearchIndexJob.cs | 8 +- .../SiteTemplates/DefaultSiteTemplate.cs | 4 +- .../Infrastructure/UpgradeManager.cs | 46 +++++ .../Search/ModuleSearchIndexManager.cs | 85 +++++---- .../Search/ModuleSearchResultManager.cs | 17 +- .../Managers/Search/PageSearchIndexManager.cs | 43 +++-- .../Search/PageSearchResultManager.cs | 46 ----- .../Managers/Search/SearchIndexManagerBase.cs | 6 +- ...ilder.cs => SearchContentEntityBuilder.cs} | 27 ++- .../SearchContentPropertyEntityBuilder.cs | 40 +++++ .../SearchContentWordCountEntityBuilder.cs | 42 +++++ .../SearchContentWordSourceEntityBuilder.cs | 31 ++++ .../SearchDocumentPropertyEntityBuilder.cs | 40 ----- .../SearchDocumentTagEntityBuilder.cs | 37 ---- .../Tenant/05020001_AddSearchTables.cs | 32 ++-- .../HtmlText/Manager/HtmlTextManager.cs | 12 +- .../Services/SearchResultsService.cs | 36 ---- .../SearchResults/Startup/ServerStartup.cs | 24 --- Oqtane.Server/Oqtane.Server.csproj | 1 + .../Providers/DatabaseSearchProvider.cs | 165 +++++++++++++---- .../Repository/Context/TenantDBContext.cs | 7 +- .../Interfaces/ISearchContentRepository.cs | 24 +++ .../Interfaces/ISearchDocumentRepository.cs | 17 -- .../Repository/SearchContentRepository.cs | 167 ++++++++++++++++++ .../Repository/SearchDocumentRepository.cs | 136 -------------- Oqtane.Server/Services/SearchService.cs | 81 +++++++-- .../Module.css | 0 .../Interfaces/ISearchIndexManager.cs | 2 +- Oqtane.Shared/Interfaces/ISearchProvider.cs | 6 +- .../Interfaces/ISearchResultManager.cs | 2 +- .../{IModuleSearch.cs => ISearchable.cs} | 4 +- .../{SearchDocument.cs => SearchContent.cs} | 18 +- ...ntProperty.cs => SearchContentProperty.cs} | 4 +- .../Models/SearchContentWordSource.cs | 12 ++ Oqtane.Shared/Models/SearchContentWords.cs | 19 ++ Oqtane.Shared/Models/SearchDocumentTag.cs | 14 -- Oqtane.Shared/Models/SearchQuery.cs | 4 +- Oqtane.Shared/Models/SearchResult.cs | 2 +- Oqtane.Shared/Shared/Constants.cs | 16 +- Oqtane.Shared/Shared/SearchUtils.cs | 69 +------- 51 files changed, 806 insertions(+), 700 deletions(-) rename Oqtane.Client/Modules/{ => Admin}/SearchResults/Index.razor (68%) rename Oqtane.Client/Modules/{ => Admin}/SearchResults/ModuleInfo.cs (64%) rename Oqtane.Client/Modules/{ => Admin}/SearchResults/Settings.razor (83%) delete mode 100644 Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs rename Oqtane.Client/Resources/Modules/{ => Admin}/SearchResults/Index.resx (100%) rename Oqtane.Client/Resources/Modules/{ => Admin}/SearchResults/Settings.resx (100%) rename Oqtane.Client/{Modules/SearchResults/Services => Services/Interfaces}/ISearchResultsService.cs (67%) create mode 100644 Oqtane.Client/Services/SearchResultsService.cs rename Oqtane.Server/{Modules/SearchResults => }/Controllers/SearchResultsController.cs (57%) delete mode 100644 Oqtane.Server/Managers/Search/PageSearchResultManager.cs rename Oqtane.Server/Migrations/EntityBuilders/{SearchDocumentEntityBuilder.cs => SearchContentEntityBuilder.cs} (58%) create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentPropertyEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs delete mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs delete mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs delete mode 100644 Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs delete mode 100644 Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs create mode 100644 Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs delete mode 100644 Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs create mode 100644 Oqtane.Server/Repository/SearchContentRepository.cs delete mode 100644 Oqtane.Server/Repository/SearchDocumentRepository.cs rename Oqtane.Server/wwwroot/Modules/{Oqtane.Modules.SearchResults => Oqtane.Modules.Admin.SearchResults}/Module.css (100%) rename Oqtane.Shared/Interfaces/{IModuleSearch.cs => ISearchable.cs} (58%) rename Oqtane.Shared/Models/{SearchDocument.cs => SearchContent.cs} (57%) rename Oqtane.Shared/Models/{SearchDocumentProperty.cs => SearchContentProperty.cs} (74%) create mode 100644 Oqtane.Shared/Models/SearchContentWordSource.cs create mode 100644 Oqtane.Shared/Models/SearchContentWords.cs delete mode 100644 Oqtane.Shared/Models/SearchDocumentTag.cs diff --git a/Oqtane.Client/Modules/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor similarity index 68% rename from Oqtane.Client/Modules/SearchResults/Index.razor rename to Oqtane.Client/Modules/Admin/SearchResults/Index.razor index 8ae0c087..2cbc3098 100644 --- a/Oqtane.Client/Modules/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -1,22 +1,26 @@ -@using Oqtane.Modules.SearchResults.Services -@namespace Oqtane.Modules.SearchResults +@using Microsoft.AspNetCore.Http +@using Oqtane.Services +@using System.Net +@namespace Oqtane.Modules.Admin.SearchResults @inherits ModuleBase @inject ISearchResultsService SearchResultsService @inject IStringLocalizer Localizer +@inject IHttpContextAccessor HttpContext
-
- @Localizer["SearchPrefix"] - - -
+
+
+ @Localizer["SearchPrefix"] + + + +
+
@@ -35,12 +39,13 @@ Format="Grid" PageSize="@_pageSize.ToString()" DisplayPages="@_displayPages.ToString()" - CurrentPage="@_currentPage.ToString()" Columns="1" - Toolbar="Bottom"> + CurrentPage="@_currentPage.ToString()" + Columns="1" + Toolbar="Bottom" + Parameters="@($"q={_keywords}")">

@context.Title

-
@context.Url

@((MarkupString)context.Snippet)

@@ -59,13 +64,16 @@
@code { + public override string RenderMode => RenderModes.Static; + private const int SearchDefaultPageSize = 10; + private SearchSortDirections _searchSortDirection = SearchSortDirections.Descending; //default sort by private SearchSortFields _searchSortField = SearchSortFields.Relevance; private string _keywords; private bool _loading; private SearchResults _searchResults; private int _currentPage = 0; - private int _pageSize = Constants.SearchDefaultPageSize; + private int _pageSize = SearchDefaultPageSize; private int _displayPages = 7; protected override async Task OnInitializedAsync() @@ -75,18 +83,9 @@ _pageSize = int.Parse(ModuleState.Settings["PageSize"]); } - if (PageState.QueryString.ContainsKey("s")) + if (PageState.QueryString.ContainsKey("q")) { - _keywords = PageState.QueryString["s"]; - } - - if (PageState.QueryString.ContainsKey("p")) - { - _currentPage = Convert.ToInt32(PageState.QueryString["p"]); - if (_currentPage < 1) - { - _currentPage = 1; - } + _keywords = WebUtility.UrlDecode(PageState.QueryString["q"]); } if (!string.IsNullOrEmpty(_keywords)) @@ -95,19 +94,9 @@ } } - private async Task KeywordsChanged(KeyboardEventArgs e) - { - if (e.Code == "Enter" || e.Code == "NumpadEnter") - { - if (!string.IsNullOrEmpty(_keywords)) - { - await Search(); - } - } - } - private async Task Search() { + _keywords = HttpContext.HttpContext.Request.Form["keywords"]; if (string.IsNullOrEmpty(_keywords)) { AddModuleMessage(Localizer["MissingKeywords"], MessageType.Warning); @@ -116,7 +105,6 @@ { ClearModuleMessage(); - _currentPage = 0; await PerformSearch(); } diff --git a/Oqtane.Client/Modules/SearchResults/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs similarity index 64% rename from Oqtane.Client/Modules/SearchResults/ModuleInfo.cs rename to Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs index ae4bc7d2..97a6835e 100644 --- a/Oqtane.Client/Modules/SearchResults/ModuleInfo.cs +++ b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs @@ -3,19 +3,18 @@ using Oqtane.Documentation; using Oqtane.Models; using Oqtane.Shared; -namespace Oqtane.Modules.SearchResults +namespace Oqtane.Modules.Admin.SearchResults { - [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] + [PrivateApi("Mark this as private, since it's not very useful in the public docs")] public class ModuleInfo : IModule { public ModuleDefinition ModuleDefinition => new ModuleDefinition { Name = "Search Results", Description = "Display Search Results", - Version = "1.0.0", + Version = Constants.Version, ServerManagerType = "", - ReleaseVersions = "1.0.0", - SettingsType = "Oqtane.Modules.SearchResults.Settings, Oqtane.Client", + SettingsType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client", Resources = new List() { new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } diff --git a/Oqtane.Client/Modules/SearchResults/Settings.razor b/Oqtane.Client/Modules/Admin/SearchResults/Settings.razor similarity index 83% rename from Oqtane.Client/Modules/SearchResults/Settings.razor rename to Oqtane.Client/Modules/Admin/SearchResults/Settings.razor index b38d2946..1e98f0f6 100644 --- a/Oqtane.Client/Modules/SearchResults/Settings.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Settings.razor @@ -1,7 +1,7 @@ -@namespace Oqtane.Modules.SearchResults +@namespace Oqtane.Modules.Admin.SearchResults @inherits ModuleBase -@inject ISettingService SettingService @implements Oqtane.Interfaces.ISettingsControl +@inject ISettingService SettingService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -15,14 +15,16 @@
@code { - private string resourceType = "Oqtane.Modules.SearchResults.Settings, Oqtane.Client"; // for localization + private const string SearchDefaultPageSize = "10"; + + private string resourceType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"; // for localization private string _pageSize; protected override void OnInitialized() { try { - _pageSize = SettingService.GetSetting(ModuleState.Settings, "PageSize", Constants.SearchDefaultPageSize.ToString()); + _pageSize = SettingService.GetSetting(ModuleState.Settings, "PageSize", SearchDefaultPageSize); } catch (Exception ex) { diff --git a/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs b/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs deleted file mode 100644 index 3b241ea4..00000000 --- a/Oqtane.Client/Modules/SearchResults/Services/SearchResultsService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Oqtane.Documentation; -using Oqtane.Models; -using Oqtane.Services; -using Oqtane.Shared; - -namespace Oqtane.Modules.SearchResults.Services -{ - [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] - public class SearchResultsService : ServiceBase, ISearchResultsService, IClientService - { - public SearchResultsService(HttpClient http, SiteState siteState) : base(http, siteState) {} - - private string ApiUrl => CreateApiUrl("SearchResults"); - - public async Task SearchAsync(int moduleId, SearchQuery searchQuery) - { - return await PostJsonAsync(CreateAuthorizationPolicyUrl(ApiUrl, EntityNames.Module, moduleId), searchQuery); - } - } -} diff --git a/Oqtane.Client/Resources/Modules/SearchResults/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx similarity index 100% rename from Oqtane.Client/Resources/Modules/SearchResults/Index.resx rename to Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx diff --git a/Oqtane.Client/Resources/Modules/SearchResults/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx similarity index 100% rename from Oqtane.Client/Resources/Modules/SearchResults/Settings.resx rename to Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx diff --git a/Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs b/Oqtane.Client/Services/Interfaces/ISearchResultsService.cs similarity index 67% rename from Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs rename to Oqtane.Client/Services/Interfaces/ISearchResultsService.cs index 2f1c1308..0671117c 100644 --- a/Oqtane.Client/Modules/SearchResults/Services/ISearchResultsService.cs +++ b/Oqtane.Client/Services/Interfaces/ISearchResultsService.cs @@ -3,11 +3,11 @@ using System.Threading.Tasks; using Oqtane.Documentation; using Oqtane.Models; -namespace Oqtane.Modules.SearchResults.Services +namespace Oqtane.Services { [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] public interface ISearchResultsService { - Task SearchAsync(int moduleId, SearchQuery searchQuery); + Task SearchAsync(int moduleId, SearchQuery searchQuery); } } diff --git a/Oqtane.Client/Services/SearchResultsService.cs b/Oqtane.Client/Services/SearchResultsService.cs new file mode 100644 index 00000000..da9687d5 --- /dev/null +++ b/Oqtane.Client/Services/SearchResultsService.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Oqtane.Documentation; +using Oqtane.Models; +using Oqtane.Modules; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class SearchResultsService : ServiceBase, ISearchResultsService, IClientService + { + public SearchResultsService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string ApiUrl => CreateApiUrl("SearchResults"); + + public async Task SearchAsync(int moduleId, SearchQuery searchQuery) + { + return await PostJsonAsync(CreateAuthorizationPolicyUrl(ApiUrl, EntityNames.Module, moduleId), searchQuery); + } + } +} diff --git a/Oqtane.Client/Themes/Controls/Theme/Search.razor b/Oqtane.Client/Themes/Controls/Theme/Search.razor index dbd28b40..fc4980ef 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Search.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Search.razor @@ -16,7 +16,7 @@ @bind-value="_keywords" placeholder="@Localizer["SearchPlaceHolder"]" aria-label="Search" /> - @@ -26,7 +26,7 @@ @code { - private const string SearchResultPagePath = "search-results"; + private const string SearchResultPagePath = "search"; private Page _searchResultsPage; private string _keywords = ""; @@ -48,7 +48,7 @@ var keywords = HttpContext.HttpContext.Request.Form["keywords"]; if (!string.IsNullOrEmpty(keywords) && _searchResultsPage != null) { - var url = NavigateUrl(_searchResultsPage.Path, $"s={keywords}"); + var url = NavigateUrl(_searchResultsPage.Path, $"q={keywords}"); NavigationManager.NavigateTo(url); } } diff --git a/Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs b/Oqtane.Server/Controllers/SearchResultsController.cs similarity index 57% rename from Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs rename to Oqtane.Server/Controllers/SearchResultsController.cs index d134b1ae..4b72a464 100644 --- a/Oqtane.Server/Modules/SearchResults/Controllers/SearchResultsController.cs +++ b/Oqtane.Server/Controllers/SearchResultsController.cs @@ -4,24 +4,22 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Oqtane.Controllers; using Oqtane.Documentation; using Oqtane.Enums; using Oqtane.Infrastructure; -using Oqtane.Modules.SearchResults.Services; +using Oqtane.Services; using Oqtane.Shared; -namespace Oqtane.Modules.SearchResults.Controllers +namespace Oqtane.Controllers { [Route(ControllerRoutes.ApiRoute)] - [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] public class SearchResultsController : ModuleControllerBase { - private readonly ISearchResultsService _searchResultsService; + private readonly ISearchService _searchService; - public SearchResultsController(ISearchResultsService searchResultsService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor) + public SearchResultsController(ISearchService searchService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor) { - _searchResultsService = searchResultsService; + _searchService = searchService; } [HttpPost] @@ -30,9 +28,9 @@ namespace Oqtane.Modules.SearchResults.Controllers { try { - return await _searchResultsService.SearchAsync(AuthEntityId(EntityNames.Module), searchQuery); + return await _searchService.SearchAsync(searchQuery); } - catch(Exception ex) + catch (Exception ex) { _logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Fetch search results failed.", searchQuery); HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index a6612866..d018a4bc 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -98,6 +98,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -134,7 +135,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); // managers services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs index e6b7f216..8ad3e194 100644 --- a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs @@ -11,6 +11,8 @@ namespace Oqtane.Infrastructure { public class SearchIndexJob : HostedServiceBase { + private const string SearchIndexStartTimeSettingName = "SearchIndex_StartTime"; + public SearchIndexJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) { Name = "Search Index Job"; @@ -54,7 +56,7 @@ namespace Oqtane.Infrastructure private DateTime? GetSearchStartTime(int siteId, ISettingRepository settingRepository) { - var setting = settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchIndexStartTimeSettingName); + var setting = settingRepository.GetSetting(EntityNames.Site, siteId, SearchIndexStartTimeSettingName); if(setting == null) { return null; @@ -65,14 +67,14 @@ namespace Oqtane.Infrastructure private void UpdateSearchStartTime(int siteId, DateTime startTime, ISettingRepository settingRepository) { - var setting = settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchIndexStartTimeSettingName); + var setting = settingRepository.GetSetting(EntityNames.Site, siteId, SearchIndexStartTimeSettingName); if (setting == null) { setting = new Setting { EntityName = EntityNames.Site, EntityId = siteId, - SettingName = Constants.SearchIndexStartTimeSettingName, + SettingName = SearchIndexStartTimeSettingName, SettingValue = Convert.ToString(startTime), }; diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs index 99bb8aaa..447678ed 100644 --- a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs +++ b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs @@ -138,7 +138,7 @@ namespace Oqtane.SiteTemplates Name = "Search Results", Parent = "", Order = 7, - Path = "search-results", + Path = "search", Icon = "oi oi-magnifying-glass", IsNavigation = false, IsPersonalizable = false, @@ -148,7 +148,7 @@ namespace Oqtane.SiteTemplates new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, PageTemplateModules = new List { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, PermissionList = new List { new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.View, RoleNames.Admin, true), diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 3f0dd0f6..8737dd65 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -66,6 +66,9 @@ namespace Oqtane.Infrastructure case "5.1.0": Upgrade_5_1_0(tenant, scope); break; + case "5.2.0": + Upgrade_5_2_0(tenant, scope); + break; } } } @@ -385,5 +388,48 @@ namespace Oqtane.Infrastructure } } + + private void Upgrade_5_2_0(Tenant tenant, IServiceScope scope) + { + CreateSearchResultsPages(tenant, scope); + } + + private void CreateSearchResultsPages(Tenant tenant, IServiceScope scope) + { + var pageTemplates = new List(); + pageTemplates.Add(new PageTemplate + { + Name = "Search Results", + Parent = "", + Path = "search", + Icon = "oi oi-magnifying-glass", + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List { + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + } + } + } + }); + + var pages = scope.ServiceProvider.GetRequiredService(); + var sites = scope.ServiceProvider.GetRequiredService(); + foreach (var site in sites.GetSites().ToList()) + { + if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "search").Any()) + { + sites.CreatePages(site, pageTemplates, null); + } + } + } } } diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index fec739d2..b68c993f 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -12,6 +12,8 @@ namespace Oqtane.Managers.Search { public class ModuleSearchIndexManager : SearchIndexManagerBase { + public const int ModuleSearchIndexManagerPriority = 200; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly IPageModuleRepository _pageModuleRepostory; @@ -30,35 +32,41 @@ namespace Oqtane.Managers.Search _pageRepository = pageRepository; } - public override string Name => Constants.ModuleSearchIndexManagerName; + public override string Name => EntityNames.Module; - public override int Priority => Constants.ModuleSearchIndexManagerPriority; + public override int Priority => ModuleSearchIndexManagerPriority; - public override int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError) + public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) { var pageModules = _pageModuleRepostory.GetPageModules(siteId).DistinctBy(i => i.ModuleId); - var searchDocuments = new List(); + var searchContentList = new List(); foreach(var pageModule in pageModules) { + var page = _pageRepository.GetPage(pageModule.PageId); + if(page == null || SearchUtils.IsSystemPage(page)) + { + continue; + } + var module = pageModule.Module; if (module.ModuleDefinition.ServerManagerType != "") { _logger.LogDebug($"Search: Begin index module {module.ModuleId}."); var type = Type.GetType(module.ModuleDefinition.ServerManagerType); - if (type?.GetInterface("IModuleSearch") != null) + if (type?.GetInterface(nameof(ISearchable)) != null) { try { - var moduleSearch = (IModuleSearch)ActivatorUtilities.CreateInstance(_serviceProvider, type); - var documents = moduleSearch.GetSearchDocuments(module, startTime.GetValueOrDefault(DateTime.MinValue)); - if(documents != null) + var moduleSearch = (ISearchable)ActivatorUtilities.CreateInstance(_serviceProvider, type); + var contentList = moduleSearch.GetSearchContentList(module, startTime.GetValueOrDefault(DateTime.MinValue)); + if(contentList != null) { - foreach(var document in documents) + foreach(var searchContent in contentList) { - SaveModuleMetaData(document, pageModule); + SaveModuleMetaData(searchContent, pageModule); - searchDocuments.Add(document); + searchContentList.Add(searchContent); } } @@ -73,54 +81,65 @@ namespace Oqtane.Managers.Search } } - processSearchDocuments(searchDocuments); + processSearchContent(searchContentList); - return searchDocuments.Count; + return searchContentList.Count; } - private void SaveModuleMetaData(SearchDocument document, PageModule pageModule) + private void SaveModuleMetaData(SearchContent searchContent, PageModule pageModule) { - - document.EntryId = pageModule.ModuleId; - document.IndexerName = Name; - document.SiteId = pageModule.Module.SiteId; - document.LanguageCode = string.Empty; + searchContent.SiteId = pageModule.Module.SiteId; - if(document.ModifiedTime == DateTime.MinValue) + if(string.IsNullOrEmpty(searchContent.EntityName)) { - document.ModifiedTime = pageModule.ModifiedOn; + searchContent.EntityName = EntityNames.Module; } - if (string.IsNullOrEmpty(document.AdditionalContent)) + if(searchContent.EntityId == 0) { - document.AdditionalContent = string.Empty; + searchContent.EntityId = pageModule.ModuleId; + } + + if (searchContent.IsActive) + { + searchContent.IsActive = !pageModule.Module.IsDeleted; + } + + if (searchContent.ModifiedTime == DateTime.MinValue) + { + searchContent.ModifiedTime = pageModule.ModifiedOn; + } + + if (string.IsNullOrEmpty(searchContent.AdditionalContent)) + { + searchContent.AdditionalContent = string.Empty; } var page = _pageRepository.GetPage(pageModule.PageId); - if (string.IsNullOrEmpty(document.Url) && page != null) + if (string.IsNullOrEmpty(searchContent.Url) && page != null) { - document.Url = page.Url; + searchContent.Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}"; } - if (string.IsNullOrEmpty(document.Title) && page != null) + if (string.IsNullOrEmpty(searchContent.Title) && page != null) { - document.Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name; + searchContent.Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name; } - if (document.Properties == null) + if (searchContent.Properties == null) { - document.Properties = new List(); + searchContent.Properties = new List(); } - if(!document.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + if(!searchContent.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) { - document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() }); + searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() }); } - if (!document.Properties.Any(i => i.Name == Constants.SearchModuleIdPropertyName)) + if (!searchContent.Properties.Any(i => i.Name == Constants.SearchModuleIdPropertyName)) { - document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() }); + searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() }); } } } diff --git a/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs index 55789c23..c40f5f0b 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs @@ -11,7 +11,7 @@ namespace Oqtane.Managers.Search { public class ModuleSearchResultManager : ISearchResultManager { - public string Name => Constants.ModuleSearchIndexManagerName; + public string Name => EntityNames.Module; private readonly IServiceProvider _serviceProvider; @@ -37,26 +37,17 @@ namespace Oqtane.Managers.Search return string.Empty; } - public bool Visible(SearchDocument searchResult, SearchQuery searchQuery) + public bool Visible(SearchContent searchResult, SearchQuery searchQuery) { var pageIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; - var moduleIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchModuleIdPropertyName)?.Value ?? string.Empty; - if (!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId) - && !string.IsNullOrEmpty(moduleIdValue) && int.TryParse(moduleIdValue, out int moduleId)) + if (!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId)) { - return CanViewPage(pageId, searchQuery.User) && CanViewModule(moduleId, searchQuery.User); + return CanViewPage(pageId, searchQuery.User); } return false; } - private bool CanViewModule(int moduleId, User user) - { - var moduleRepository = _serviceProvider.GetRequiredService(); - var module = moduleRepository.GetModule(moduleId); - return module != null && !module.IsDeleted && UserSecurity.IsAuthorized(user, PermissionNames.View, module.PermissionList); - } - private bool CanViewPage(int pageId, User user) { var pageRepository = _serviceProvider.GetRequiredService(); diff --git a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs index 68c4af02..49599804 100644 --- a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs @@ -14,6 +14,8 @@ namespace Oqtane.Managers.Search { public class PageSearchIndexManager : SearchIndexManagerBase { + private const int PageSearchIndexManagerPriority = 100; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly IPageRepository _pageRepository; @@ -29,50 +31,50 @@ namespace Oqtane.Managers.Search _pageRepository = pageRepository; } - public override string Name => Constants.PageSearchIndexManagerName; + public override string Name => EntityNames.Page; - public override int Priority => Constants.PageSearchIndexManagerPriority; + public override int Priority => PageSearchIndexManagerPriority; - public override int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError) + public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) { var startTimeValue = startTime.GetValueOrDefault(DateTime.MinValue); var pages = _pageRepository.GetPages(siteId).Where(i => i.ModifiedOn >= startTimeValue); - var searchDocuments = new List(); + var searchContentList = new List(); foreach(var page in pages) { try { - if(IsSystemPage(page)) + if(SearchUtils.IsSystemPage(page)) { continue; } - var document = new SearchDocument + var searchContent = new SearchContent { - EntryId = page.PageId, - IndexerName = Name, + EntityName = EntityNames.Page, + EntityId = page.PageId, SiteId = page.SiteId, - LanguageCode = string.Empty, ModifiedTime = page.ModifiedOn, AdditionalContent = string.Empty, - Url = page.Url ?? string.Empty, + Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}", Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name, Description = string.Empty, - Body = $"{page.Name} {page.Title}" + Body = $"{page.Name} {page.Title}", + IsActive = !page.IsDeleted && Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) }; - if (document.Properties == null) + if (searchContent.Properties == null) { - document.Properties = new List(); + searchContent.Properties = new List(); } - if (!document.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + if (!searchContent.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) { - document.Properties.Add(new SearchDocumentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() }); + searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() }); } - searchDocuments.Add(document); + searchContentList.Add(searchContent); } catch(Exception ex) { @@ -81,14 +83,9 @@ namespace Oqtane.Managers.Search } } - processSearchDocuments(searchDocuments); + processSearchContent(searchContentList); - return searchDocuments.Count; - } - - private bool IsSystemPage(Models.Page page) - { - return page.Path.Contains("admin") || page.Path == "login" || page.Path == "register" || page.Path == "profile"; + return searchContentList.Count; } } } diff --git a/Oqtane.Server/Managers/Search/PageSearchResultManager.cs b/Oqtane.Server/Managers/Search/PageSearchResultManager.cs deleted file mode 100644 index fd5456a4..00000000 --- a/Oqtane.Server/Managers/Search/PageSearchResultManager.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Oqtane.Models; -using Oqtane.Repository; -using Oqtane.Security; -using Oqtane.Services; -using Oqtane.Shared; - -namespace Oqtane.Managers.Search -{ - public class PageSearchResultManager : ISearchResultManager - { - public string Name => Constants.PageSearchIndexManagerName; - - private readonly IServiceProvider _serviceProvider; - - public PageSearchResultManager( - IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - - public string GetUrl(SearchResult searchResult, SearchQuery searchQuery) - { - var pageRepository = _serviceProvider.GetRequiredService(); - var page = pageRepository.GetPage(searchResult.EntryId); - if (page != null) - { - return $"{searchQuery.Alias.Protocol}{searchQuery.Alias.Name}{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}"; - } - - return string.Empty; - } - - public bool Visible(SearchDocument searchResult, SearchQuery searchQuery) - { - var pageRepository = _serviceProvider.GetRequiredService(); - var page = pageRepository.GetPage(searchResult.EntryId); - - return page != null && !page.IsDeleted - && UserSecurity.IsAuthorized(searchQuery.User, PermissionNames.View, page.PermissionList) - && (Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) || UserSecurity.IsAuthorized(searchQuery.User, PermissionNames.Edit, page.PermissionList)); - } - } -} diff --git a/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs index 0ecb2880..779a950d 100644 --- a/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs +++ b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs @@ -10,6 +10,8 @@ namespace Oqtane.Managers.Search { public abstract class SearchIndexManagerBase : ISearchIndexManager { + private const string SearchIndexManagerEnabledSettingFormat = "SearchIndexManager_{0}_Enabled"; + private readonly IServiceProvider _serviceProvider; public SearchIndexManagerBase(IServiceProvider serviceProvider) @@ -21,11 +23,11 @@ namespace Oqtane.Managers.Search public abstract string Name { get; } - public abstract int IndexDocuments(int siteId, DateTime? startDate, Action> processSearchDocuments, Action handleError); + public abstract int IndexContent(int siteId, DateTime? startDate, Action> processSearchContent, Action handleError); public virtual bool IsIndexEnabled(int siteId) { - var settingName = string.Format(Constants.SearchIndexManagerEnabledSettingFormat, Name); + var settingName = string.Format(SearchIndexManagerEnabledSettingFormat, Name); var settingRepository = _serviceProvider.GetRequiredService(); var setting = settingRepository.GetSetting(EntityNames.Site, siteId, settingName); return setting == null || setting.SettingValue == "true"; diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs similarity index 58% rename from Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs rename to Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs index 2f5b6d8d..4cf2aa4a 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs @@ -2,25 +2,26 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; using Oqtane.Databases.Interfaces; +using Oqtane.Models; namespace Oqtane.Migrations.EntityBuilders { - public class SearchDocumentEntityBuilder : AuditableBaseEntityBuilder + public class SearchContentEntityBuilder : AuditableBaseEntityBuilder { - private const string _entityTableName = "SearchDocument"; - private readonly PrimaryKey _primaryKey = new("PK_SearchDocument", x => x.SearchDocumentId); + private const string _entityTableName = "SearchContent"; + private readonly PrimaryKey _primaryKey = new("PK_SearchContent", x => x.SearchContentId); - public SearchDocumentEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + public SearchContentEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) { EntityTableName = _entityTableName; PrimaryKey = _primaryKey; } - protected override SearchDocumentEntityBuilder BuildTable(ColumnsBuilder table) + protected override SearchContentEntityBuilder BuildTable(ColumnsBuilder table) { - SearchDocumentId = AddAutoIncrementColumn(table, "SearchDocumentId"); - EntryId = AddIntegerColumn(table, "EntryId"); - IndexerName = AddStringColumn(table, "IndexerName", 50); + SearchContentId = AddAutoIncrementColumn(table, "SearchContentId"); + EntityName = AddStringColumn(table, "EntityName", 50); + EntityId = AddIntegerColumn(table, "EntityId"); SiteId = AddIntegerColumn(table, "SiteId"); Title = AddStringColumn(table, "Title", 255); Description = AddMaxStringColumn(table, "Description"); @@ -29,18 +30,17 @@ namespace Oqtane.Migrations.EntityBuilders ModifiedTime = AddDateTimeColumn(table, "ModifiedTime"); IsActive = AddBooleanColumn(table, "IsActive"); AdditionalContent = AddMaxStringColumn(table, "AdditionalContent"); - LanguageCode = AddStringColumn(table, "LanguageCode", 20); AddAuditableColumns(table); return this; } - public OperationBuilder SearchDocumentId { get; private set; } + public OperationBuilder SearchContentId { get; private set; } - public OperationBuilder EntryId { get; private set; } + public OperationBuilder EntityName { get; private set; } - public OperationBuilder IndexerName { get; private set; } + public OperationBuilder EntityId { get; private set; } public OperationBuilder SiteId { get; private set; } @@ -57,8 +57,5 @@ namespace Oqtane.Migrations.EntityBuilders public OperationBuilder IsActive { get; private set; } public OperationBuilder AdditionalContent { get; private set; } - - public OperationBuilder LanguageCode { get; private set; } - } } diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentPropertyEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentPropertyEntityBuilder.cs new file mode 100644 index 00000000..01870cd1 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentPropertyEntityBuilder.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchContentPropertyEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchContentProperty"; + private readonly PrimaryKey _primaryKey = new("PK_SearchContentProperty", x => x.PropertyId); + private readonly ForeignKey _searchContentForeignKey = new("FK_SearchContentProperty_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade); + + public SearchContentPropertyEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + + ForeignKeys.Add(_searchContentForeignKey); + } + + protected override SearchContentPropertyEntityBuilder BuildTable(ColumnsBuilder table) + { + PropertyId = AddAutoIncrementColumn(table, "PropertyId"); + SearchContentId = AddIntegerColumn(table, "SearchContentId"); + Name = AddStringColumn(table, "Name", 50); + Value = AddStringColumn(table, "Value", 50); + + return this; + } + + public OperationBuilder PropertyId { get; private set; } + + public OperationBuilder SearchContentId { get; private set; } + + public OperationBuilder Name { get; private set; } + + public OperationBuilder Value { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs new file mode 100644 index 00000000..2eb845fc --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchContentWordsEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchContentWords"; + private readonly PrimaryKey _primaryKey = new("PK_SearchContentWords", x => x.WordId); + private readonly ForeignKey _searchContentForeignKey = new("FK_SearchContentWords_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade); + private readonly ForeignKey _wordSourceForeignKey = new("FK_SearchContentWords_WordSource", x => x.WordSourceId, "SearchContentWordSource", "WordSourceId", ReferentialAction.Cascade); + + public SearchContentWordsEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + + ForeignKeys.Add(_searchContentForeignKey); + ForeignKeys.Add(_wordSourceForeignKey); + } + + protected override SearchContentWordsEntityBuilder BuildTable(ColumnsBuilder table) + { + WordId = AddAutoIncrementColumn(table, "WordId"); + SearchContentId = AddIntegerColumn(table, "SearchContentId"); + WordSourceId = AddIntegerColumn(table, "WordSourceId"); + Count = AddIntegerColumn(table, "Count"); + + return this; + } + + public OperationBuilder WordId { get; private set; } + + public OperationBuilder SearchContentId { get; private set; } + + public OperationBuilder WordSourceId { get; private set; } + + public OperationBuilder Count { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs new file mode 100644 index 00000000..0787a3a4 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchContentWordSourceEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchContentWordSource"; + private readonly PrimaryKey _primaryKey = new("PK_SearchContentWordSource", x => x.WordSourceId); + + public SearchContentWordSourceEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override SearchContentWordSourceEntityBuilder BuildTable(ColumnsBuilder table) + { + WordSourceId = AddAutoIncrementColumn(table, "WordSourceId"); + Word = AddStringColumn(table, "Word", 255); + + return this; + } + + public OperationBuilder WordSourceId { get; private set; } + + public OperationBuilder Word { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs deleted file mode 100644 index a591aec2..00000000 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentPropertyEntityBuilder.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; -using Oqtane.Databases.Interfaces; - -namespace Oqtane.Migrations.EntityBuilders -{ - public class SearchDocumentPropertyEntityBuilder : BaseEntityBuilder - { - private const string _entityTableName = "SearchDocumentProperty"; - private readonly PrimaryKey _primaryKey = new("PK_SearchDocumentProperty", x => x.PropertyId); - private readonly ForeignKey _searchDocumentForeignKey = new("FK_SearchDocumentProperty_SearchDocument", x => x.SearchDocumentId, "SearchDocument", "SearchDocumentId", ReferentialAction.Cascade); - - public SearchDocumentPropertyEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) - { - EntityTableName = _entityTableName; - PrimaryKey = _primaryKey; - - ForeignKeys.Add(_searchDocumentForeignKey); - } - - protected override SearchDocumentPropertyEntityBuilder BuildTable(ColumnsBuilder table) - { - PropertyId = AddAutoIncrementColumn(table, "PropertyId"); - SearchDocumentId = AddIntegerColumn(table, "SearchDocumentId"); - Name = AddStringColumn(table, "Name", 50); - Value = AddStringColumn(table, "Value", 50); - - return this; - } - - public OperationBuilder PropertyId { get; private set; } - - public OperationBuilder SearchDocumentId { get; private set; } - - public OperationBuilder Name { get; private set; } - - public OperationBuilder Value { get; private set; } - } -} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs deleted file mode 100644 index bd864f22..00000000 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchDocumentTagEntityBuilder.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; -using Oqtane.Databases.Interfaces; - -namespace Oqtane.Migrations.EntityBuilders -{ - public class SearchDocumentTagEntityBuilder : BaseEntityBuilder - { - private const string _entityTableName = "SearchDocumentTag"; - private readonly PrimaryKey _primaryKey = new("PK_SearchDocumentTag", x => x.TagId); - private readonly ForeignKey _searchDocumentForeignKey = new("FK_SearchDocumentTag_SearchDocument", x => x.SearchDocumentId, "SearchDocument", "SearchDocumentId", ReferentialAction.Cascade); - - public SearchDocumentTagEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) - { - EntityTableName = _entityTableName; - PrimaryKey = _primaryKey; - - ForeignKeys.Add(_searchDocumentForeignKey); - } - - protected override SearchDocumentTagEntityBuilder BuildTable(ColumnsBuilder table) - { - TagId = AddAutoIncrementColumn(table, "TagId"); - SearchDocumentId = AddIntegerColumn(table, "SearchDocumentId"); - Tag = AddStringColumn(table, "Tag", 50); - - return this; - } - - public OperationBuilder TagId { get; private set; } - - public OperationBuilder SearchDocumentId { get; private set; } - - public OperationBuilder Tag { get; private set; } - } -} diff --git a/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs index f32a8137..c66a4fe5 100644 --- a/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs +++ b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs @@ -17,26 +17,34 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - var searchDocumentEntityBuilder = new SearchDocumentEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentEntityBuilder.Create(); + var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentEntityBuilder.Create(); - var searchDocumentPropertyEntityBuilder = new SearchDocumentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentPropertyEntityBuilder.Create(); + var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentPropertyEntityBuilder.Create(); - var searchDocumentTagEntityBuilder = new SearchDocumentTagEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentTagEntityBuilder.Create(); + var searchContentWordSourceEntityBuilder = new SearchContentWordSourceEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordSourceEntityBuilder.Create(); + searchContentWordSourceEntityBuilder.AddIndex("IX_SearchContentWordSource", "Word", true); + + var searchContentWordsEntityBuilder = new SearchContentWordsEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordsEntityBuilder.Create(); } protected override void Down(MigrationBuilder migrationBuilder) { - var searchDocumentPropertyEntityBuilder = new SearchDocumentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentPropertyEntityBuilder.Drop(); + var searchContentWordsEntityBuilder = new SearchContentWordsEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordsEntityBuilder.Drop(); - var searchDocumentTagEntityBuilder = new SearchDocumentTagEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentTagEntityBuilder.Drop(); + var searchContentWordSourceEntityBuilder = new SearchContentWordSourceEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordSourceEntityBuilder.DropIndex("IX_SearchContentWordSource"); + searchContentWordSourceEntityBuilder.Drop(); - var searchDocumentEntityBuilder = new SearchDocumentEntityBuilder(migrationBuilder, ActiveDatabase); - searchDocumentEntityBuilder.Drop(); + var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentPropertyEntityBuilder.Drop(); + + var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentEntityBuilder.Drop(); } } } diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 8cabf391..e5cfb8de 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -17,7 +17,7 @@ using System; namespace Oqtane.Modules.HtmlText.Manager { [PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")] - public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, IModuleSearch + public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISearchable { private readonly IServiceProvider _serviceProvider; private readonly IHtmlTextRepository _htmlText; @@ -48,25 +48,25 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } - public IList GetSearchDocuments(Module module, DateTime startDate) + public IList GetSearchContentList(Module module, DateTime startDate) { - var searchDocuments = new List(); + var searchContentList = new List(); var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId); if (htmltexts != null && htmltexts.Any(i => i.CreatedOn >= startDate)) { var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First(); - searchDocuments.Add(new SearchDocument + searchContentList.Add(new SearchContent { Title = module.Title, Description = string.Empty, - Body = SearchUtils.Clean(htmltext.Content, true), + Body = htmltext.Content, ModifiedTime = htmltext.ModifiedOn }); } - return searchDocuments; + return searchContentList; } public void ImportModule(Module module, string content, string version) diff --git a/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs b/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs deleted file mode 100644 index 3823bac6..00000000 --- a/Oqtane.Server/Modules/SearchResults/Services/SearchResultsService.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Oqtane.Documentation; -using Oqtane.Infrastructure; -using Oqtane.Models; -using Oqtane.Services; - -namespace Oqtane.Modules.SearchResults.Services -{ - [PrivateApi("Mark SearchResults classes as private, since it's not very useful in the public docs")] - public class ServerSearchResultsService : ISearchResultsService, ITransientService - { - private readonly ILogManager _logger; - private readonly IHttpContextAccessor _accessor; - private readonly Alias _alias; - private readonly ISearchService _searchService; - - public ServerSearchResultsService( - ITenantManager tenantManager, - ILogManager logger, - IHttpContextAccessor accessor, - ISearchService searchService) - { - _logger = logger; - _accessor = accessor; - _alias = tenantManager.GetAlias(); - _searchService = searchService; - } - - public async Task SearchAsync(int moduleId, SearchQuery searchQuery) - { - var results = await _searchService.SearchAsync(searchQuery); - return results; - } - } -} diff --git a/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs b/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs deleted file mode 100644 index 25d2d15f..00000000 --- a/Oqtane.Server/Modules/SearchResults/Startup/ServerStartup.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Oqtane.Infrastructure; -using Oqtane.Modules.SearchResults.Services; - -namespace Oqtane.Modules.SearchResults.Startup -{ - public class ServerStartup : IServerStartup - { - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - } - - public void ConfigureMvc(IMvcBuilder mvcBuilder) - { - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddTransient(); - } - } -} diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index f661e715..52776903 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -33,6 +33,7 @@ + diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index ac2ed576..74884934 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml; +using HtmlAgilityPack; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Services; @@ -13,27 +16,28 @@ namespace Oqtane.Providers { public class DatabaseSearchProvider : ISearchProvider { - private readonly ISearchDocumentRepository _searchDocumentRepository; + private readonly ISearchContentRepository _searchContentRepository; private const float TitleBoost = 100f; private const float DescriptionBoost = 10f; private const float BodyBoost = 10f; private const float AdditionalContentBoost = 5f; - + private const string IgnoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us"; + private const int WordMinLength = 3; public string Name => Constants.DefaultSearchProviderName; - public DatabaseSearchProvider(ISearchDocumentRepository searchDocumentRepository) + public DatabaseSearchProvider(ISearchContentRepository searchContentRepository) { - _searchDocumentRepository = searchDocumentRepository; + _searchContentRepository = searchContentRepository; } public void Commit() { } - public void DeleteDocument(string id) + public void DeleteSearchContent(string id) { - _searchDocumentRepository.DeleteSearchDocument(id); + _searchContentRepository.DeleteSearchContent(id); } public bool Optimize() @@ -43,25 +47,28 @@ namespace Oqtane.Providers public void ResetIndex() { - _searchDocumentRepository.DeleteAllSearchDocuments(); + _searchContentRepository.DeleteAllSearchContent(); } - public void SaveDocument(SearchDocument document, bool autoCommit = false) + public void SaveSearchContent(SearchContent searchContent, bool autoCommit = false) { //remove exist document - _searchDocumentRepository.DeleteSearchDocument(document.IndexerName, document.EntryId); + _searchContentRepository.DeleteSearchContent(searchContent.EntityName, searchContent.EntityId); - _searchDocumentRepository.AddSearchDocument(document); + _searchContentRepository.AddSearchContent(searchContent); + + //save the index words + AnalyzeSearchContent(searchContent); } - public async Task SearchAsync(SearchQuery searchQuery, Func validateFunc) + public async Task SearchAsync(SearchQuery searchQuery, Func validateFunc) { var totalResults = 0; - var documents = await _searchDocumentRepository.GetSearchDocumentsAsync(searchQuery); + var searchContentList = await _searchContentRepository.GetSearchContentListAsync(searchQuery); - //convert the search documents to search results. - var results = documents + //convert the search content to search results. + var results = searchContentList .Where(i => validateFunc(i, searchQuery)) .Select(i => ConvertToSearchResult(i, searchQuery)); @@ -99,7 +106,7 @@ namespace Oqtane.Providers //remove duplicated results based on page id for Page and Module types results = results.DistinctBy(i => { - if (i.IndexerName == Constants.PageSearchIndexManagerName || i.IndexerName == Constants.ModuleSearchIndexManagerName) + if (i.EntityName == EntityNames.Page || i.EntityName == EntityNames.Module) { var pageId = i.Properties.FirstOrDefault(p => p.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; return !string.IsNullOrEmpty(pageId) ? pageId : i.UniqueKey; @@ -119,45 +126,44 @@ namespace Oqtane.Providers }; } - private SearchResult ConvertToSearchResult(SearchDocument searchDocument, SearchQuery searchQuery) + private SearchResult ConvertToSearchResult(SearchContent searchContent, SearchQuery searchQuery) { var searchResult = new SearchResult() { - SearchDocumentId = searchDocument.SearchDocumentId, - SiteId = searchDocument.SiteId, - IndexerName = searchDocument.IndexerName, - EntryId = searchDocument.EntryId, - Title = searchDocument.Title, - Description = searchDocument.Description, - Body = searchDocument.Body, - Url = searchDocument.Url, - ModifiedTime = searchDocument.ModifiedTime, - Tags = searchDocument.Tags, - Properties = searchDocument.Properties, - Snippet = BuildSnippet(searchDocument, searchQuery), - Score = CalculateScore(searchDocument, searchQuery) + SearchContentId = searchContent.SearchContentId, + SiteId = searchContent.SiteId, + EntityName = searchContent.EntityName, + EntityId = searchContent.EntityId, + Title = searchContent.Title, + Description = searchContent.Description, + Body = searchContent.Body, + Url = searchContent.Url, + ModifiedTime = searchContent.ModifiedTime, + Properties = searchContent.Properties, + Snippet = BuildSnippet(searchContent, searchQuery), + Score = CalculateScore(searchContent, searchQuery) }; return searchResult; } - private float CalculateScore(SearchDocument searchDocument, SearchQuery searchQuery) + private float CalculateScore(SearchContent searchContent, SearchQuery searchQuery) { var score = 0f; foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) { - score += Regex.Matches(searchDocument.Title, keyword, RegexOptions.IgnoreCase).Count * TitleBoost; - score += Regex.Matches(searchDocument.Description, keyword, RegexOptions.IgnoreCase).Count * DescriptionBoost; - score += Regex.Matches(searchDocument.Body, keyword, RegexOptions.IgnoreCase).Count * BodyBoost; - score += Regex.Matches(searchDocument.AdditionalContent, keyword, RegexOptions.IgnoreCase).Count * AdditionalContentBoost; + score += Regex.Matches(searchContent.Title, keyword, RegexOptions.IgnoreCase).Count * TitleBoost; + score += Regex.Matches(searchContent.Description, keyword, RegexOptions.IgnoreCase).Count * DescriptionBoost; + score += Regex.Matches(searchContent.Body, keyword, RegexOptions.IgnoreCase).Count * BodyBoost; + score += Regex.Matches(searchContent.AdditionalContent, keyword, RegexOptions.IgnoreCase).Count * AdditionalContentBoost; } return score / 100; } - private string BuildSnippet(SearchDocument searchDocument, SearchQuery searchQuery) + private string BuildSnippet(SearchContent searchContent, SearchQuery searchQuery) { - var content = $"{searchDocument.Title} {searchDocument.Description} {searchDocument.Body}"; + var content = $"{searchContent.Title} {searchContent.Description} {searchContent.Body}"; var snippet = string.Empty; foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) { @@ -198,5 +204,92 @@ namespace Oqtane.Providers return snippet; } + + private void AnalyzeSearchContent(SearchContent searchContent) + { + //analyze the search content and save the index words + var indexContent = $"{searchContent.Title} {searchContent.Description} {searchContent.Body} {searchContent.AdditionalContent}"; + var words = GetWords(indexContent, WordMinLength); + var existWords = _searchContentRepository.GetWords(searchContent.SearchContentId); + foreach (var kvp in words) + { + var word = existWords.FirstOrDefault(i => i.WordSource.Word == kvp.Key); + if (word != null) + { + word.Count = kvp.Value; + _searchContentRepository.UpdateSearchContentWords(word); + } + else + { + var wordSource = _searchContentRepository.GetSearchContentWordSource(kvp.Key); + if (wordSource == null) + { + wordSource = _searchContentRepository.AddSearchContentWordSource(new SearchContentWordSource { Word = kvp.Key }); + } + + word = new SearchContentWords + { + SearchContentId = searchContent.SearchContentId, + WordSourceId = wordSource.WordSourceId, + Count = kvp.Value + }; + + _searchContentRepository.AddSearchContentWords(word); + } + } + } + + private static Dictionary GetWords(string content, int minLength) + { + content = WebUtility.HtmlDecode(content); + + var words = new Dictionary(); + var ignoreWords = IgnoreWords.Split(','); + + var page = new HtmlDocument(); + page.LoadHtml(content); + + var phrases = page.DocumentNode.Descendants().Where(i => + i.NodeType == HtmlNodeType.Text && + i.ParentNode.Name != "script" && + i.ParentNode.Name != "style" && + !string.IsNullOrEmpty(i.InnerText.Trim()) + ).Select(i => FormatText(i.InnerText)); + + foreach (var phrase in phrases) + { + if (!string.IsNullOrEmpty(phrase)) + { + foreach (var word in phrase.Split(' ')) + { + if (word.Length >= minLength && !ignoreWords.Contains(word)) + { + if (!words.ContainsKey(word)) + { + words.Add(word, 1); + } + else + { + words[word] += 1; + } + } + } + } + } + + return words; + } + + private static string FormatText(string text) + { + text = HtmlEntity.DeEntitize(text); + foreach (var punctuation in ".?!,;:-_()[]{}'\"/\\".ToCharArray()) + { + text = text.Replace(punctuation, ' '); + } + text = text.Replace(" ", " ").ToLower().Trim(); + return text; + + } } } diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index cbe57631..cb0dcb28 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -29,8 +29,9 @@ namespace Oqtane.Repository public virtual DbSet Language { get; set; } public virtual DbSet Visitor { get; set; } public virtual DbSet UrlMapping { get; set; } - public virtual DbSet SearchDocument { get; set; } - public virtual DbSet SearchDocumentProperty { get; set; } - public virtual DbSet SearchDocumentTag { get; set; } + public virtual DbSet SearchContent { get; set; } + public virtual DbSet SearchContentProperty { get; set; } + public virtual DbSet SearchContentWords { get; set; } + public virtual DbSet SearchContentWordSource { get; set; } } } diff --git a/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs new file mode 100644 index 00000000..868265b0 --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface ISearchContentRepository + { + Task> GetSearchContentListAsync(SearchQuery searchQuery); + SearchContent AddSearchContent(SearchContent searchContent); + void DeleteSearchContent(int searchContentId); + void DeleteSearchContent(string entityName, int entryId); + void DeleteSearchContent(string uniqueKey); + void DeleteAllSearchContent(); + + SearchContentWordSource GetSearchContentWordSource(string word); + SearchContentWordSource AddSearchContentWordSource(SearchContentWordSource wordSource); + + IEnumerable GetWords(int searchContentId); + SearchContentWords AddSearchContentWords(SearchContentWords word); + SearchContentWords UpdateSearchContentWords(SearchContentWords word); + } +} diff --git a/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs b/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs deleted file mode 100644 index 6128c40c..00000000 --- a/Oqtane.Server/Repository/Interfaces/ISearchDocumentRepository.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Oqtane.Models; - -namespace Oqtane.Repository -{ - public interface ISearchDocumentRepository - { - Task> GetSearchDocumentsAsync(SearchQuery searchQuery); - SearchDocument AddSearchDocument(SearchDocument searchDocument); - void DeleteSearchDocument(int searchDocumentId); - void DeleteSearchDocument(string indexerName, int entryId); - void DeleteSearchDocument(string uniqueKey); - void DeleteAllSearchDocuments(); - } -} diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs new file mode 100644 index 00000000..c1577d81 --- /dev/null +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Oqtane.Models; +using Oqtane.Shared; + +namespace Oqtane.Repository +{ + public class SearchContentRepository : ISearchContentRepository + { + private readonly IDbContextFactory _dbContextFactory; + + public SearchContentRepository(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public async Task> GetSearchContentListAsync(SearchQuery searchQuery) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchContentList = db.SearchContent.AsNoTracking() + .Include(i => i.Properties) + .Where(i => i.SiteId == searchQuery.SiteId && i.IsActive); + + if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) + { + searchContentList = searchContentList.Where(i => searchQuery.EntityNames.Contains(i.EntityName)); + } + + if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) + { + searchContentList = searchContentList.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); + } + + if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) + { + searchContentList = searchContentList.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); + } + + if (searchQuery.Properties != null && searchQuery.Properties.Any()) + { + foreach (var property in searchQuery.Properties) + { + searchContentList = searchContentList.Where(i => i.Properties.Any(p => p.Name == property.Key && p.Value == property.Value)); + } + } + + var filteredContentList = new List(); + if (!string.IsNullOrEmpty(searchQuery.Keywords)) + { + foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + { + filteredContentList.AddRange(await searchContentList.Where(i => i.Words.Any(w => w.WordSource.Word.StartsWith(keyword))).ToListAsync()); + } + } + + return filteredContentList.DistinctBy(i => i.UniqueKey); + } + + public SearchContent AddSearchContent(SearchContent searchContent) + { + using var context = _dbContextFactory.CreateDbContext(); + context.SearchContent.Add(searchContent); + + if(searchContent.Properties != null && searchContent.Properties.Any()) + { + foreach(var property in searchContent.Properties) + { + property.SearchContentId = searchContent.SearchContentId; + context.SearchContentProperty.Add(property); + } + } + + context.SaveChanges(); + + return searchContent; + } + + public void DeleteSearchContent(int searchContentId) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchContent = db.SearchContent.Find(searchContentId); + db.SearchContent.Remove(searchContent); + db.SaveChanges(); + } + + public void DeleteSearchContent(string entityName, int entryId) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchContent = db.SearchContent.FirstOrDefault(i => i.EntityName == entityName && i.EntityId == entryId); + if(searchContent != null) + { + db.SearchContent.Remove(searchContent); + db.SaveChanges(); + } + } + + public void DeleteSearchContent(string uniqueKey) + { + using var db = _dbContextFactory.CreateDbContext(); + var searchContent = db.SearchContent.FirstOrDefault(i => (i.EntityName + ":" + i.EntityId) == uniqueKey); + if (searchContent != null) + { + db.SearchContent.Remove(searchContent); + db.SaveChanges(); + } + } + + public void DeleteAllSearchContent() + { + using var db = _dbContextFactory.CreateDbContext(); + db.SearchContent.RemoveRange(db.SearchContent); + db.SaveChanges(); + } + + public SearchContentWordSource GetSearchContentWordSource(string word) + { + if(string.IsNullOrEmpty(word)) + { + return null; + } + + using var db = _dbContextFactory.CreateDbContext(); + return db.SearchContentWordSource.FirstOrDefault(i => i.Word == word); + } + + public SearchContentWordSource AddSearchContentWordSource(SearchContentWordSource wordSource) + { + using var db = _dbContextFactory.CreateDbContext(); + + db.SearchContentWordSource.Add(wordSource); + db.SaveChanges(); + + return wordSource; + } + + public IEnumerable GetWords(int searchContentId) + { + using var db = _dbContextFactory.CreateDbContext(); + return db.SearchContentWords + .Include(i => i.WordSource) + .Where(i => i.SearchContentId == searchContentId).ToList(); + } + + public SearchContentWords AddSearchContentWords(SearchContentWords word) + { + using var db = _dbContextFactory.CreateDbContext(); + + db.SearchContentWords.Add(word); + db.SaveChanges(); + + return word; + } + + public SearchContentWords UpdateSearchContentWords(SearchContentWords word) + { + using var db = _dbContextFactory.CreateDbContext(); + + db.Entry(word).State = EntityState.Modified; + db.SaveChanges(); + + return word; + } + } +} diff --git a/Oqtane.Server/Repository/SearchDocumentRepository.cs b/Oqtane.Server/Repository/SearchDocumentRepository.cs deleted file mode 100644 index bdceb52e..00000000 --- a/Oqtane.Server/Repository/SearchDocumentRepository.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Oqtane.Models; -using Oqtane.Shared; - -namespace Oqtane.Repository -{ - public class SearchDocumentRepository : ISearchDocumentRepository - { - private readonly IDbContextFactory _dbContextFactory; - - public SearchDocumentRepository(IDbContextFactory dbContextFactory) - { - _dbContextFactory = dbContextFactory; - } - - public async Task> GetSearchDocumentsAsync(SearchQuery searchQuery) - { - using var db = _dbContextFactory.CreateDbContext(); - var documents = db.SearchDocument.AsNoTracking() - .Include(i => i.Properties) - .Include(i => i.Tags) - .Where(i => i.SiteId == searchQuery.SiteId); - - if (searchQuery.Sources != null && searchQuery.Sources.Any()) - { - documents = documents.Where(i => searchQuery.Sources.Contains(i.IndexerName)); - } - - if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) - { - documents = documents.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); - } - - if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) - { - documents = documents.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); - } - - if (searchQuery.Tags != null && searchQuery.Tags.Any()) - { - foreach (var tag in searchQuery.Tags) - { - documents = documents.Where(i => i.Tags.Any(t => t.Tag == tag)); - } - } - - if (searchQuery.Properties != null && searchQuery.Properties.Any()) - { - foreach (var property in searchQuery.Properties) - { - documents = documents.Where(i => i.Properties.Any(p => p.Name == property.Key && p.Value == property.Value)); - } - } - - var filteredDocuments = new List(); - if (!string.IsNullOrEmpty(searchQuery.Keywords)) - { - foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) - { - filteredDocuments.AddRange(await documents.Where(i => i.Title.Contains(keyword) || i.Description.Contains(keyword) || i.Body.Contains(keyword)).ToListAsync()); - } - } - - return filteredDocuments.DistinctBy(i => i.UniqueKey); - } - - public SearchDocument AddSearchDocument(SearchDocument searchDocument) - { - using var context = _dbContextFactory.CreateDbContext(); - context.SearchDocument.Add(searchDocument); - - if(searchDocument.Properties != null && searchDocument.Properties.Any()) - { - foreach(var property in searchDocument.Properties) - { - property.SearchDocumentId = searchDocument.SearchDocumentId; - context.SearchDocumentProperty.Add(property); - } - } - - if (searchDocument.Tags != null && searchDocument.Tags.Any()) - { - foreach (var tag in searchDocument.Tags) - { - tag.SearchDocumentId = searchDocument.SearchDocumentId; - context.SearchDocumentTag.Add(tag); - } - } - - context.SaveChanges(); - - return searchDocument; - } - - public void DeleteSearchDocument(int searchDocumentId) - { - using var db = _dbContextFactory.CreateDbContext(); - var searchDocument = db.SearchDocument.Find(searchDocumentId); - db.SearchDocument.Remove(searchDocument); - db.SaveChanges(); - } - - public void DeleteSearchDocument(string indexerName, int entryId) - { - using var db = _dbContextFactory.CreateDbContext(); - var searchDocument = db.SearchDocument.FirstOrDefault(i => i.IndexerName == indexerName && i.EntryId == entryId); - if(searchDocument != null) - { - db.SearchDocument.Remove(searchDocument); - db.SaveChanges(); - } - } - - public void DeleteSearchDocument(string uniqueKey) - { - using var db = _dbContextFactory.CreateDbContext(); - var searchDocument = db.SearchDocument.FirstOrDefault(i => (i.IndexerName + ":" + i.EntryId) == uniqueKey); - if (searchDocument != null) - { - db.SearchDocument.Remove(searchDocument); - db.SaveChanges(); - } - } - - public void DeleteAllSearchDocuments() - { - using var db = _dbContextFactory.CreateDbContext(); - db.SearchDocument.RemoveRange(db.SearchDocument); - db.SaveChanges(); - } - } -} diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index caa9fce6..8fd9aab0 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -1,23 +1,30 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; +using HtmlAgilityPack; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository; +using Oqtane.Security; using Oqtane.Shared; namespace Oqtane.Services { public class SearchService : ISearchService { + private const string SearchProviderSettingName = "SearchProvider"; + private const string SearchEnabledSettingName = "SearchEnabled"; + private readonly IServiceProvider _serviceProvider; private readonly ITenantManager _tenantManager; private readonly IAliasRepository _aliasRepository; private readonly ISettingRepository _settingRepository; + private readonly IPermissionRepository _permissionRepository; private readonly ILogger _logger; private readonly IMemoryCache _cache; @@ -26,12 +33,14 @@ namespace Oqtane.Services ITenantManager tenantManager, IAliasRepository aliasRepository, ISettingRepository settingRepository, + IPermissionRepository permissionRepository, ILogger logger, IMemoryCache cache) { _tenantManager = tenantManager; _aliasRepository = aliasRepository; _settingRepository = settingRepository; + _permissionRepository = permissionRepository; _serviceProvider = serviceProvider; _logger = logger; _cache = cache; @@ -68,8 +77,8 @@ namespace Oqtane.Services { _logger.LogDebug($"Search: Begin Index {searchIndexManager.Name}"); - var count = searchIndexManager.IndexDocuments(siteId, startTime, SaveIndexDocuments, handleError); - logNote($"Search: Indexer {searchIndexManager.Name} processed {count} documents.
"); + var count = searchIndexManager.IndexContent(siteId, startTime, SaveSearchContent, handleError); + logNote($"Search: Indexer {searchIndexManager.Name} processed {count} search content.
"); _logger.LogDebug($"Search: End Index {searchIndexManager.Name}"); } @@ -79,7 +88,7 @@ namespace Oqtane.Services public async Task SearchAsync(SearchQuery searchQuery) { var searchProvider = GetSearchProvider(searchQuery.SiteId); - var searchResults = await searchProvider.SearchAsync(searchQuery, HasViewPermission); + var searchResults = await searchProvider.SearchAsync(searchQuery, Visible); //generate the document url if it's not set. foreach (var result in searchResults.Results) @@ -108,7 +117,7 @@ namespace Oqtane.Services private string GetSearchProviderSetting(int siteId) { - var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchProviderSettingName); + var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, SearchProviderSettingName); if(!string.IsNullOrEmpty(setting?.SettingValue)) { return setting.SettingValue; @@ -119,7 +128,7 @@ namespace Oqtane.Services private bool SearchEnabled(int siteId) { - var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, Constants.SearchEnabledSettingName); + var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, SearchEnabledSettingName); if (!string.IsNullOrEmpty(setting?.SettingValue)) { return bool.TryParse(setting.SettingValue, out bool enabled) && enabled; @@ -167,21 +176,22 @@ namespace Oqtane.Services return managers.ToList(); } - private void SaveIndexDocuments(IList searchDocuments) + private void SaveSearchContent(IList searchContentList) { - if(searchDocuments.Any()) + if(searchContentList.Any()) { - var searchProvider = GetSearchProvider(searchDocuments.First().SiteId); + var searchProvider = GetSearchProvider(searchContentList.First().SiteId); - foreach (var searchDocument in searchDocuments) + foreach (var searchContent in searchContentList) { try { - searchProvider.SaveDocument(searchDocument); + CleanSearchContent(searchContent); + searchProvider.SaveSearchContent(searchContent); } catch(Exception ex) { - _logger.LogError(ex, $"Search: Save search document {searchDocument.UniqueKey} failed."); + _logger.LogError(ex, $"Search: Save search content {searchContent.UniqueKey} failed."); } } @@ -190,19 +200,30 @@ namespace Oqtane.Services } } - private bool HasViewPermission(SearchDocument searchDocument, SearchQuery searchQuery) + private bool Visible(SearchContent searchContent, SearchQuery searchQuery) { - var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == searchDocument.IndexerName); + if(!HasViewPermission(searchQuery.SiteId, searchQuery.User, searchContent.EntityName, searchContent.EntityId)) + { + return false; + } + + var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == searchContent.EntityName); if (searchResultManager != null) { - return searchResultManager.Visible(searchDocument, searchQuery); + return searchResultManager.Visible(searchContent, searchQuery); } return true; } + private bool HasViewPermission(int siteId, User user, string entityName, int entityId) + { + var permissions = _permissionRepository.GetPermissions(siteId, entityName, entityId).ToList(); + return UserSecurity.IsAuthorized(user, PermissionNames.View, permissions); + } + private string GetDocumentUrl(SearchResult result, SearchQuery searchQuery) { - var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == result.IndexerName); + var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == result.EntityName); if(searchResultManager != null) { return searchResultManager.GetUrl(result, searchQuery); @@ -210,5 +231,35 @@ namespace Oqtane.Services return string.Empty; } + + private void CleanSearchContent(SearchContent searchContent) + { + searchContent.Title = GetCleanContent(searchContent.Title); + searchContent.Description = GetCleanContent(searchContent.Description); + searchContent.Body = GetCleanContent(searchContent.Body); + searchContent.AdditionalContent = GetCleanContent(searchContent.AdditionalContent); + } + + private string GetCleanContent(string content) + { + if(string.IsNullOrWhiteSpace(content)) + { + return string.Empty; + } + + content = WebUtility.HtmlDecode(content); + + var page = new HtmlDocument(); + page.LoadHtml(content); + + var phrases = page.DocumentNode.Descendants().Where(i => + i.NodeType == HtmlNodeType.Text && + i.ParentNode.Name != "script" && + i.ParentNode.Name != "style" && + !string.IsNullOrEmpty(i.InnerText.Trim()) + ).Select(i => i.InnerText); + + return string.Join(" ", phrases); + } } } diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css similarity index 100% rename from Oqtane.Server/wwwroot/Modules/Oqtane.Modules.SearchResults/Module.css rename to Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css diff --git a/Oqtane.Shared/Interfaces/ISearchIndexManager.cs b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs index 9a322ab5..0a4123f8 100644 --- a/Oqtane.Shared/Interfaces/ISearchIndexManager.cs +++ b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs @@ -15,6 +15,6 @@ namespace Oqtane.Services bool IsIndexEnabled(int siteId); - int IndexDocuments(int siteId, DateTime? startTime, Action> processSearchDocuments, Action handleError); + int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError); } } diff --git a/Oqtane.Shared/Interfaces/ISearchProvider.cs b/Oqtane.Shared/Interfaces/ISearchProvider.cs index e1e9fb16..cb075d3c 100644 --- a/Oqtane.Shared/Interfaces/ISearchProvider.cs +++ b/Oqtane.Shared/Interfaces/ISearchProvider.cs @@ -11,11 +11,11 @@ namespace Oqtane.Services { string Name { get; } - void SaveDocument(SearchDocument document, bool autoCommit = false); + void SaveSearchContent(SearchContent searchContent, bool autoCommit = false); - void DeleteDocument(string id); + void DeleteSearchContent(string id); - Task SearchAsync(SearchQuery searchQuery, Func validateFunc); + Task SearchAsync(SearchQuery searchQuery, Func validateFunc); bool Optimize(); diff --git a/Oqtane.Shared/Interfaces/ISearchResultManager.cs b/Oqtane.Shared/Interfaces/ISearchResultManager.cs index 2dc459b0..b6f6cd32 100644 --- a/Oqtane.Shared/Interfaces/ISearchResultManager.cs +++ b/Oqtane.Shared/Interfaces/ISearchResultManager.cs @@ -7,7 +7,7 @@ namespace Oqtane.Services { string Name { get; } - bool Visible(SearchDocument searchResult, SearchQuery searchQuery); + bool Visible(SearchContent searchResult, SearchQuery searchQuery); string GetUrl(SearchResult searchResult, SearchQuery searchQuery); } diff --git a/Oqtane.Shared/Interfaces/IModuleSearch.cs b/Oqtane.Shared/Interfaces/ISearchable.cs similarity index 58% rename from Oqtane.Shared/Interfaces/IModuleSearch.cs rename to Oqtane.Shared/Interfaces/ISearchable.cs index af94a300..c0a4c00e 100644 --- a/Oqtane.Shared/Interfaces/IModuleSearch.cs +++ b/Oqtane.Shared/Interfaces/ISearchable.cs @@ -7,8 +7,8 @@ using Oqtane.Models; namespace Oqtane.Interfaces { - public interface IModuleSearch + public interface ISearchable { - public IList GetSearchDocuments(Module module, DateTime startTime); + public IList GetSearchContentList(Module module, DateTime startTime); } } diff --git a/Oqtane.Shared/Models/SearchDocument.cs b/Oqtane.Shared/Models/SearchContent.cs similarity index 57% rename from Oqtane.Shared/Models/SearchDocument.cs rename to Oqtane.Shared/Models/SearchContent.cs index 67161b3b..36f4c664 100644 --- a/Oqtane.Shared/Models/SearchDocument.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -4,16 +4,16 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; namespace Oqtane.Models { - public class SearchDocument : ModelBase + public class SearchContent : ModelBase { - public int SearchDocumentId { get; set; } + public int SearchContentId { get; set; } [NotMapped] - public string UniqueKey => $"{IndexerName}:{EntryId}"; + public string UniqueKey => $"{EntityName}:{EntityId}"; - public int EntryId { get; set; } + public string EntityName { get; set; } - public string IndexerName { get; set; } + public int EntityId { get; set; } public int SiteId { get; set; } @@ -27,15 +27,13 @@ namespace Oqtane.Models public DateTime ModifiedTime { get; set; } - public bool IsActive { get; set; } + public bool IsActive { get; set; } = true; public string AdditionalContent { get; set; } - public string LanguageCode { get; set; } + public IList Properties { get; set; } - public IList Tags { get; set; } - - public IList Properties { get; set; } + public IList Words { get; set; } public override string ToString() { diff --git a/Oqtane.Shared/Models/SearchDocumentProperty.cs b/Oqtane.Shared/Models/SearchContentProperty.cs similarity index 74% rename from Oqtane.Shared/Models/SearchDocumentProperty.cs rename to Oqtane.Shared/Models/SearchContentProperty.cs index 4febae43..0c2b7f9b 100644 --- a/Oqtane.Shared/Models/SearchDocumentProperty.cs +++ b/Oqtane.Shared/Models/SearchContentProperty.cs @@ -3,12 +3,12 @@ using Microsoft.EntityFrameworkCore; namespace Oqtane.Models { - public class SearchDocumentProperty + public class SearchContentProperty { [Key] public int PropertyId { get; set; } - public int SearchDocumentId { get; set; } + public int SearchContentId { get; set; } public string Name { get; set; } diff --git a/Oqtane.Shared/Models/SearchContentWordSource.cs b/Oqtane.Shared/Models/SearchContentWordSource.cs new file mode 100644 index 00000000..ec814757 --- /dev/null +++ b/Oqtane.Shared/Models/SearchContentWordSource.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Oqtane.Models +{ + public class SearchContentWordSource + { + [Key] + public int WordSourceId { get; set; } + + public string Word { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchContentWords.cs b/Oqtane.Shared/Models/SearchContentWords.cs new file mode 100644 index 00000000..c7630815 --- /dev/null +++ b/Oqtane.Shared/Models/SearchContentWords.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Oqtane.Models +{ + public class SearchContentWords + { + [Key] + public int WordId { get; set; } + + public int SearchContentId { get; set; } + + public int WordSourceId { get; set; } + + public int Count { get; set; } + + public SearchContentWordSource WordSource { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchDocumentTag.cs b/Oqtane.Shared/Models/SearchDocumentTag.cs deleted file mode 100644 index ee929efc..00000000 --- a/Oqtane.Shared/Models/SearchDocumentTag.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Oqtane.Models -{ - public class SearchDocumentTag - { - [Key] - public int TagId { get; set; } - - public int SearchDocumentId { get; set; } - - public string Tag { get; set; } - } -} diff --git a/Oqtane.Shared/Models/SearchQuery.cs b/Oqtane.Shared/Models/SearchQuery.cs index f148252d..cf03e8c1 100644 --- a/Oqtane.Shared/Models/SearchQuery.cs +++ b/Oqtane.Shared/Models/SearchQuery.cs @@ -14,14 +14,12 @@ namespace Oqtane.Models public string Keywords { get; set; } - public IList Sources { get; set; } = new List(); + public IList EntityNames { get; set; } = new List(); public DateTime BeginModifiedTimeUtc { get; set; } public DateTime EndModifiedTimeUtc { get; set; } - public IList Tags { get; set; } = new List(); - public IDictionary Properties { get; set; } = new Dictionary(); public int PageIndex { get; set; } diff --git a/Oqtane.Shared/Models/SearchResult.cs b/Oqtane.Shared/Models/SearchResult.cs index d13cafa1..dd878d42 100644 --- a/Oqtane.Shared/Models/SearchResult.cs +++ b/Oqtane.Shared/Models/SearchResult.cs @@ -1,6 +1,6 @@ namespace Oqtane.Models { - public class SearchResult : SearchDocument + public class SearchResult : SearchContent { public float Score { get; set; } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index f1a14cd4..979f0872 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -77,22 +77,10 @@ namespace Oqtane.Shared public static readonly string VisitorCookiePrefix = "APP_VISITOR_"; - public const string SearchIndexManagerEnabledSettingFormat = "SearchIndexManager_{0}_Enabled"; - public const string SearchIndexStartTimeSettingName = "SearchIndex_StartTime"; - public const string SearchResultManagersCacheName = "SearchResultManagers"; - public const int SearchDefaultPageSize = 10; + public const string DefaultSearchProviderName = "Database"; public const string SearchPageIdPropertyName = "PageId"; public const string SearchModuleIdPropertyName = "ModuleId"; - public const string DefaultSearchProviderName = "Database"; - public const string SearchProviderSettingName = "SearchProvider"; - public const string SearchEnabledSettingName = "SearchEnabled"; - - public const string ModuleSearchIndexManagerName = "Module"; - public const string PageSearchIndexManagerName = "Page"; - - public const int PageSearchIndexManagerPriority = 100; - public const int ModuleSearchIndexManagerPriority = 200; - + // Obsolete constants const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames"; diff --git a/Oqtane.Shared/Shared/SearchUtils.cs b/Oqtane.Shared/Shared/SearchUtils.cs index b7116d4a..745770a5 100644 --- a/Oqtane.Shared/Shared/SearchUtils.cs +++ b/Oqtane.Shared/Shared/SearchUtils.cs @@ -1,40 +1,14 @@ -using System.Collections; using System.Collections.Generic; -using System.Net; -using System.Text.RegularExpressions; namespace Oqtane.Shared { public sealed class SearchUtils { - private const string PunctuationMatch = "[~!#\\$%\\^&*\\(\\)-+=\\{\\[\\}\\]\\|;:\\x22'<,>\\.\\?\\\\\\t\\r\\v\\f\\n]"; - private static readonly Regex _stripWhiteSpaceRegex = new Regex("\\s+", RegexOptions.Compiled); - private static readonly Regex _stripTagsRegex = new Regex("<[^<>]*>", RegexOptions.Compiled); - private static readonly Regex _afterRegEx = new Regex(PunctuationMatch + "\\s", RegexOptions.Compiled); - private static readonly Regex _beforeRegEx = new Regex("\\s" + PunctuationMatch, RegexOptions.Compiled); + private static readonly IList _systemPages; - public static string Clean(string html, bool removePunctuation) + static SearchUtils() { - if (string.IsNullOrWhiteSpace(html)) - { - return string.Empty; - } - - if (html.Contains("<")) - { - html = WebUtility.HtmlDecode(html); - } - - html = StripTags(html, true); - html = WebUtility.HtmlDecode(html); - - if (removePunctuation) - { - html = StripPunctuation(html, true); - html = StripWhiteSpace(html, true); - } - - return html; + _systemPages = new List { "login", "register", "profile", "404", "search" }; } public static IList GetKeywordsList(string keywords) @@ -54,42 +28,9 @@ namespace Oqtane.Shared return keywordsList; } - private static string StripTags(string html, bool retainSpace) + public static bool IsSystemPage(Models.Page page) { - return _stripTagsRegex.Replace(html, retainSpace ? " " : string.Empty); - } - - private static string StripPunctuation(string html, bool retainSpace) - { - if (string.IsNullOrWhiteSpace(html)) - { - return string.Empty; - } - - string retHTML = html + " "; - - var repString = retainSpace ? " " : string.Empty; - while (_beforeRegEx.IsMatch(retHTML)) - { - retHTML = _beforeRegEx.Replace(retHTML, repString); - } - - while (_afterRegEx.IsMatch(retHTML)) - { - retHTML = _afterRegEx.Replace(retHTML, repString); - } - - return retHTML.Trim('"'); - } - - private static string StripWhiteSpace(string html, bool retainSpace) - { - if (string.IsNullOrWhiteSpace(html)) - { - return string.Empty; - } - - return _stripWhiteSpaceRegex.Replace(html, retainSpace ? " " : string.Empty); + return page.Path.Contains("admin") || _systemPages.Contains(page.Path); } } } From 790fc88e47516bea45bd9f5ac873914e224a57c5 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Jun 2024 17:50:29 +0800 Subject: [PATCH 124/189] using correct module id value. --- Oqtane.Client/Modules/Admin/SearchResults/Index.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index 2cbc3098..3b25090e 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -127,7 +127,7 @@ PageSize = int.MaxValue }; - _searchResults = await SearchResultsService.SearchAsync(PageState.ModuleId, searchQuery); + _searchResults = await SearchResultsService.SearchAsync(ModuleState.ModuleId, searchQuery); _loading = false; StateHasChanged(); From 8048788042cc99a983b0b2b30dec5088b3ccebfe Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 4 Jun 2024 12:21:59 +0200 Subject: [PATCH 125/189] Oqtane controls updates ActionDialog and ActionLink now allow other icon sets whilst still adhering to the legacy "oi oi-" icon set. Pager added SearchBoxClass Class parameter to the Search div. TabStrip added a TabContentClass Class parameter to the tab content div. --- Oqtane.Client/Modules/Controls/ActionDialog.razor | 5 ++++- Oqtane.Client/Modules/Controls/ActionLink.razor | 5 ++++- Oqtane.Client/Modules/Controls/Pager.razor | 7 +++++-- Oqtane.Client/Modules/Controls/TabStrip.razor | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor index defb520d..b2308d3a 100644 --- a/Oqtane.Client/Modules/Controls/ActionDialog.razor +++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor @@ -181,7 +181,10 @@ else _openText = string.Empty; } - if (!IconName.Contains(" ")) + // Check if IconName starts with "oi oi-" + bool startsWithOiOi = IconName.StartsWith("oi oi-"); + + if (!startsWithOiOi && !IconName.Contains(" ")) { IconName = "oi oi-" + IconName; } diff --git a/Oqtane.Client/Modules/Controls/ActionLink.razor b/Oqtane.Client/Modules/Controls/ActionLink.razor index 9517e275..545dca5c 100644 --- a/Oqtane.Client/Modules/Controls/ActionLink.razor +++ b/Oqtane.Client/Modules/Controls/ActionLink.razor @@ -145,7 +145,10 @@ if (!string.IsNullOrEmpty(IconName)) { - if (!IconName.Contains(" ")) + // Check if IconName starts with "oi oi-" + bool startsWithOiOi = IconName.StartsWith("oi oi-"); + + if (!startsWithOiOi && !IconName.Contains(" ")) { IconName = "oi oi-" + IconName; } diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor index 8e643f17..ce14eed6 100644 --- a/Oqtane.Client/Modules/Controls/Pager.razor +++ b/Oqtane.Client/Modules/Controls/Pager.razor @@ -11,7 +11,7 @@ @if (!string.IsNullOrEmpty(SearchProperties)) {
-
+
@@ -74,7 +74,7 @@ { -
+
@SharedLocalizer["Reset"] @@ -359,6 +359,9 @@ [Parameter] public string SearchProperties { get; set; } // comma delimited list of property names to include in search + [Parameter] + public string SearchBoxClass { get; set; } // class for Search box + [Parameter] public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode diff --git a/Oqtane.Client/Modules/Controls/TabStrip.razor b/Oqtane.Client/Modules/Controls/TabStrip.razor index 90b3948a..e2a3c0f1 100644 --- a/Oqtane.Client/Modules/Controls/TabStrip.razor +++ b/Oqtane.Client/Modules/Controls/TabStrip.razor @@ -23,7 +23,7 @@ } -
+

@ChildContent
@@ -47,6 +47,9 @@ [Parameter] public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided) + [Parameter] + public string TabContentClass { get; set; } // optional - to extend the TabContent div. + protected override void OnInitialized() { if (string.IsNullOrEmpty(Id)) From d9d917e267e675cae448c41fba1050fbc500005e Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Jun 2024 21:09:25 +0800 Subject: [PATCH 126/189] set search result page path to be parameter. --- Oqtane.Client/Themes/Controls/Theme/Search.razor | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/Search.razor b/Oqtane.Client/Themes/Controls/Theme/Search.razor index fc4980ef..a782bdaf 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Search.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Search.razor @@ -26,17 +26,21 @@ @code { - private const string SearchResultPagePath = "search"; - private Page _searchResultsPage; private string _keywords = ""; [Parameter] public string CssClass { get; set; } + [Parameter] + public string SearchResultPagePath { get; set; } = "search"; + protected override void OnInitialized() { - _searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath); + if(!string.IsNullOrEmpty(SearchResultPagePath)) + { + _searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath); + } } protected override void OnParametersSet() From e1cdc7b387257be0674c500daa4d57e7da85b394 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 4 Jun 2024 21:57:50 +0800 Subject: [PATCH 127/189] return the words count to calculate the ranking. --- .../Providers/DatabaseSearchProvider.cs | 77 +++++++++++-------- .../Repository/SearchContentRepository.cs | 2 + Oqtane.Server/Services/SearchService.cs | 31 -------- Oqtane.Shared/Shared/SearchUtils.cs | 2 +- 4 files changed, 48 insertions(+), 64 deletions(-) diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index 74884934..adf2cee8 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -18,10 +18,6 @@ namespace Oqtane.Providers { private readonly ISearchContentRepository _searchContentRepository; - private const float TitleBoost = 100f; - private const float DescriptionBoost = 10f; - private const float BodyBoost = 10f; - private const float AdditionalContentBoost = 5f; private const string IgnoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us"; private const int WordMinLength = 3; public string Name => Constants.DefaultSearchProviderName; @@ -55,6 +51,9 @@ namespace Oqtane.Providers //remove exist document _searchContentRepository.DeleteSearchContent(searchContent.EntityName, searchContent.EntityId); + //clean the search content to remove html tags + CleanSearchContent(searchContent); + _searchContentRepository.AddSearchContent(searchContent); //save the index words @@ -152,10 +151,7 @@ namespace Oqtane.Providers var score = 0f; foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) { - score += Regex.Matches(searchContent.Title, keyword, RegexOptions.IgnoreCase).Count * TitleBoost; - score += Regex.Matches(searchContent.Description, keyword, RegexOptions.IgnoreCase).Count * DescriptionBoost; - score += Regex.Matches(searchContent.Body, keyword, RegexOptions.IgnoreCase).Count * BodyBoost; - score += Regex.Matches(searchContent.AdditionalContent, keyword, RegexOptions.IgnoreCase).Count * AdditionalContentBoost; + score += searchContent.Words.Where(i => i.WordSource.Word.StartsWith(keyword)).Sum(i => i.Count); } return score / 100; @@ -241,37 +237,24 @@ namespace Oqtane.Providers private static Dictionary GetWords(string content, int minLength) { - content = WebUtility.HtmlDecode(content); + content = FormatText(content); var words = new Dictionary(); var ignoreWords = IgnoreWords.Split(','); - var page = new HtmlDocument(); - page.LoadHtml(content); - - var phrases = page.DocumentNode.Descendants().Where(i => - i.NodeType == HtmlNodeType.Text && - i.ParentNode.Name != "script" && - i.ParentNode.Name != "style" && - !string.IsNullOrEmpty(i.InnerText.Trim()) - ).Select(i => FormatText(i.InnerText)); - - foreach (var phrase in phrases) + if (!string.IsNullOrEmpty(content)) { - if (!string.IsNullOrEmpty(phrase)) + foreach (var word in content.Split(' ')) { - foreach (var word in phrase.Split(' ')) + if (word.Length >= minLength && !ignoreWords.Contains(word)) { - if (word.Length >= minLength && !ignoreWords.Contains(word)) + if (!words.ContainsKey(word)) { - if (!words.ContainsKey(word)) - { - words.Add(word, 1); - } - else - { - words[word] += 1; - } + words.Add(word, 1); + } + else + { + words[word] += 1; } } } @@ -288,8 +271,38 @@ namespace Oqtane.Providers text = text.Replace(punctuation, ' '); } text = text.Replace(" ", " ").ToLower().Trim(); - return text; + return text; + } + + private void CleanSearchContent(SearchContent searchContent) + { + searchContent.Title = GetCleanContent(searchContent.Title); + searchContent.Description = GetCleanContent(searchContent.Description); + searchContent.Body = GetCleanContent(searchContent.Body); + searchContent.AdditionalContent = GetCleanContent(searchContent.AdditionalContent); + } + + private string GetCleanContent(string content) + { + if (string.IsNullOrWhiteSpace(content)) + { + return string.Empty; + } + + content = WebUtility.HtmlDecode(content); + + var page = new HtmlDocument(); + page.LoadHtml(content); + + var phrases = page.DocumentNode.Descendants().Where(i => + i.NodeType == HtmlNodeType.Text && + i.ParentNode.Name != "script" && + i.ParentNode.Name != "style" && + !string.IsNullOrEmpty(i.InnerText.Trim()) + ).Select(i => i.InnerText); + + return string.Join(" ", phrases); } } } diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index c1577d81..67ecfcf7 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -22,6 +22,8 @@ namespace Oqtane.Repository using var db = _dbContextFactory.CreateDbContext(); var searchContentList = db.SearchContent.AsNoTracking() .Include(i => i.Properties) + .Include(i => i.Words) + .ThenInclude(w => w.WordSource) .Where(i => i.SiteId == searchQuery.SiteId && i.IsActive); if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index 8fd9aab0..8d3fe4ef 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -186,7 +186,6 @@ namespace Oqtane.Services { try { - CleanSearchContent(searchContent); searchProvider.SaveSearchContent(searchContent); } catch(Exception ex) @@ -231,35 +230,5 @@ namespace Oqtane.Services return string.Empty; } - - private void CleanSearchContent(SearchContent searchContent) - { - searchContent.Title = GetCleanContent(searchContent.Title); - searchContent.Description = GetCleanContent(searchContent.Description); - searchContent.Body = GetCleanContent(searchContent.Body); - searchContent.AdditionalContent = GetCleanContent(searchContent.AdditionalContent); - } - - private string GetCleanContent(string content) - { - if(string.IsNullOrWhiteSpace(content)) - { - return string.Empty; - } - - content = WebUtility.HtmlDecode(content); - - var page = new HtmlDocument(); - page.LoadHtml(content); - - var phrases = page.DocumentNode.Descendants().Where(i => - i.NodeType == HtmlNodeType.Text && - i.ParentNode.Name != "script" && - i.ParentNode.Name != "style" && - !string.IsNullOrEmpty(i.InnerText.Trim()) - ).Select(i => i.InnerText); - - return string.Join(" ", phrases); - } } } diff --git a/Oqtane.Shared/Shared/SearchUtils.cs b/Oqtane.Shared/Shared/SearchUtils.cs index 745770a5..d49c932d 100644 --- a/Oqtane.Shared/Shared/SearchUtils.cs +++ b/Oqtane.Shared/Shared/SearchUtils.cs @@ -20,7 +20,7 @@ namespace Oqtane.Shared { if (!string.IsNullOrWhiteSpace(keyword.Trim())) { - keywordsList.Add(keyword.Trim()); + keywordsList.Add(keyword.Trim().ToLower()); } } } From 842b7b1402c523a61488591b9f55a45e9f98c5b7 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 5 Jun 2024 10:27:38 +0800 Subject: [PATCH 128/189] Fix #4309: make searchbox responsive. --- .../Themes/BlazorTheme/Themes/Default.razor | 9 +++- .../Themes/Controls/Theme/Search.razor | 5 ++- .../Themes/OqtaneTheme/Themes/Default.razor | 2 +- .../Oqtane.Themes.BlazorTheme/Theme.css | 43 +++++++++++++++++++ .../Oqtane.Themes.OqtaneTheme/Theme.css | 42 ++++++++++++++++++ 5 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index 2e0da4d3..9f3b8cd9 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -11,13 +11,18 @@
-
+
+ + + + +
diff --git a/Oqtane.Client/Themes/Controls/Theme/Search.razor b/Oqtane.Client/Themes/Controls/Theme/Search.razor index a782bdaf..54baac45 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Search.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Search.razor @@ -9,7 +9,7 @@ @if (_searchResultsPage != null) { - + - +
diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css index 266adcc8..481fa649 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css @@ -121,6 +121,49 @@ .main .top-row { display: none; } + + .app-search { + border-radius: 6px; + } + .app-search input{ + display: none !important; + } + + .app-search input + button{ + position: initial; + color: #fff; + padding-top: 7px; + padding-bottom: 7px; + } + + .app-search:active, .app-search:hover { + display: block; + position: fixed; + top: 0; + min-height: 60px; + width: 100%; + left: 0; + z-index: 999; + border-radius: 0; + } + + .app-search:active .app-form-inline, .app-search:hover .app-form-inline{ + margin: 10px auto; + position: relative; + display: block; + max-width: 80%; + } + + .app-search:active .app-form-inline input, .app-search:hover .app-form-inline input{ + width: 100%; + display: block !important; + } + .app-search:active .app-form-inline input + button, .app-search:hover .app-form-inline input + button{ + position: absolute; + color: rgb(42, 159, 214); + padding-top: 6px; + padding-bottom: 6px; + } } @media (min-width: 768px) { diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css index bbb65908..862165b4 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css @@ -130,4 +130,46 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu position: relative; top: 60px; } + .app-search { + border-radius: 6px; + } + .app-search input{ + display: none !important; + } + + .app-search input + button{ + position: initial; + color: #fff; + padding-top: 7px; + padding-bottom: 7px; + } + + .app-search:active, .app-search:hover { + display: block; + position: fixed; + top: 0; + min-height: 60px; + width: 100%; + left: 0; + z-index: 999; + border-radius: 0; + } + + .app-search:active .app-form-inline, .app-search:hover .app-form-inline{ + margin: 10px auto; + position: relative; + display: block; + max-width: 80%; + } + + .app-search:active .app-form-inline input, .app-search:hover .app-form-inline input{ + width: 100%; + display: block !important; + } + .app-search:active .app-form-inline input + button, .app-search:hover .app-form-inline input + button{ + position: absolute; + color: rgb(42, 159, 214); + padding-top: 6px; + padding-bottom: 6px; + } } From ac377a8b686a54aaa56b9a62f419e03291adb763 Mon Sep 17 00:00:00 2001 From: fonsecaf Date: Wed, 5 Jun 2024 13:39:31 +1000 Subject: [PATCH 129/189] Modified date parsing and formatting to use invariant culture, ensuring consistency and preventing non-ASCII characters in HTTP headers. --- Oqtane.Server/Components/App.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 03baf121..3c53c820 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -338,7 +338,7 @@ { var values = visitorCookieValue.Split('|'); int.TryParse(values[0], out _visitorId); - DateTime.TryParse(values[1], out expiry); + DateTime.TryParseExact(values[1], "M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out expiry); } else // legacy cookie format { @@ -425,7 +425,7 @@ Context.Response.Cookies.Append( visitorCookieName, - $"{_visitorId}|{expiry}", + $"{_visitorId}|{expiry.ToString("M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture)}", new CookieOptions() { Expires = DateTimeOffset.UtcNow.AddYears(10), From c125a7fe07c6936747d2688d9e9179fc4716d14b Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 6 Jun 2024 16:39:35 +0800 Subject: [PATCH 130/189] Fix #4316: add text editor interfaces. --- ...EditorInterop.cs => QuillEditorInterop.cs} | 4 +- .../Modules/Controls/QuillTextEditor.razor | 210 ++++++++++++ .../Modules/Controls/RichTextEditor.razor | 304 +++++++----------- .../Providers/QuillTextEditorProvider.cs | 11 + .../Modules/Controls/QuillTextEditor.resx | 129 ++++++++ .../OqtaneServiceCollectionExtensions.cs | 5 + Oqtane.Shared/Interfaces/ITextEditor.cs | 22 ++ .../Interfaces/ITextEditorProvider.cs | 18 ++ 8 files changed, 506 insertions(+), 197 deletions(-) rename Oqtane.Client/Modules/Controls/{RichTextEditorInterop.cs => QuillEditorInterop.cs} (97%) create mode 100644 Oqtane.Client/Modules/Controls/QuillTextEditor.razor create mode 100644 Oqtane.Client/Providers/QuillTextEditorProvider.cs create mode 100644 Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx create mode 100644 Oqtane.Shared/Interfaces/ITextEditor.cs create mode 100644 Oqtane.Shared/Interfaces/ITextEditorProvider.cs diff --git a/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs b/Oqtane.Client/Modules/Controls/QuillEditorInterop.cs similarity index 97% rename from Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs rename to Oqtane.Client/Modules/Controls/QuillEditorInterop.cs index 338f240a..ed53c4e7 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditorInterop.cs +++ b/Oqtane.Client/Modules/Controls/QuillEditorInterop.cs @@ -4,11 +4,11 @@ using System.Threading.Tasks; namespace Oqtane.Modules.Controls { - public class RichTextEditorInterop + public class QuillEditorInterop { private readonly IJSRuntime _jsRuntime; - public RichTextEditorInterop(IJSRuntime jsRuntime) + public QuillEditorInterop(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } diff --git a/Oqtane.Client/Modules/Controls/QuillTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillTextEditor.razor new file mode 100644 index 00000000..5bb183b2 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/QuillTextEditor.razor @@ -0,0 +1,210 @@ +@namespace Oqtane.Modules.Controls +@inherits ModuleControlBase +@implements ITextEditor +@inject IStringLocalizer Localizer + +
+ @if (_richfilemanager) + { + + +
+ } +
+ @if (AllowFileManagement) + { + + } + @if (_richfilemanager) + { + @((MarkupString)"  ") + + } +
+
+
+
+ @if (ToolbarContent != null) + { + @ToolbarContent + } + else + { + + + + + + + + + + + + + + + + + + + } +
+
+
+
+
+ +@code { + private bool _richfilemanager = false; + private FileManager _fileManager; + private string _message = string.Empty; + private ElementReference _editorElement; + private ElementReference _toolBar; + private QuillEditorInterop interop; + private int _editorIndex; + private string _richhtml = string.Empty; + private string _originalrichhtml = string.Empty; + private bool _initialized = false; + private bool _contentchanged = false; + + [Parameter] + public bool AllowFileManagement{ get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + [Parameter] + public string Placeholder { get; set; } + + [Parameter] + public string Theme { get; set; } + + [Parameter] + public string DebugLevel { get; set; } + + [Parameter] + public RenderFragment ToolbarContent { get; set; } + + public override List Resources { get; set; } = new List() + { + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body }, + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body }, + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body } + }; + + protected override void OnInitialized() + { + interop = new QuillEditorInterop(JSRuntime); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + await interop.CreateEditor( + _editorElement, + _toolBar, + ReadOnly, + Placeholder, + Theme, + DebugLevel); + + await interop.LoadEditorContent(_editorElement, _richhtml); + + // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified) + _originalrichhtml = await interop.GetHtml(_editorElement); + + _initialized = true; + } + else + { + if (_initialized) + { + if (_contentchanged) + { + // reload editor if Content passed to component has changed + await interop.LoadEditorContent(_editorElement, _richhtml); + _originalrichhtml = await interop.GetHtml(_editorElement); + } + else + { + // preserve changed content on re-render event + var richhtml = await interop.GetHtml(_editorElement); + if (richhtml != _richhtml) + { + _richhtml = richhtml; + await interop.LoadEditorContent(_editorElement, _richhtml); + } + } + } + } + + _contentchanged = false; + } + + public void Initialize(string content, bool updated) + { + _richhtml = content; + _contentchanged = updated; + } + + public async Task InsertRichImage() + { + _message = string.Empty; + if (_richfilemanager) + { + var file = _fileManager.GetFile(); + if (file != null) + { + await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex); + _richhtml = await interop.GetHtml(_editorElement); + _richfilemanager = false; + } + else + { + _message = Localizer["Message.Require.Image"]; + } + } + else + { + _editorIndex = await interop.GetCurrentCursor(_editorElement); + _richfilemanager = true; + } + StateHasChanged(); + } + + public void CloseRichFileManager() + { + _richfilemanager = false; + _message = string.Empty; + StateHasChanged(); + } + + public async Task GetContent() + { + var richhtml = await interop.GetHtml(_editorElement); + if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + { + // convert Quill's empty content to empty string + if (richhtml == "


") + { + richhtml = string.Empty; + } + return richhtml; + } + else + { + return null; + } + } +} diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 83986c35..907fff94 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -1,6 +1,9 @@ @using System.Text.RegularExpressions +@using Microsoft.AspNetCore.Components.Rendering +@using Microsoft.Extensions.DependencyInjection @namespace Oqtane.Modules.Controls @inherits ModuleControlBase +@inject IServiceProvider ServiceProvider @inject ISettingService SettingService @inject IStringLocalizer Localizer @@ -10,62 +13,7 @@ @if (AllowRichText) { - @if (_richfilemanager) - { - - -
- } -
- @if (AllowFileManagement) - { - - } - @if (_richfilemanager) - { - @((MarkupString)"  ") - - } -
-
-
-
- @if (ToolbarContent != null) - { - @ToolbarContent - } - else - { - - - - - - - - - - - - - - - - - - - } -
-
-
-
+ @_textEditorComponent
} @if (AllowRawHtml) @@ -103,26 +51,18 @@
@code { - private bool _initialized = false; - - private RichTextEditorInterop interop; - private FileManager _fileManager; private string _activetab = "Rich"; - private ElementReference _editorElement; - private ElementReference _toolBar; - private bool _richfilemanager = false; - private string _richhtml = string.Empty; - private string _originalrichhtml = string.Empty; - private bool _rawfilemanager = false; + private FileManager _fileManager; + private string _message = string.Empty; private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N"); private string _rawhtml = string.Empty; private string _originalrawhtml = string.Empty; - private string _message = string.Empty; - private bool _contentchanged = false; - private int _editorIndex; + private ITextEditorProvider _textEditorProvider; + private RenderFragment _textEditorComponent; + private ITextEditor _textEditor; [Parameter] public string Content { get; set; } @@ -152,96 +92,47 @@ [Parameter] public string DebugLevel { get; set; } = "info"; - public override List Resources => new List() - { - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body }, - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body }, - new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body } - }; + public override List Resources { get; set; } = new List(); - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - interop = new RichTextEditorInterop(JSRuntime); if (string.IsNullOrEmpty(Placeholder)) { Placeholder = Localizer["Placeholder"]; } + + if(AllowRichText) + { + _textEditorProvider = await GetTextEditorProvider(); + } } protected override void OnParametersSet() { - _richhtml = Content; _rawhtml = Content; _originalrawhtml = _rawhtml; // preserve for comparison later - _originalrichhtml = ""; - - if (Content != _originalrawhtml) - { - _contentchanged = true; // identifies when Content parameter has changed - } if (!AllowRichText) { _activetab = "Raw"; } + + _textEditorComponent = (builder) => + { + CreateTextEditor(builder); + }; } protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnAfterRenderAsync(firstRender); - - if (AllowRichText) + if(_textEditor != null) { - if (firstRender) - { - await interop.CreateEditor( - _editorElement, - _toolBar, - ReadOnly, - Placeholder, - Theme, - DebugLevel); - - await interop.LoadEditorContent(_editorElement, _richhtml); - - // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified) - _originalrichhtml = await interop.GetHtml(_editorElement); - - _initialized = true; - } - else - { - if (_initialized) - { - if (_contentchanged) - { - // reload editor if Content passed to component has changed - await interop.LoadEditorContent(_editorElement, _richhtml); - _originalrichhtml = await interop.GetHtml(_editorElement); - } - else - { - // preserve changed content on re-render event - var richhtml = await interop.GetHtml(_editorElement); - if (richhtml != _richhtml) - { - _richhtml = richhtml; - await interop.LoadEditorContent(_editorElement, _richhtml); - } - } - } - } - - _contentchanged = false; + _textEditor.Initialize(Content, Content != _originalrawhtml); } + + await base.OnAfterRenderAsync(firstRender); } - public void CloseRichFileManager() - { - _richfilemanager = false; - _message = string.Empty; - StateHasChanged(); - } public void CloseRawFileManager() { @@ -259,78 +150,101 @@ } else { - var richhtml = ""; - - if (AllowRichText) + var richhtml = string.Empty; + if (AllowRichText && _textEditor != null) { - richhtml = await interop.GetHtml(_editorElement); + richhtml = await _textEditor.GetContent(); } - if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + return richhtml != null ? richhtml : _originalrawhtml; + } + } + + + public async Task InsertRawImage() + { + _message = string.Empty; + if (_rawfilemanager) + { + var file = _fileManager.GetFile(); + if (file != null) { - // convert Quill's empty content to empty string - if (richhtml == "


") - { - richhtml = string.Empty; - } - return richhtml; + var interop = new Interop(JSRuntime); + int pos = await interop.GetCaretPosition(_rawhtmlid); + var image = "\"""; + _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); + _rawfilemanager = false; } else { - // return original raw html content - return _originalrawhtml; + _message = Localizer["Message.Require.Image"]; + } + } + else + { + _rawfilemanager = true; + } + StateHasChanged(); + } + + private void CreateTextEditor(RenderTreeBuilder builder) + { + if(_textEditorProvider != null) + { + var editorType = Type.GetType(_textEditorProvider.EditorType); + if (editorType != null) + { + builder.OpenComponent(0, editorType); + + //set editor parameters if available. + var attributes = new Dictionary + { + { "AllowFileManagement", AllowFileManagement }, + { "ReadOnly", ReadOnly }, + { "Placeholder", Placeholder }, + { "Theme", Theme }, + { "DebugLevel", DebugLevel }, + { "ToolbarContent", ToolbarContent } + }; + + var index = 1; + foreach(var name in attributes.Keys) + { + if (editorType.GetProperty(name) != null) + { + builder.AddAttribute(index++, name, attributes[name]); + } + } + + builder.AddComponentReferenceCapture(index, (c) => + { + _textEditor = (ITextEditor)c; + }); + builder.CloseComponent(); } } } - public async Task InsertRichImage() + private async Task GetTextEditorProvider() { - _message = string.Empty; - if (_richfilemanager) - { - var file = _fileManager.GetFile(); - if (file != null) - { - await interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex); - _richhtml = await interop.GetHtml(_editorElement); - _richfilemanager = false; - } - else - { - _message = Localizer["Message.Require.Image"]; - } - } - else - { - _editorIndex = await interop.GetCurrentCursor(_editorElement); - _richfilemanager = true; - } - StateHasChanged(); - } + const string DefaultEditorName = "Quill"; - public async Task InsertRawImage() - { - _message = string.Empty; - if (_rawfilemanager) - { - var file = _fileManager.GetFile(); - if (file != null) - { - var interop = new Interop(JSRuntime); - int pos = await interop.GetCaretPosition(_rawhtmlid); - var image = "\"""; - _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); - _rawfilemanager = false; - } - else - { - _message = Localizer["Message.Require.Image"]; - } - } - else - { - _rawfilemanager = true; - } - StateHasChanged(); - } + var editorName = await GetTextEditorName(DefaultEditorName); + var editorProviders = ServiceProvider.GetServices(); + var editorProvider = editorProviders.FirstOrDefault(i => i.Name == editorName); + if(editorProvider == null) + { + editorProvider = editorProviders.FirstOrDefault(i => i.Name == DefaultEditorName); + } + + return editorProvider; + } + + private async Task GetTextEditorName(string defaultName) + { + const string EditorSettingName = "TextEditor"; + + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + return SettingService.GetSetting(settings, EditorSettingName, defaultName); + } } diff --git a/Oqtane.Client/Providers/QuillTextEditorProvider.cs b/Oqtane.Client/Providers/QuillTextEditorProvider.cs new file mode 100644 index 00000000..d591a590 --- /dev/null +++ b/Oqtane.Client/Providers/QuillTextEditorProvider.cs @@ -0,0 +1,11 @@ +using Oqtane.Interfaces; + +namespace Oqtane.Providers +{ + public class QuillTextEditorProvider : ITextEditorProvider + { + public string Name => "Quill"; + + public string EditorType => "Oqtane.Modules.Controls.QuillTextEditor, Oqtane.Client"; + } +} diff --git a/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx new file mode 100644 index 00000000..5ac2f720 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Insert Image + + + Close + + + You Must Select An Image To Insert + + \ No newline at end of file diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index d018a4bc..0eaf05dd 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -19,6 +19,7 @@ using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Oqtane.Infrastructure; using Oqtane.Infrastructure.Interfaces; +using Oqtane.Interfaces; using Oqtane.Managers; using Oqtane.Modules; using Oqtane.Providers; @@ -101,6 +102,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + return services; } @@ -148,6 +150,9 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); + // providers + services.AddTransient(); + // obsolete - replaced by ITenantManager services.AddTransient(); diff --git a/Oqtane.Shared/Interfaces/ITextEditor.cs b/Oqtane.Shared/Interfaces/ITextEditor.cs new file mode 100644 index 00000000..8ebc32c4 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ITextEditor.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Oqtane.Interfaces +{ + /// + /// Text editor interface. + /// + public interface ITextEditor + { + /// + /// initializes the editor with the initialize content. + /// + /// the initialize content. + void Initialize(string content, bool updated); + + /// + /// get content from the editor. + /// + /// + Task GetContent(); + } +} diff --git a/Oqtane.Shared/Interfaces/ITextEditorProvider.cs b/Oqtane.Shared/Interfaces/ITextEditorProvider.cs new file mode 100644 index 00000000..a00c6817 --- /dev/null +++ b/Oqtane.Shared/Interfaces/ITextEditorProvider.cs @@ -0,0 +1,18 @@ +namespace Oqtane.Interfaces +{ + /// + /// Rich text editor provider interface. + /// + public interface ITextEditorProvider + { + /// + /// The text editor provider name. + /// + string Name { get; } + + /// + /// The text editor type full name. + /// + string EditorType { get; } + } +} From b00c2afc46a029d0fa8f299b214f5de2c455ceff Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 7 Jun 2024 08:35:42 +0800 Subject: [PATCH 131/189] Fix #4316: move the raw html editor into quill editor instance. --- .../Modules/Controls/QuillTextEditor.razor | 365 +++++++++++++----- .../Modules/Controls/RichTextEditor.razor | 148 ++----- .../Modules/Controls/QuillTextEditor.resx | 9 +- .../Modules/Controls/RichTextEditor.resx | 132 ------- Oqtane.Shared/Interfaces/ITextEditor.cs | 2 +- 5 files changed, 293 insertions(+), 363 deletions(-) delete mode 100644 Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx diff --git a/Oqtane.Client/Modules/Controls/QuillTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillTextEditor.razor index 5bb183b2..c64a5c6f 100644 --- a/Oqtane.Client/Modules/Controls/QuillTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillTextEditor.razor @@ -4,80 +4,132 @@ @inject IStringLocalizer Localizer
- @if (_richfilemanager) - { - - -
- } -
- @if (AllowFileManagement) + + @if (AllowRichText) { - - } - @if (_richfilemanager) - { - @((MarkupString)"  ") - - } -
-
-
-
- @if (ToolbarContent != null) + + @if (_richfilemanager) { - @ToolbarContent + + +
+ } +
+ @if (AllowFileManagement) + { + + } + @if (_richfilemanager) + { + @((MarkupString)"  ") + + } +
+
+
+
+ @if (ToolbarContent != null) + { + @ToolbarContent + } + else + { + + + + + + + + + + + + + + + + + + + } +
+
+
+
+
+ } + @if (AllowRawHtml) + { + + @if (_rawfilemanager) + { + + +
+ } +
+ @if (AllowFileManagement) + { + + } + @if (_rawfilemanager) + { + @((MarkupString)"  ") + + } +
+ @if (ReadOnly) + { + } else { - - - - - - - - - - - - - - - - - - + } -
-
-
-
+ + } +
@code { - private bool _richfilemanager = false; + private bool _initialized = false; + + private QuillEditorInterop interop; private FileManager _fileManager; - private string _message = string.Empty; + private string _activetab = "Rich"; + private ElementReference _editorElement; private ElementReference _toolBar; - private QuillEditorInterop interop; - private int _editorIndex; + private bool _richfilemanager = false; private string _richhtml = string.Empty; private string _originalrichhtml = string.Empty; - private bool _initialized = false; + + private bool _rawfilemanager = false; + private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N"); + private string _rawhtml = string.Empty; + private string _originalrawhtml = string.Empty; + + private string _message = string.Empty; private bool _contentchanged = false; + private int _editorIndex; [Parameter] public bool AllowFileManagement{ get; set; } + [Parameter] + public bool AllowRichText { get; set; } = true; + + [Parameter] + public bool AllowRawHtml { get; set; } = true; + [Parameter] public bool ReadOnly { get; set; } @@ -85,10 +137,10 @@ public string Placeholder { get; set; } [Parameter] - public string Theme { get; set; } + public string Theme { get; set; } = "snow"; [Parameter] - public string DebugLevel { get; set; } + public string DebugLevel { get; set; } = "info"; [Parameter] public RenderFragment ToolbarContent { get; set; } @@ -103,59 +155,161 @@ protected override void OnInitialized() { interop = new QuillEditorInterop(JSRuntime); + + if (string.IsNullOrEmpty(Placeholder)) + { + Placeholder = Localizer["Placeholder"]; + } + } + + protected override void OnParametersSet() + { + if (!AllowRichText) + { + _activetab = "Raw"; + } } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); - if (firstRender) + if (AllowRichText) { - await interop.CreateEditor( - _editorElement, - _toolBar, - ReadOnly, - Placeholder, - Theme, - DebugLevel); - - await interop.LoadEditorContent(_editorElement, _richhtml); - - // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified) - _originalrichhtml = await interop.GetHtml(_editorElement); - - _initialized = true; - } - else - { - if (_initialized) + if (firstRender) { - if (_contentchanged) + await interop.CreateEditor( + _editorElement, + _toolBar, + ReadOnly, + Placeholder, + Theme, + DebugLevel); + + await interop.LoadEditorContent(_editorElement, _richhtml); + + // preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified) + _originalrichhtml = await interop.GetHtml(_editorElement); + + _initialized = true; + } + else + { + if (_initialized) { - // reload editor if Content passed to component has changed - await interop.LoadEditorContent(_editorElement, _richhtml); - _originalrichhtml = await interop.GetHtml(_editorElement); - } - else - { - // preserve changed content on re-render event - var richhtml = await interop.GetHtml(_editorElement); - if (richhtml != _richhtml) + if (_contentchanged) { - _richhtml = richhtml; + // reload editor if Content passed to component has changed await interop.LoadEditorContent(_editorElement, _richhtml); + _originalrichhtml = await interop.GetHtml(_editorElement); + } + else + { + // preserve changed content on re-render event + var richhtml = await interop.GetHtml(_editorElement); + if (richhtml != _richhtml) + { + _richhtml = richhtml; + await interop.LoadEditorContent(_editorElement, _richhtml); + } } } } - } - _contentchanged = false; + _contentchanged = false; + } } - public void Initialize(string content, bool updated) + public void Initialize(string content) { _richhtml = content; - _contentchanged = updated; + _rawhtml = content; + _originalrawhtml = _rawhtml; // preserve for comparison later + _originalrichhtml = ""; + _richhtml = content; + _contentchanged = content != _originalrawhtml; + + StateHasChanged(); + } + + public async Task GetContent() + { + // evaluate raw html content as first priority + if (_rawhtml != _originalrawhtml) + { + return _rawhtml; + } + else + { + var richhtml = ""; + + if (AllowRichText) + { + richhtml = await interop.GetHtml(_editorElement); + } + + if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + { + // convert Quill's empty content to empty string + if (richhtml == "


") + { + richhtml = string.Empty; + } + return richhtml; + } + else + { + // return original raw html content + return _originalrawhtml; + } + } + } + + public void CloseRichFileManager() + { + _richfilemanager = false; + _message = string.Empty; + StateHasChanged(); + } + + public void CloseRawFileManager() + { + _rawfilemanager = false; + _message = string.Empty; + StateHasChanged(); + } + + public async Task GetHtml() + { + // evaluate raw html content as first priority + if (_rawhtml != _originalrawhtml) + { + return _rawhtml; + } + else + { + var richhtml = ""; + + if (AllowRichText) + { + richhtml = await interop.GetHtml(_editorElement); + } + + if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + { + // convert Quill's empty content to empty string + if (richhtml == "


") + { + richhtml = string.Empty; + } + return richhtml; + } + else + { + // return original raw html content + return _originalrawhtml; + } + } } public async Task InsertRichImage() @@ -183,28 +337,29 @@ StateHasChanged(); } - public void CloseRichFileManager() + public async Task InsertRawImage() { - _richfilemanager = false; _message = string.Empty; - StateHasChanged(); - } - - public async Task GetContent() - { - var richhtml = await interop.GetHtml(_editorElement); - if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml)) + if (_rawfilemanager) { - // convert Quill's empty content to empty string - if (richhtml == "


") + var file = _fileManager.GetFile(); + if (file != null) { - richhtml = string.Empty; + var interop = new Interop(JSRuntime); + int pos = await interop.GetCaretPosition(_rawhtmlid); + var image = "\"""; + _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); + _rawfilemanager = false; + } + else + { + _message = Localizer["Message.Require.Image"]; } - return richhtml; } else { - return null; + _rawfilemanager = true; } + StateHasChanged(); } } diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 907fff94..52d03be8 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -9,57 +9,11 @@
- - @if (AllowRichText) - { - - @_textEditorComponent - - } - @if (AllowRawHtml) - { - - @if (_rawfilemanager) - { - - -
- } -
- @if (AllowFileManagement) - { - - } - @if (_rawfilemanager) - { - @((MarkupString)"  ") - - } -
- @if (ReadOnly) - { - - } - else - { - - } -
- } -
+ @_textEditorComponent
@code { - private string _activetab = "Rich"; - - private bool _rawfilemanager = false; - private FileManager _fileManager; - private string _message = string.Empty; - private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N"); - private string _rawhtml = string.Empty; - private string _originalrawhtml = string.Empty; - private ITextEditorProvider _textEditorProvider; private RenderFragment _textEditorComponent; private ITextEditor _textEditor; @@ -87,36 +41,20 @@ public RenderFragment ToolbarContent { get; set; } [Parameter] - public string Theme { get; set; } = "snow"; + public string Theme { get; set; } [Parameter] - public string DebugLevel { get; set; } = "info"; + public string DebugLevel { get; set; } public override List Resources { get; set; } = new List(); protected override async Task OnInitializedAsync() { - if (string.IsNullOrEmpty(Placeholder)) - { - Placeholder = Localizer["Placeholder"]; - } - - if(AllowRichText) - { - _textEditorProvider = await GetTextEditorProvider(); - } + _textEditorProvider = await GetTextEditorProvider(); } protected override void OnParametersSet() { - _rawhtml = Content; - _originalrawhtml = _rawhtml; // preserve for comparison later - - if (!AllowRichText) - { - _activetab = "Raw"; - } - _textEditorComponent = (builder) => { CreateTextEditor(builder); @@ -127,64 +65,15 @@ { if(_textEditor != null) { - _textEditor.Initialize(Content, Content != _originalrawhtml); + _textEditor.Initialize(Content); } await base.OnAfterRenderAsync(firstRender); } - - public void CloseRawFileManager() - { - _rawfilemanager = false; - _message = string.Empty; - StateHasChanged(); - } - public async Task GetHtml() { - // evaluate raw html content as first priority - if (_rawhtml != _originalrawhtml) - { - return _rawhtml; - } - else - { - var richhtml = string.Empty; - if (AllowRichText && _textEditor != null) - { - richhtml = await _textEditor.GetContent(); - } - - return richhtml != null ? richhtml : _originalrawhtml; - } - } - - - public async Task InsertRawImage() - { - _message = string.Empty; - if (_rawfilemanager) - { - var file = _fileManager.GetFile(); - if (file != null) - { - var interop = new Interop(JSRuntime); - int pos = await interop.GetCaretPosition(_rawhtmlid); - var image = "\"""; - _rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos); - _rawfilemanager = false; - } - else - { - _message = Localizer["Message.Require.Image"]; - } - } - else - { - _rawfilemanager = true; - } - StateHasChanged(); + return await _textEditor.GetContent(); } private void CreateTextEditor(RenderTreeBuilder builder) @@ -200,13 +89,28 @@ var attributes = new Dictionary { { "AllowFileManagement", AllowFileManagement }, - { "ReadOnly", ReadOnly }, - { "Placeholder", Placeholder }, - { "Theme", Theme }, - { "DebugLevel", DebugLevel }, - { "ToolbarContent", ToolbarContent } + { "AllowRichText", AllowRichText }, + { "AllowRawHtml", AllowRawHtml }, + { "ReadOnly", ReadOnly } }; + if(!string.IsNullOrEmpty(Theme)) + { + attributes.Add("Theme", Theme); + } + if (!string.IsNullOrEmpty(DebugLevel)) + { + attributes.Add("DebugLevel", DebugLevel); + } + if (!string.IsNullOrEmpty(Placeholder)) + { + attributes.Add("Placeholder", Placeholder); + } + if(ToolbarContent != null) + { + attributes.Add("ToolbarContent", ToolbarContent); + } + var index = 1; foreach(var name in attributes.Keys) { diff --git a/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx index 5ac2f720..e8180682 100644 --- a/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx +++ b/Oqtane.Client/Resources/Modules/Controls/QuillTextEditor.resx @@ -117,13 +117,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Insert Image - Close + + Insert Image + You Must Select An Image To Insert + + Enter Your Content... + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx deleted file mode 100644 index 4e2b64c5..00000000 --- a/Oqtane.Client/Resources/Modules/Controls/RichTextEditor.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Insert Image - - - Close - - - You Must Select An Image To Insert - - - Enter Your Content... - - \ No newline at end of file diff --git a/Oqtane.Shared/Interfaces/ITextEditor.cs b/Oqtane.Shared/Interfaces/ITextEditor.cs index 8ebc32c4..67f4efab 100644 --- a/Oqtane.Shared/Interfaces/ITextEditor.cs +++ b/Oqtane.Shared/Interfaces/ITextEditor.cs @@ -11,7 +11,7 @@ namespace Oqtane.Interfaces /// initializes the editor with the initialize content. /// /// the initialize content. - void Initialize(string content, bool updated); + void Initialize(string content); /// /// get content from the editor. From bc0573918f64b428240dceda422d4f40d029c541 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Sat, 8 Jun 2024 16:14:56 -0400 Subject: [PATCH 132/189] search refactoring --- .../Search/ModuleSearchIndexManager.cs | 12 ++--- .../Search/ModuleSearchResultManager.cs | 4 +- .../Managers/Search/PageSearchIndexManager.cs | 8 ++-- .../SearchContentWordCountEntityBuilder.cs | 42 ----------------- .../SearchContentWordEntityBuilder.cs | 47 +++++++++++++++++++ .../SearchContentWordSourceEntityBuilder.cs | 31 ------------ .../EntityBuilders/SearchWordEntityBuilder.cs | 34 ++++++++++++++ .../Tenant/05020001_AddSearchTables.cs | 20 ++++---- .../Providers/DatabaseSearchProvider.cs | 37 ++++++++------- .../Repository/Context/TenantDBContext.cs | 4 +- .../Interfaces/ISearchContentRepository.cs | 12 ++--- .../Repository/SearchContentRepository.cs | 44 ++++++++--------- Oqtane.Shared/Models/SearchContent.cs | 4 +- Oqtane.Shared/Models/SearchContentWord.cs | 24 ++++++++++ Oqtane.Shared/Models/SearchContentWords.cs | 19 -------- ...archContentWordSource.cs => SearchWord.cs} | 7 ++- 16 files changed, 183 insertions(+), 166 deletions(-) delete mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs delete mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs create mode 100644 Oqtane.Server/Migrations/EntityBuilders/SearchWordEntityBuilder.cs create mode 100644 Oqtane.Shared/Models/SearchContentWord.cs delete mode 100644 Oqtane.Shared/Models/SearchContentWords.cs rename Oqtane.Shared/Models/{SearchContentWordSource.cs => SearchWord.cs} (50%) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index b68c993f..92c933be 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -127,19 +127,19 @@ namespace Oqtane.Managers.Search searchContent.Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name; } - if (searchContent.Properties == null) + if (searchContent.SearchContentProperties == null) { - searchContent.Properties = new List(); + searchContent.SearchContentProperties = new List(); } - if(!searchContent.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + if(!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) { - searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() }); + searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() }); } - if (!searchContent.Properties.Any(i => i.Name == Constants.SearchModuleIdPropertyName)) + if (!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchModuleIdPropertyName)) { - searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() }); + searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() }); } } } diff --git a/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs index c40f5f0b..d108962c 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchResultManager.cs @@ -24,7 +24,7 @@ namespace Oqtane.Managers.Search public string GetUrl(SearchResult searchResult, SearchQuery searchQuery) { var pageRepository = _serviceProvider.GetRequiredService(); - var pageIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + var pageIdValue = searchResult.SearchContentProperties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; if(!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId)) { var page = pageRepository.GetPage(pageId); @@ -39,7 +39,7 @@ namespace Oqtane.Managers.Search public bool Visible(SearchContent searchResult, SearchQuery searchQuery) { - var pageIdValue = searchResult.Properties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + var pageIdValue = searchResult.SearchContentProperties?.FirstOrDefault(i => i.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; if (!string.IsNullOrEmpty(pageIdValue) && int.TryParse(pageIdValue, out int pageId)) { return CanViewPage(pageId, searchQuery.User); diff --git a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs index 49599804..07601905 100644 --- a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs @@ -64,14 +64,14 @@ namespace Oqtane.Managers.Search IsActive = !page.IsDeleted && Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) }; - if (searchContent.Properties == null) + if (searchContent.SearchContentProperties == null) { - searchContent.Properties = new List(); + searchContent.SearchContentProperties = new List(); } - if (!searchContent.Properties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) + if (!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchPageIdPropertyName)) { - searchContent.Properties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() }); + searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() }); } searchContentList.Add(searchContent); diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs deleted file mode 100644 index 2eb845fc..00000000 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordCountEntityBuilder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; -using Oqtane.Databases.Interfaces; - -namespace Oqtane.Migrations.EntityBuilders -{ - public class SearchContentWordsEntityBuilder : BaseEntityBuilder - { - private const string _entityTableName = "SearchContentWords"; - private readonly PrimaryKey _primaryKey = new("PK_SearchContentWords", x => x.WordId); - private readonly ForeignKey _searchContentForeignKey = new("FK_SearchContentWords_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade); - private readonly ForeignKey _wordSourceForeignKey = new("FK_SearchContentWords_WordSource", x => x.WordSourceId, "SearchContentWordSource", "WordSourceId", ReferentialAction.Cascade); - - public SearchContentWordsEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) - { - EntityTableName = _entityTableName; - PrimaryKey = _primaryKey; - - ForeignKeys.Add(_searchContentForeignKey); - ForeignKeys.Add(_wordSourceForeignKey); - } - - protected override SearchContentWordsEntityBuilder BuildTable(ColumnsBuilder table) - { - WordId = AddAutoIncrementColumn(table, "WordId"); - SearchContentId = AddIntegerColumn(table, "SearchContentId"); - WordSourceId = AddIntegerColumn(table, "WordSourceId"); - Count = AddIntegerColumn(table, "Count"); - - return this; - } - - public OperationBuilder WordId { get; private set; } - - public OperationBuilder SearchContentId { get; private set; } - - public OperationBuilder WordSourceId { get; private set; } - - public OperationBuilder Count { get; private set; } - } -} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs new file mode 100644 index 00000000..5721b6f5 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchContentWordEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchContentWord"; + private readonly PrimaryKey _primaryKey = new("PK_SearchContentWord", x => x.SearchWordId); + private readonly ForeignKey _foreignKey1 = new("FK_SearchContentWord_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade); + private readonly ForeignKey _foreignKey2 = new("FK_SearchContentWord_SearchWord", x => x.SearchWordId, "SearchWord", "SearchWordId", ReferentialAction.Cascade); + + public SearchContentWordEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + ForeignKeys.Add(_foreignKey1); + ForeignKeys.Add(_foreignKey2); + } + + protected override SearchContentWordEntityBuilder BuildTable(ColumnsBuilder table) + { + SearchContentWordId = AddAutoIncrementColumn(table, "SearchContentWordId"); + SearchContentId = AddIntegerColumn(table, "SearchContentId"); + SearchWordId = AddIntegerColumn(table, "SearchWordId"); + Count = AddIntegerColumn(table, "Count"); + CreatedOn = AddDateTimeColumn(table, "CreatedOn"); + ModifiedOn = AddDateTimeColumn(table, "ModifiedOn"); + + return this; + } + + public OperationBuilder SearchContentWordId { get; private set; } + + public OperationBuilder SearchContentId { get; private set; } + + public OperationBuilder SearchWordId { get; private set; } + + public OperationBuilder Count { get; private set; } + + public OperationBuilder CreatedOn { get; private set; } + + public OperationBuilder ModifiedOn { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs deleted file mode 100644 index 0787a3a4..00000000 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordSourceEntityBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; -using Oqtane.Databases.Interfaces; - -namespace Oqtane.Migrations.EntityBuilders -{ - public class SearchContentWordSourceEntityBuilder : BaseEntityBuilder - { - private const string _entityTableName = "SearchContentWordSource"; - private readonly PrimaryKey _primaryKey = new("PK_SearchContentWordSource", x => x.WordSourceId); - - public SearchContentWordSourceEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) - { - EntityTableName = _entityTableName; - PrimaryKey = _primaryKey; - } - - protected override SearchContentWordSourceEntityBuilder BuildTable(ColumnsBuilder table) - { - WordSourceId = AddAutoIncrementColumn(table, "WordSourceId"); - Word = AddStringColumn(table, "Word", 255); - - return this; - } - - public OperationBuilder WordSourceId { get; private set; } - - public OperationBuilder Word { get; private set; } - } -} diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchWordEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchWordEntityBuilder.cs new file mode 100644 index 00000000..77180ea9 --- /dev/null +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchWordEntityBuilder.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Oqtane.Databases.Interfaces; + +namespace Oqtane.Migrations.EntityBuilders +{ + public class SearchWordEntityBuilder : BaseEntityBuilder + { + private const string _entityTableName = "SearchWord"; + private readonly PrimaryKey _primaryKey = new("PK_SearchWord", x => x.SearchWordId); + + public SearchWordEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database) + { + EntityTableName = _entityTableName; + PrimaryKey = _primaryKey; + } + + protected override SearchWordEntityBuilder BuildTable(ColumnsBuilder table) + { + SearchWordId = AddAutoIncrementColumn(table, "SearchWordId"); + Word = AddStringColumn(table, "Word", 255); + CreatedOn = AddDateTimeColumn(table, "CreatedOn"); + + return this; + } + + public OperationBuilder SearchWordId { get; private set; } + + public OperationBuilder Word { get; private set; } + + public OperationBuilder CreatedOn { get; private set; } + } +} diff --git a/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs index c66a4fe5..575515dd 100644 --- a/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs +++ b/Oqtane.Server/Migrations/Tenant/05020001_AddSearchTables.cs @@ -23,22 +23,22 @@ namespace Oqtane.Migrations.Tenant var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); searchContentPropertyEntityBuilder.Create(); - var searchContentWordSourceEntityBuilder = new SearchContentWordSourceEntityBuilder(migrationBuilder, ActiveDatabase); - searchContentWordSourceEntityBuilder.Create(); - searchContentWordSourceEntityBuilder.AddIndex("IX_SearchContentWordSource", "Word", true); + var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase); + searchWordEntityBuilder.Create(); + searchWordEntityBuilder.AddIndex("IX_SearchWord", "Word", true); - var searchContentWordsEntityBuilder = new SearchContentWordsEntityBuilder(migrationBuilder, ActiveDatabase); - searchContentWordsEntityBuilder.Create(); + var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordEntityBuilder.Create(); } protected override void Down(MigrationBuilder migrationBuilder) { - var searchContentWordsEntityBuilder = new SearchContentWordsEntityBuilder(migrationBuilder, ActiveDatabase); - searchContentWordsEntityBuilder.Drop(); + var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase); + searchContentWordEntityBuilder.Drop(); - var searchContentWordSourceEntityBuilder = new SearchContentWordSourceEntityBuilder(migrationBuilder, ActiveDatabase); - searchContentWordSourceEntityBuilder.DropIndex("IX_SearchContentWordSource"); - searchContentWordSourceEntityBuilder.Drop(); + var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase); + searchWordEntityBuilder.DropIndex("IX_SearchWord"); + searchWordEntityBuilder.Drop(); var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase); searchContentPropertyEntityBuilder.Drop(); diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index adf2cee8..24e8a7da 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -4,13 +4,11 @@ using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Xml; using HtmlAgilityPack; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Services; using Oqtane.Shared; -using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource; namespace Oqtane.Providers { @@ -64,7 +62,7 @@ namespace Oqtane.Providers { var totalResults = 0; - var searchContentList = await _searchContentRepository.GetSearchContentListAsync(searchQuery); + var searchContentList = await _searchContentRepository.GetSearchContentsAsync(searchQuery); //convert the search content to search results. var results = searchContentList @@ -107,7 +105,7 @@ namespace Oqtane.Providers { if (i.EntityName == EntityNames.Page || i.EntityName == EntityNames.Module) { - var pageId = i.Properties.FirstOrDefault(p => p.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; + var pageId = i.SearchContentProperties.FirstOrDefault(p => p.Name == Constants.SearchPageIdPropertyName)?.Value ?? string.Empty; return !string.IsNullOrEmpty(pageId) ? pageId : i.UniqueKey; } else @@ -138,7 +136,7 @@ namespace Oqtane.Providers Body = searchContent.Body, Url = searchContent.Url, ModifiedTime = searchContent.ModifiedTime, - Properties = searchContent.Properties, + SearchContentProperties = searchContent.SearchContentProperties, Snippet = BuildSnippet(searchContent, searchQuery), Score = CalculateScore(searchContent, searchQuery) }; @@ -151,7 +149,7 @@ namespace Oqtane.Providers var score = 0f; foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) { - score += searchContent.Words.Where(i => i.WordSource.Word.StartsWith(keyword)).Sum(i => i.Count); + score += searchContent.SearchContentWords.Where(i => i.SearchWord.Word.StartsWith(keyword)).Sum(i => i.Count); } return score / 100; @@ -206,31 +204,34 @@ namespace Oqtane.Providers //analyze the search content and save the index words var indexContent = $"{searchContent.Title} {searchContent.Description} {searchContent.Body} {searchContent.AdditionalContent}"; var words = GetWords(indexContent, WordMinLength); - var existWords = _searchContentRepository.GetWords(searchContent.SearchContentId); + var existingSearchContentWords = _searchContentRepository.GetSearchContentWords(searchContent.SearchContentId); foreach (var kvp in words) { - var word = existWords.FirstOrDefault(i => i.WordSource.Word == kvp.Key); - if (word != null) + var searchContentWord = existingSearchContentWords.FirstOrDefault(i => i.SearchWord.Word == kvp.Key); + if (searchContentWord != null) { - word.Count = kvp.Value; - _searchContentRepository.UpdateSearchContentWords(word); + searchContentWord.Count = kvp.Value; + searchContentWord.ModifiedOn = DateTime.UtcNow; + _searchContentRepository.UpdateSearchContentWord(searchContentWord); } else { - var wordSource = _searchContentRepository.GetSearchContentWordSource(kvp.Key); - if (wordSource == null) + var searchWord = _searchContentRepository.GetSearchWord(kvp.Key); + if (searchWord == null) { - wordSource = _searchContentRepository.AddSearchContentWordSource(new SearchContentWordSource { Word = kvp.Key }); + searchWord = _searchContentRepository.AddSearchWord(new SearchWord { Word = kvp.Key, CreatedOn = DateTime.UtcNow }); } - word = new SearchContentWords + searchContentWord = new SearchContentWord { SearchContentId = searchContent.SearchContentId, - WordSourceId = wordSource.WordSourceId, - Count = kvp.Value + SearchWordId = searchWord.SearchWordId, + Count = kvp.Value, + CreatedOn = DateTime.UtcNow, + ModifiedOn = DateTime.UtcNow }; - _searchContentRepository.AddSearchContentWords(word); + _searchContentRepository.AddSearchContentWord(searchContentWord); } } } diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index cb0dcb28..599485f7 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -31,7 +31,7 @@ namespace Oqtane.Repository public virtual DbSet UrlMapping { get; set; } public virtual DbSet SearchContent { get; set; } public virtual DbSet SearchContentProperty { get; set; } - public virtual DbSet SearchContentWords { get; set; } - public virtual DbSet SearchContentWordSource { get; set; } + public virtual DbSet SearchContentWord { get; set; } + public virtual DbSet SearchWord { get; set; } } } diff --git a/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs index 868265b0..38e3769c 100644 --- a/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs @@ -7,18 +7,18 @@ namespace Oqtane.Repository { public interface ISearchContentRepository { - Task> GetSearchContentListAsync(SearchQuery searchQuery); + Task> GetSearchContentsAsync(SearchQuery searchQuery); SearchContent AddSearchContent(SearchContent searchContent); void DeleteSearchContent(int searchContentId); void DeleteSearchContent(string entityName, int entryId); void DeleteSearchContent(string uniqueKey); void DeleteAllSearchContent(); - SearchContentWordSource GetSearchContentWordSource(string word); - SearchContentWordSource AddSearchContentWordSource(SearchContentWordSource wordSource); + SearchWord GetSearchWord(string word); + SearchWord AddSearchWord(SearchWord searchWord); - IEnumerable GetWords(int searchContentId); - SearchContentWords AddSearchContentWords(SearchContentWords word); - SearchContentWords UpdateSearchContentWords(SearchContentWords word); + IEnumerable GetSearchContentWords(int searchContentId); + SearchContentWord AddSearchContentWord(SearchContentWord searchContentWord); + SearchContentWord UpdateSearchContentWord(SearchContentWord searchContentWord); } } diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index 67ecfcf7..5bc9eca0 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -17,13 +17,13 @@ namespace Oqtane.Repository _dbContextFactory = dbContextFactory; } - public async Task> GetSearchContentListAsync(SearchQuery searchQuery) + public async Task> GetSearchContentsAsync(SearchQuery searchQuery) { using var db = _dbContextFactory.CreateDbContext(); var searchContentList = db.SearchContent.AsNoTracking() - .Include(i => i.Properties) - .Include(i => i.Words) - .ThenInclude(w => w.WordSource) + .Include(i => i.SearchContentProperties) + .Include(i => i.SearchContentWords) + .ThenInclude(w => w.SearchWord) .Where(i => i.SiteId == searchQuery.SiteId && i.IsActive); if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) @@ -45,7 +45,7 @@ namespace Oqtane.Repository { foreach (var property in searchQuery.Properties) { - searchContentList = searchContentList.Where(i => i.Properties.Any(p => p.Name == property.Key && p.Value == property.Value)); + searchContentList = searchContentList.Where(i => i.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value)); } } @@ -54,7 +54,7 @@ namespace Oqtane.Repository { foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) { - filteredContentList.AddRange(await searchContentList.Where(i => i.Words.Any(w => w.WordSource.Word.StartsWith(keyword))).ToListAsync()); + filteredContentList.AddRange(await searchContentList.Where(i => i.SearchContentWords.Any(w => w.SearchWord.Word.StartsWith(keyword))).ToListAsync()); } } @@ -66,9 +66,9 @@ namespace Oqtane.Repository using var context = _dbContextFactory.CreateDbContext(); context.SearchContent.Add(searchContent); - if(searchContent.Properties != null && searchContent.Properties.Any()) + if(searchContent.SearchContentProperties != null && searchContent.SearchContentProperties.Any()) { - foreach(var property in searchContent.Properties) + foreach(var property in searchContent.SearchContentProperties) { property.SearchContentId = searchContent.SearchContentId; context.SearchContentProperty.Add(property); @@ -117,7 +117,7 @@ namespace Oqtane.Repository db.SaveChanges(); } - public SearchContentWordSource GetSearchContentWordSource(string word) + public SearchWord GetSearchWord(string word) { if(string.IsNullOrEmpty(word)) { @@ -125,45 +125,45 @@ namespace Oqtane.Repository } using var db = _dbContextFactory.CreateDbContext(); - return db.SearchContentWordSource.FirstOrDefault(i => i.Word == word); + return db.SearchWord.FirstOrDefault(i => i.Word == word); } - public SearchContentWordSource AddSearchContentWordSource(SearchContentWordSource wordSource) + public SearchWord AddSearchWord(SearchWord searchWord) { using var db = _dbContextFactory.CreateDbContext(); - db.SearchContentWordSource.Add(wordSource); + db.SearchWord.Add(searchWord); db.SaveChanges(); - return wordSource; + return searchWord; } - public IEnumerable GetWords(int searchContentId) + public IEnumerable GetSearchContentWords(int searchContentId) { using var db = _dbContextFactory.CreateDbContext(); - return db.SearchContentWords - .Include(i => i.WordSource) + return db.SearchContentWord + .Include(i => i.SearchWord) .Where(i => i.SearchContentId == searchContentId).ToList(); } - public SearchContentWords AddSearchContentWords(SearchContentWords word) + public SearchContentWord AddSearchContentWord(SearchContentWord searchContentWord) { using var db = _dbContextFactory.CreateDbContext(); - db.SearchContentWords.Add(word); + db.SearchContentWord.Add(searchContentWord); db.SaveChanges(); - return word; + return searchContentWord; } - public SearchContentWords UpdateSearchContentWords(SearchContentWords word) + public SearchContentWord UpdateSearchContentWord(SearchContentWord searchContentWord) { using var db = _dbContextFactory.CreateDbContext(); - db.Entry(word).State = EntityState.Modified; + db.Entry(searchContentWord).State = EntityState.Modified; db.SaveChanges(); - return word; + return searchContentWord; } } } diff --git a/Oqtane.Shared/Models/SearchContent.cs b/Oqtane.Shared/Models/SearchContent.cs index 36f4c664..f9ecafa4 100644 --- a/Oqtane.Shared/Models/SearchContent.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -31,9 +31,9 @@ namespace Oqtane.Models public string AdditionalContent { get; set; } - public IList Properties { get; set; } + public IList SearchContentProperties { get; set; } - public IList Words { get; set; } + public IList SearchContentWords { get; set; } public override string ToString() { diff --git a/Oqtane.Shared/Models/SearchContentWord.cs b/Oqtane.Shared/Models/SearchContentWord.cs new file mode 100644 index 00000000..e5c7a092 --- /dev/null +++ b/Oqtane.Shared/Models/SearchContentWord.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Oqtane.Models +{ + public class SearchContentWord + { + [Key] + public int SearchContentWordId { get; set; } + + public int SearchContentId { get; set; } + + public int SearchWordId { get; set; } + + public int Count { get; set; } + + public DateTime CreatedOn { get; set; } + + public DateTime ModifiedOn { get; set; } + + public SearchWord SearchWord { get; set; } + } +} diff --git a/Oqtane.Shared/Models/SearchContentWords.cs b/Oqtane.Shared/Models/SearchContentWords.cs deleted file mode 100644 index c7630815..00000000 --- a/Oqtane.Shared/Models/SearchContentWords.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Oqtane.Models -{ - public class SearchContentWords - { - [Key] - public int WordId { get; set; } - - public int SearchContentId { get; set; } - - public int WordSourceId { get; set; } - - public int Count { get; set; } - - public SearchContentWordSource WordSource { get; set; } - } -} diff --git a/Oqtane.Shared/Models/SearchContentWordSource.cs b/Oqtane.Shared/Models/SearchWord.cs similarity index 50% rename from Oqtane.Shared/Models/SearchContentWordSource.cs rename to Oqtane.Shared/Models/SearchWord.cs index ec814757..58ac1ff8 100644 --- a/Oqtane.Shared/Models/SearchContentWordSource.cs +++ b/Oqtane.Shared/Models/SearchWord.cs @@ -1,12 +1,15 @@ +using System; using System.ComponentModel.DataAnnotations; namespace Oqtane.Models { - public class SearchContentWordSource + public class SearchWord { [Key] - public int WordSourceId { get; set; } + public int SearchWordId { get; set; } public string Word { get; set; } + + public DateTime CreatedOn { get; set; } } } From 1c2abe794a3e88f4d2c9a3df17184bf12fe74861 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 12:33:09 -0400 Subject: [PATCH 133/189] move Search page/module to Admin template so that it is always provisioned --- .../SiteTemplates/DefaultSiteTemplate.cs | 24 ----------------- .../Infrastructure/UpgradeManager.cs | 4 +-- Oqtane.Server/Repository/SiteRepository.cs | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs index 447678ed..7cf27808 100644 --- a/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs +++ b/Oqtane.Server/Infrastructure/SiteTemplates/DefaultSiteTemplate.cs @@ -133,30 +133,6 @@ namespace Oqtane.SiteTemplates } } }); - _pageTemplates.Add(new PageTemplate - { - Name = "Search Results", - Parent = "", - Order = 7, - Path = "search", - Icon = "oi oi-magnifying-glass", - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, - PermissionList = new List { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - } - } - } - }); if (System.IO.File.Exists(Path.Combine(_environment.WebRootPath, "images", "logo-white.png"))) { diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 8737dd65..903c612d 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -399,7 +399,7 @@ namespace Oqtane.Infrastructure var pageTemplates = new List(); pageTemplates.Add(new PageTemplate { - Name = "Search Results", + Name = "Search", Parent = "", Path = "search", Icon = "oi oi-magnifying-glass", @@ -411,7 +411,7 @@ namespace Oqtane.Infrastructure new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, PageTemplateModules = new List { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search Results", Pane = PaneNames.Default, + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default, PermissionList = new List { new Permission(PermissionNames.View, RoleNames.Everyone, true), new Permission(PermissionNames.View, RoleNames.Admin, true), diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 3328461b..cc8afd58 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -607,6 +607,7 @@ namespace Oqtane.Repository } } }); + pageTemplates.Add(new PageTemplate { Name = "Register", @@ -666,6 +667,7 @@ namespace Oqtane.Repository } } }); + pageTemplates.Add(new PageTemplate { Name = "Profile", @@ -695,6 +697,31 @@ namespace Oqtane.Repository } } }); + + pageTemplates.Add(new PageTemplate + { + Name = "Search", + Parent = "", + Path = "search", + Icon = "oi oi-magnifying-glass", + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List { + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + } + } + } + }); + pageTemplates.Add(new PageTemplate { Name = "Not Found", From 15c8b724e60ddb448e3333e94fff12ff7d69e4f8 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:24:52 -0700 Subject: [PATCH 134/189] Update Oqtane.Client.csproj Package Dependencies --- Oqtane.Client/Oqtane.Client.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 884b79a0..066ec75f 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -4,7 +4,7 @@ net8.0 Exe Debug;Release - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation @@ -22,9 +22,9 @@ - - - + + + From 8be5d0c72d8cd69be9baa99f60a9493c62616d2d Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:27:15 -0700 Subject: [PATCH 135/189] Update Oqtane.Database.MySQL.csproj Package Dependencies --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index 71019446..e81f539a 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -33,8 +33,8 @@ - - + + From f7c0ebb8d3507c15c1d0505ee25a24778917e862 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:27:53 -0700 Subject: [PATCH 136/189] Update Oqtane.Database.MySQL.csproj prepare 5.2.0 --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index e81f539a..d21dd428 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -2,7 +2,7 @@ net8.0 - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation From 68604ec15ac5f70f934a80f67f7d5ff5747c4af3 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:28:51 -0700 Subject: [PATCH 137/189] Update Oqtane.Database.PostgreSQL.csproj Package Dependencies --- Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 04093e5a..894e5113 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -2,7 +2,7 @@ net8.0 - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation @@ -34,7 +34,7 @@ - + From 900d026bcb4f13c7e7ab7f4618f6d5ed381ae39b Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:29:58 -0700 Subject: [PATCH 138/189] Update v5.2.0 Package Dependencies --- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index f742b253..a04f5863 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -2,7 +2,7 @@ net8.0 - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation @@ -33,7 +33,7 @@ - + From a2140a3b7b146ffa148a8361a3d719307829a7e3 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:30:37 -0700 Subject: [PATCH 139/189] Update v5.2.0 Package Dependencies --- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index 80e4e18d..cfdd87c8 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -2,7 +2,7 @@ net8.0 - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation @@ -33,7 +33,7 @@ - + From 9cf2d30e774822c6727cc605dea5850ad2d57f47 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:32:30 -0700 Subject: [PATCH 140/189] Prepare Update v5.2.0 --- Oqtane.Updater/Oqtane.Updater.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index c9b31857..3ac22fb3 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -3,7 +3,7 @@ net8.0 Exe - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation From 35bdd8b4ef5ae4fcd9e5daf31adba1e51e2f018c Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:35:16 -0700 Subject: [PATCH 141/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Maui/Oqtane.Maui.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index a7a79ea3..84f2bc0c 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,7 +6,7 @@ Exe - 5.1.2 + 5.2.0 Oqtane Shaun Walker .NET Foundation @@ -31,7 +31,7 @@ 0E29FC31-1B83-48ED-B6E0-9F3C67B775D4 - 5.1.2 + 5.2.0 1 14.2 @@ -65,11 +65,11 @@ - - + + - + From ed14f6d13fa5ab0e1fe93057f6621c5de7c108de Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:35:43 -0700 Subject: [PATCH 142/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Maui/Oqtane.Maui.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 84f2bc0c..991287b0 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git Oqtane.Maui From 5821d67e69926a12f4341fc73b103239e8a889c4 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:36:47 -0700 Subject: [PATCH 143/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index a04f5863..9755e6ea 100644 --- a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj +++ b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git true From da9e4c026c60ee60c40e3410d155a89733ccac02 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:37:04 -0700 Subject: [PATCH 144/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index 894e5113..a8eb4718 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git true From c757cef549521f05d25d08b40a8647d30e305666 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:37:21 -0700 Subject: [PATCH 145/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index d21dd428..4f58db37 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git true From c1be1f329f4565a4b7b53daf5914529646f4c56e Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:37:51 -0700 Subject: [PATCH 146/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Client/Oqtane.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 066ec75f..1599df40 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git Oqtane From 27dafa83ac9d9412e1a16ba26909890c2fbac79a Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:38:52 -0700 Subject: [PATCH 147/189] Prepare Update v5.2.0 Package Dependencies --- Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj index cfdd87c8..4a788d7e 100644 --- a/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj +++ b/Oqtane.Database.Sqlite/Oqtane.Database.Sqlite.csproj @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git true From 8f1cc2653738be9344a60a29de7a8555827d1657 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:40:50 -0700 Subject: [PATCH 148/189] Prepare v5.2.0 Release --- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 4b9d0a44..3b87b962 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 5.1.2 + 5.2.0 Shaun Walker .NET Foundation Oqtane Framework @@ -21,4 +21,4 @@ - \ No newline at end of file + From 3356dcf8f796b816f6870f84e04e419bf0d7ec4d Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:41:10 -0700 Subject: [PATCH 149/189] Prepare v5.2.0 Release --- Oqtane.Package/Oqtane.Client.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 3b87b962..fdaba846 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 icon.png oqtane From b82a811c336c21528d5ff8e8638da3fd927082d4 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:41:51 -0700 Subject: [PATCH 150/189] Prepare v5.2.0 Release and Package Dependencies --- Oqtane.Package/Oqtane.Framework.nuspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index d81c1693..ea16d1aa 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 5.1.2 + 5.2.0 Shaun Walker .NET Foundation Oqtane Framework @@ -11,12 +11,12 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v5.1.2/Oqtane.Framework.5.1.2.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/download/v5.2.0/Oqtane.Framework.5.2.0.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 icon.png oqtane framework - \ No newline at end of file + From d0e5aef443e0c59bcc820e09f20c2d0700a36759 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:42:32 -0700 Subject: [PATCH 151/189] Prepare v5.2.0 Release --- Oqtane.Package/Oqtane.Server.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 7a489cfb..f318997a 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 5.1.2 + 5.2.0 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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 icon.png oqtane @@ -21,4 +21,4 @@ - \ No newline at end of file + From a719518f8f80e91a63ca5bae22cc7f47d8e28770 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:42:57 -0700 Subject: [PATCH 152/189] Prepare v5.2.0 Release --- Oqtane.Package/Oqtane.Shared.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index ab1deb4d..e24aa694 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 5.1.2 + 5.2.0 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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 icon.png oqtane @@ -21,4 +21,4 @@ - \ No newline at end of file + From 5e5caa979be2e70d195069d6afb73f1962250a76 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:43:19 -0700 Subject: [PATCH 153/189] Prepare v5.2.0 Release --- Oqtane.Package/Oqtane.Updater.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 2c51fc0e..e0e17e51 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 5.1.2 + 5.2.0 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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 icon.png oqtane @@ -20,4 +20,4 @@ - \ No newline at end of file + From 7ce61a5d2b4e6d0cadc274f653b4623e71f920b9 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:43:54 -0700 Subject: [PATCH 154/189] Prepare v5.2.0 Release --- Oqtane.Package/upgrade.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 0c0d2f56..8d850af9 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Upgrade.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Upgrade.zip" -Force From 51425cac4a899eee2282d22f680aa6c1aeb8ebd5 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:44:40 -0700 Subject: [PATCH 155/189] Prepare v5.2.0 Release --- Oqtane.Package/install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 57e2d394..bebefa40 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.1.2.Install.zip" -Force \ No newline at end of file +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Install.zip" -Force From 3fa6dcea16714a3c967e97a9425799978a2511db Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:47:03 -0700 Subject: [PATCH 156/189] Prepare v5.2.0 Release and Package Dependencies --- Oqtane.Server/Oqtane.Server.csproj | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 52776903..7937ca07 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.2 + 5.2.0 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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -34,19 +34,19 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + From 1d00330e7aa05ecf8a8ea14a7c57003312bc8f31 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:48:18 -0700 Subject: [PATCH 157/189] Prepare v5.2.0 Release and Package Dependencies --- Oqtane.Shared/Oqtane.Shared.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index f16dcd9c..fb0cc36f 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -3,7 +3,7 @@ net8.0 Debug;Release - 5.1.2 + 5.2.0 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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git Oqtane @@ -19,8 +19,8 @@ - - + + From 0515aaa946ac23ee69bd76af578c1697accfcb76 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 10 Jun 2024 10:48:46 -0700 Subject: [PATCH 158/189] Prepare v5.2.0 Release --- Oqtane.Updater/Oqtane.Updater.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index 3ac22fb3..72103598 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -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/v5.1.2 + https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0 https://github.com/oqtane/oqtane.framework Git Oqtane From af35fb79fedb91e83872fdf8fce84ed6adeb90d3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 14:55:06 -0400 Subject: [PATCH 159/189] refactored to move AdminSiteTemplate out of SiteRepository --- .../SiteTemplates/AdminSiteTemplate.cs | 745 ++++++++++++++++++ Oqtane.Server/Repository/SiteRepository.cs | 735 +---------------- .../Repository/SiteTemplateRepository.cs | 20 +- Oqtane.Shared/Shared/Constants.cs | 1 + 4 files changed, 768 insertions(+), 733 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs new file mode 100644 index 00000000..c70962ad --- /dev/null +++ b/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs @@ -0,0 +1,745 @@ +using Oqtane.Models; +using Oqtane.Infrastructure; +using System.Collections.Generic; +using Oqtane.Shared; +using Oqtane.Documentation; + +namespace Oqtane.SiteTemplates +{ + [PrivateApi("Mark Site-Template classes as private, since it's not very useful in the public docs")] + public class AdminSiteTemplate : ISiteTemplate + { + public string Name + { + get { return "Admin Site Template"; } + } + + public List CreateSite(Site site) + { + var pageTemplates = new List(); + var seed = 1000; // order + + // user pages + pageTemplates.Add(new PageTemplate + { + Name = "Login", + Parent = "", + Path = "login", + Order = seed + 1, + Icon = Icons.LockLocked, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Register", + Parent = "", + Path = "register", + Order = seed + 3, + Icon = Icons.Person, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Reset", + Parent = "", + Path = "reset", + Order = seed + 5, + Icon = Icons.Person, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Profile", + Parent = "", + Path = "profile", + Order = seed + 7, + Icon = Icons.Person, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Registered, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Registered, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Search", + Parent = "", + Path = "search", + Order = seed + 9, + Icon = "oi oi-magnifying-glass", + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List { + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + } + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Not Found", + Parent = "", + Path = "404", + Order = seed + 11, + Icon = Icons.X, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Everyone, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "

The page you requested does not exist or you do not have sufficient rights to view it.

" + } + } + }); + + // admin pages + pageTemplates.Add(new PageTemplate + { + Name = "Admin", + Parent = "", + Path = "admin", + Order = seed + 51, + Icon = "", + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Site Settings", + Parent = "Admin", + Order = 1, + Path = "admin/site", + Icon = Icons.Home, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Page Management", + Parent = "Admin", + Order = 3, + Path = "admin/pages", + Icon = Icons.Layers, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.View, RoleNames.Registered, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "User Management", + Parent = "Admin", + Order = 5, + Path = "admin/users", + Icon = Icons.People, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Profile Management", + Parent = "Admin", + Order = 7, + Path = "admin/profiles", + Icon = Icons.Person, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Role Management", + Parent = "Admin", + Order = 9, + Path = "admin/roles", + Icon = Icons.LockLocked, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "File Management", + Parent = "Admin", + Order = 11, + Path = "admin/files", + Icon = Icons.File, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Recycle Bin", + Parent = "Admin", + Order = 13, + Path = "admin/recyclebin", + Icon = Icons.Trash, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Url Mappings", + Parent = "Admin", + Order = 15, + Path = "admin/urlmappings", + Icon = Icons.LinkBroken, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + pageTemplates.Add(new PageTemplate + { + Name = "Visitor Management", + Parent = "Admin", + Order = 17, + Path = "admin/visitors", + Icon = Icons.Eye, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + // host pages + pageTemplates.Add(new PageTemplate + { + Name = "Event Log", + Parent = "Admin", + Order = 19, + Path = "admin/log", + Icon = Icons.MagnifyingGlass, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Site Management", + Parent = "Admin", + Order = 21, + Path = "admin/sites", + Icon = Icons.Globe, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Module Management", + Parent = "Admin", + Order = 23, + Path = "admin/modules", + Icon = Icons.Browser, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Theme Management", + Parent = "Admin", + Order = 25, + Path = "admin/themes", + Icon = Icons.Brush, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Language Management", + Parent = "Admin", + Order = 27, + Path = "admin/languages", + Icon = Icons.Text, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true), + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Scheduled Jobs", + Parent = "Admin", + Order = 29, + Path = "admin/jobs", + Icon = Icons.Timer, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "Sql Management", + Parent = "Admin", + Order = 31, + Path = "admin/sql", + Icon = Icons.Spreadsheet, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "System Info", + Parent = "Admin", + Order = 33, + Path = "admin/system", + Icon = Icons.MedicalCross, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + pageTemplates.Add(new PageTemplate + { + Name = "System Update", + Parent = "Admin", + Order = 35, + Path = "admin/update", + Icon = Icons.Aperture, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + PageTemplateModules = new List + { + new PageTemplateModule + { + ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Host, true), + new Permission(PermissionNames.Edit, RoleNames.Host, true) + }, + Content = "" + } + } + }); + + return pageTemplates; + } + } +} diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index cc8afd58..37eba499 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -329,7 +329,13 @@ namespace Oqtane.Repository } }); - // process site template first + // admin site template + var siteTemplateType = Type.GetType(Constants.AdminSiteTemplate); + var siteTemplateObject = ActivatorUtilities.CreateInstance(_serviceProvider, siteTemplateType); + List adminPageTemplates = ((ISiteTemplate)siteTemplateObject).CreateSite(site); + CreatePages(site, adminPageTemplates, null); + + // process site template if (string.IsNullOrEmpty(site.SiteTemplateType)) { var section = _config.GetSection("Installation:SiteTemplate"); @@ -349,19 +355,16 @@ namespace Oqtane.Repository } } - Type siteTemplateType = Type.GetType(site.SiteTemplateType); + siteTemplateType = Type.GetType(site.SiteTemplateType); if (siteTemplateType != null) { - var siteTemplateObject = ActivatorUtilities.CreateInstance(_serviceProvider, siteTemplateType); + siteTemplateObject = ActivatorUtilities.CreateInstance(_serviceProvider, siteTemplateType); List pageTemplates = ((ISiteTemplate) siteTemplateObject).CreateSite(site); if (pageTemplates != null && pageTemplates.Count > 0) { CreatePages(site, pageTemplates, null); } } - - // create admin pages - CreatePages(site, CreateAdminPages(), null); } public void CreatePages(Site site, List pageTemplates, Alias alias) @@ -572,725 +575,5 @@ namespace Oqtane.Repository } } } - - private List CreateAdminPages(List pageTemplates = null) - { - if (pageTemplates == null) pageTemplates = new List(); - - // user pages - pageTemplates.Add(new PageTemplate - { - Name = "Login", - Parent = "", - Path = "login", - Icon = Icons.LockLocked, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Register", - Parent = "", - Path = "register", - Icon = Icons.Person, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Reset", - Parent = "", - Path = "reset", - Icon = Icons.Person, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Profile", - Parent = "", - Path = "profile", - Icon = Icons.Person, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Registered, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Registered, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Search", - Parent = "", - Path = "search", - Icon = "oi oi-magnifying-glass", - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default, - PermissionList = new List { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - } - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Not Found", - Parent = "", - Path = "404", - Icon = Icons.X, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Default, - PermissionList = new List { - new Permission(PermissionNames.View, RoleNames.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "

The page you requested does not exist or you do not have sufficient rights to view it.

" - } - } - }); - - // admin pages - pageTemplates.Add(new PageTemplate - { - Name = "Admin", - Parent = "", - Path = "admin", - Icon = "", - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Site Settings", - Parent = "Admin", - Order = 1, - Path = "admin/site", - Icon = Icons.Home, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Page Management", - Parent = "Admin", - Order = 3, - Path = "admin/pages", - Icon = Icons.Layers, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.View, RoleNames.Registered, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "User Management", - Parent = "Admin", - Order = 5, - Path = "admin/users", - Icon = Icons.People, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Profile Management", - Parent = "Admin", - Order = 7, - Path = "admin/profiles", - Icon = Icons.Person, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Role Management", - Parent = "Admin", - Order = 9, - Path = "admin/roles", - Icon = Icons.LockLocked, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "File Management", - Parent = "Admin", - Order = 11, - Path = "admin/files", - Icon = Icons.File, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Recycle Bin", - Parent = "Admin", - Order = 13, - Path = "admin/recyclebin", - Icon = Icons.Trash, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Url Mappings", - Parent = "Admin", - Order = 15, - Path = "admin/urlmappings", - Icon = Icons.LinkBroken, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - pageTemplates.Add(new PageTemplate - { - Name = "Visitor Management", - Parent = "Admin", - Order = 17, - Path = "admin/visitors", - Icon = Icons.Eye, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - - // host pages - pageTemplates.Add(new PageTemplate - { - Name = "Event Log", - Parent = "Admin", - Order = 19, - Path = "admin/log", - Icon = Icons.MagnifyingGlass, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Site Management", - Parent = "Admin", - Order = 21, - Path = "admin/sites", - Icon = Icons.Globe, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Module Management", - Parent = "Admin", - Order = 23, - Path = "admin/modules", - Icon = Icons.Browser, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Theme Management", - Parent = "Admin", - Order = 25, - Path = "admin/themes", - Icon = Icons.Brush, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Language Management", - Parent = "Admin", - Order = 27, - Path = "admin/languages", - Icon = Icons.Text, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Scheduled Jobs", - Parent = "Admin", - Order = 29, - Path = "admin/jobs", - Icon = Icons.Timer, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "Sql Management", - Parent = "Admin", - Order = 31, - Path = "admin/sql", - Icon = Icons.Spreadsheet, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "System Info", - Parent = "Admin", - Order = 33, - Path = "admin/system", - Icon = Icons.MedicalCross, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - pageTemplates.Add(new PageTemplate - { - Name = "System Update", - Parent = "Admin", - Order = 35, - Path = "admin/update", - Icon = Icons.Aperture, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule - { - ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Default, - PermissionList = new List - { - new Permission(PermissionNames.View, RoleNames.Host, true), - new Permission(PermissionNames.Edit, RoleNames.Host, true) - }, - Content = "" - } - } - }); - - return pageTemplates; - } } } diff --git a/Oqtane.Server/Repository/SiteTemplateRepository.cs b/Oqtane.Server/Repository/SiteTemplateRepository.cs index 04540e72..db16600c 100644 --- a/Oqtane.Server/Repository/SiteTemplateRepository.cs +++ b/Oqtane.Server/Repository/SiteTemplateRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -35,17 +35,23 @@ namespace Oqtane.Repository private List LoadSiteTemplatesFromAssembly(List siteTemplates, Assembly assembly) { - SiteTemplate siteTemplate; Type[] siteTemplateTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ISiteTemplate))).ToArray(); foreach (Type siteTemplateType in siteTemplateTypes) { var siteTemplateObject = ActivatorUtilities.CreateInstance(_serviceProvider, siteTemplateType); - siteTemplate = new SiteTemplate + if (siteTemplateObject != null) { - Name = (string)siteTemplateType.GetProperty("Name")?.GetValue(siteTemplateObject), - TypeName = Utilities.GetFullTypeName(siteTemplateType.AssemblyQualifiedName) - }; - siteTemplates.Add(siteTemplate); + var typename = Utilities.GetFullTypeName(siteTemplateType.AssemblyQualifiedName); + var name = (string)siteTemplateType.GetProperty("Name")?.GetValue(siteTemplateObject); + if (typename != Constants.AdminSiteTemplate && !string.IsNullOrEmpty(name)) + { + siteTemplates.Add(new SiteTemplate + { + Name = name, + TypeName = typename + }); + } + } } return siteTemplates; } diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 979f0872..5664a8ed 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -33,6 +33,7 @@ namespace Oqtane.Shared public const string PageManagementModule = "Oqtane.Modules.Admin.Pages, Oqtane.Client"; public const string ErrorModule = "Oqtane.Modules.Admin.Error.{Action}, Oqtane.Client"; + public const string AdminSiteTemplate = "Oqtane.SiteTemplates.AdminSiteTemplate, Oqtane.Server"; public const string DefaultSiteTemplate = "Oqtane.SiteTemplates.DefaultSiteTemplate, Oqtane.Server"; public static readonly string[] DefaultHostModuleTypes = new[] { "Upgrade", "Themes", "SystemInfo", "Sql", "Sites", "ModuleDefinitions", "Logs", "Jobs", "ModuleCreator" }; From cb728f65b3d7f8c470d2cc85b770c087073ee99c Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 10 Jun 2024 21:50:30 +0200 Subject: [PATCH 160/189] Update Project Templates from 8.0.5 - 8.0.6 --- .../External/Client/[Owner].Module.[Module].Client.csproj | 6 +++--- .../External/Server/[Owner].Module.[Module].Server.csproj | 8 ++++---- .../External/Client/[Owner].Theme.[Theme].Client.csproj | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) 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 8088f6cf..4181f2df 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,9 +13,9 @@
- - - + + + 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 1bdfa524..43859ecc 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 4a10f700..706882e3 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 @@ -9,12 +9,13 @@ [Owner].Theme.[Theme] [Owner] [Owner].Theme.[Theme].Client.Oqtane + true
- - - + + + From af3da7ca6ef404f584e90d943bb6d4e75d991ab6 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 10 Jun 2024 21:54:44 +0200 Subject: [PATCH 161/189] update to template.json files to align with Oqtane version --- Oqtane.Server/wwwroot/Modules/Templates/External/template.json | 2 +- Oqtane.Server/wwwroot/Themes/Templates/External/template.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json b/Oqtane.Server/wwwroot/Modules/Templates/External/template.json index 56ec6c9c..0579d8e6 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/template.json @@ -1,6 +1,6 @@ { "Title": "Default Module Template", "Type": "External", - "Version": "5.1.0", + "Version": "5.2.0", "Namespace": "[Owner].Module.[Module]" } diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json b/Oqtane.Server/wwwroot/Themes/Templates/External/template.json index f799eb88..bf1ec318 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/template.json @@ -1,6 +1,6 @@ { "Title": "Default Theme Template", "Type": "External", - "Version": "5.1.0", + "Version": "5.2.0", "Namespace": "[Owner].Theme.[Theme]" } From f013ee64a24d6969035645272408f8aae5751a0a Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Mon, 10 Jun 2024 22:10:39 +0200 Subject: [PATCH 162/189] Updated Module Template with ISearchable implementation --- .../Server/Manager/[Module]Manager.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs index 4be95aa7..9c263fef 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs @@ -1,16 +1,18 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using Oqtane.Modules; using Oqtane.Models; using Oqtane.Infrastructure; +using Oqtane.Interfaces; using Oqtane.Enums; using Oqtane.Repository; using [Owner].Module.[Module].Repository; namespace [Owner].Module.[Module].Manager { - public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable + public class [Module]Manager : MigratableModuleBase, IInstallable, IPortable, ISearchable { private readonly I[Module]Repository _[Module]Repository; private readonly IDBContextDependencies _DBContextDependencies; @@ -57,5 +59,24 @@ namespace [Owner].Module.[Module].Manager } } } - } + + public IList GetSearchContentList(Oqtane.Models.Module module, DateTime startTime) + { + var searchContentList = new List(); + + var [Module]s = _[Module]Repository.Get[Module]s(module.ModuleId); + foreach (var [Module] in [Module]s) + { + searchContentList.Add(new SearchContent + { + Title = module.Title, + Description = string.Empty, + Body = [Module].Name, + ModifiedTime = [Module].ModifiedOn + }); + } + + return searchContentList; + } +} } From 3508ae1e0a0278157a1f0b2829b669c8225eb86d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 16:17:05 -0400 Subject: [PATCH 163/189] remove List from method name to conform to Oqtane naming conventions --- Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs | 2 +- Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs | 2 +- Oqtane.Shared/Interfaces/ISearchable.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index 92c933be..c83ebaaf 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -59,7 +59,7 @@ namespace Oqtane.Managers.Search try { var moduleSearch = (ISearchable)ActivatorUtilities.CreateInstance(_serviceProvider, type); - var contentList = moduleSearch.GetSearchContentList(module, startTime.GetValueOrDefault(DateTime.MinValue)); + var contentList = moduleSearch.GetSearchContent(module, startTime.GetValueOrDefault(DateTime.MinValue)); if(contentList != null) { foreach(var searchContent in contentList) diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index e5cfb8de..98dbce98 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -48,7 +48,7 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } - public IList GetSearchContentList(Module module, DateTime startDate) + public IList GetSearchContent(Module module, DateTime startDate) { var searchContentList = new List(); diff --git a/Oqtane.Shared/Interfaces/ISearchable.cs b/Oqtane.Shared/Interfaces/ISearchable.cs index c0a4c00e..4193f345 100644 --- a/Oqtane.Shared/Interfaces/ISearchable.cs +++ b/Oqtane.Shared/Interfaces/ISearchable.cs @@ -9,6 +9,6 @@ namespace Oqtane.Interfaces { public interface ISearchable { - public IList GetSearchContentList(Module module, DateTime startTime); + public IList GetSearchContent(Module module, DateTime startTime); } } From 7548c52e21ca9b0c335135d4c04e2dc3c77fb74f Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 16:22:58 -0400 Subject: [PATCH 164/189] remove unnecessary using statements --- Oqtane.Shared/Interfaces/ISearchable.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Oqtane.Shared/Interfaces/ISearchable.cs b/Oqtane.Shared/Interfaces/ISearchable.cs index 4193f345..3a3d58a2 100644 --- a/Oqtane.Shared/Interfaces/ISearchable.cs +++ b/Oqtane.Shared/Interfaces/ISearchable.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Oqtane.Models; namespace Oqtane.Interfaces From 0d493b32501989d1d899b19ff0080fa9084445a6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 16:47:32 -0400 Subject: [PATCH 165/189] fix issue with primary key on SearchContentWord table --- .../Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs index 5721b6f5..05ab7241 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentWordEntityBuilder.cs @@ -8,7 +8,7 @@ namespace Oqtane.Migrations.EntityBuilders public class SearchContentWordEntityBuilder : BaseEntityBuilder { private const string _entityTableName = "SearchContentWord"; - private readonly PrimaryKey _primaryKey = new("PK_SearchContentWord", x => x.SearchWordId); + private readonly PrimaryKey _primaryKey = new("PK_SearchContentWord", x => x.SearchContentWordId); private readonly ForeignKey _foreignKey1 = new("FK_SearchContentWord_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade); private readonly ForeignKey _foreignKey2 = new("FK_SearchContentWord_SearchWord", x => x.SearchWordId, "SearchWord", "SearchWordId", ReferentialAction.Cascade); From 59fed7dda8f39cdb21dd46b006e92559adb985e5 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 16:50:33 -0400 Subject: [PATCH 166/189] update app constant to 5.2.0 --- Oqtane.Shared/Shared/Constants.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 5664a8ed..3abb1ed5 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 = "5.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"; + public static readonly string Version = "5.2.0"; + 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"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; From 8ce07ced9e2de787381c83522d61b3ade97070c7 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 10 Jun 2024 17:17:20 -0400 Subject: [PATCH 167/189] change IList to List for consistency with rest of framework --- Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs | 2 +- Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs | 2 +- Oqtane.Shared/Interfaces/ISearchable.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index c83ebaaf..8545e86e 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -59,7 +59,7 @@ namespace Oqtane.Managers.Search try { var moduleSearch = (ISearchable)ActivatorUtilities.CreateInstance(_serviceProvider, type); - var contentList = moduleSearch.GetSearchContent(module, startTime.GetValueOrDefault(DateTime.MinValue)); + var contentList = moduleSearch.GetSearchContents(module, startTime.GetValueOrDefault(DateTime.MinValue)); if(contentList != null) { foreach(var searchContent in contentList) diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 98dbce98..4c184e4b 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -48,7 +48,7 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } - public IList GetSearchContent(Module module, DateTime startDate) + public List GetSearchContents(Module module, DateTime startDate) { var searchContentList = new List(); diff --git a/Oqtane.Shared/Interfaces/ISearchable.cs b/Oqtane.Shared/Interfaces/ISearchable.cs index 3a3d58a2..35019c01 100644 --- a/Oqtane.Shared/Interfaces/ISearchable.cs +++ b/Oqtane.Shared/Interfaces/ISearchable.cs @@ -6,6 +6,6 @@ namespace Oqtane.Interfaces { public interface ISearchable { - public IList GetSearchContent(Module module, DateTime startTime); + public List GetSearchContents(Module module, DateTime startTime); } } From 0e0d404997fb7f22b9a68417020c6cb1cc05f7b0 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 11 Jun 2024 09:11:25 +0200 Subject: [PATCH 168/189] Update [Module]Manager.cs --- .../Templates/External/Server/Manager/[Module]Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs index 9c263fef..f252cfbc 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs @@ -60,7 +60,7 @@ namespace [Owner].Module.[Module].Manager } } - public IList GetSearchContentList(Oqtane.Models.Module module, DateTime startTime) + public List GetSearchContentList(Oqtane.Models.Module module, DateTime startTime) { var searchContentList = new List(); From 5ec190225d1ed9c12f178e5fc463eca85cc74b4a Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 11 Jun 2024 09:51:48 +0200 Subject: [PATCH 169/189] Upgrade to Bootstrap 5.3.3 Oqtane ThemeInfo updated Blazor Default.razor updated --- Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor | 8 ++++++-- Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index 9f3b8cd9..f4b5156d 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -38,9 +38,13 @@ public override List Resources => new List() { // obtained from https://cdnjs.com/libraries - new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css", Integrity = "sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==", CrossOrigin = "anonymous" }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css", + Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==", + CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" }, - new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body } + new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js", + Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==", + CrossOrigin = "anonymous", Location = ResourceLocation.Body }, }; } diff --git a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs index 931a83ad..a533d45d 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs +++ b/Oqtane.Client/Themes/OqtaneTheme/ThemeInfo.cs @@ -17,9 +17,13 @@ namespace Oqtane.Themes.OqtaneTheme Resources = new List() { // obtained from https://cdnjs.com/libraries - new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.2/cyborg/bootstrap.min.css", Integrity = "sha512-RfNxVfFNFgqk9MXO4TCKXYXn9hgc+keHCg3xFFGbnp2q7Cifda+YYzMTDHwsQtNx4DuqIMgfvZead7XOtB9CDQ==", CrossOrigin = "anonymous" }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css", + Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==", + CrossOrigin = "anonymous" }, new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" }, - new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js", Integrity = "sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A==", CrossOrigin = "anonymous", Location = ResourceLocation.Body } + new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js", + Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==", + CrossOrigin = "anonymous", Location = ResourceLocation.Body }, } }; } From 1d7fcfdaa1db6ac0e6c80f406d5a441ee3589be8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 11 Jun 2024 07:37:02 -0400 Subject: [PATCH 170/189] fix ISearchable method name in module template --- .../Templates/External/Server/Manager/[Module]Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs index f252cfbc..dcd9e432 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs @@ -60,7 +60,7 @@ namespace [Owner].Module.[Module].Manager } } - public List GetSearchContentList(Oqtane.Models.Module module, DateTime startTime) + public List GetSearchContents(Oqtane.Models.Module module, DateTime startTime) { var searchContentList = new List(); From b3706574de99708c49f71c5110e3424f752f454b Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 11 Jun 2024 08:38:51 -0400 Subject: [PATCH 171/189] removed IHttpContextAccessor as it shoudl not be used in Blazor, fixed form handling, added Reset button to consistent with other framework search options, used SharedLocalizer, removed unused localization keys --- .../Modules/Admin/SearchResults/Index.razor | 43 ++---- .../Modules/Admin/SearchResults/ModuleInfo.cs | 2 - .../Admin/SearchResults/Settings.razor | 48 ------- .../Modules/Admin/SearchResults/Index.resx | 33 +---- .../Modules/Admin/SearchResults/Settings.resx | 123 ------------------ 5 files changed, 15 insertions(+), 234 deletions(-) delete mode 100644 Oqtane.Client/Modules/Admin/SearchResults/Settings.razor delete mode 100644 Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index 3b25090e..01039a51 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -1,24 +1,25 @@ -@using Microsoft.AspNetCore.Http @using Oqtane.Services @using System.Net @namespace Oqtane.Modules.Admin.SearchResults @inherits ModuleBase +@inject NavigationManager NavigationManager @inject ISearchResultsService SearchResultsService @inject IStringLocalizer Localizer -@inject IHttpContextAccessor HttpContext +@inject IStringLocalizer SharedLocalizer
- +
- @Localizer["SearchPrefix"] + @Localizer["SearchLabel"] - + @bind="@_keywords"> + + @SharedLocalizer["Reset"]
@@ -37,9 +38,6 @@ { @@ -65,24 +63,18 @@
@code { public override string RenderMode => RenderModes.Static; - private const int SearchDefaultPageSize = 10; private SearchSortDirections _searchSortDirection = SearchSortDirections.Descending; //default sort by private SearchSortFields _searchSortField = SearchSortFields.Relevance; private string _keywords; private bool _loading; private SearchResults _searchResults; - private int _currentPage = 0; - private int _pageSize = SearchDefaultPageSize; - private int _displayPages = 7; + + [SupplyParameterFromForm(FormName = "SearchInputForm")] + public string KeyWords { get => ""; set => _keywords = value; } protected override async Task OnInitializedAsync() { - if (ModuleState.Settings.ContainsKey("PageSize")) - { - _pageSize = int.Parse(ModuleState.Settings["PageSize"]); - } - if (PageState.QueryString.ContainsKey("q")) { _keywords = WebUtility.UrlDecode(PageState.QueryString["q"]); @@ -94,20 +86,9 @@ } } - private async Task Search() + private void Search() { - _keywords = HttpContext.HttpContext.Request.Form["keywords"]; - if (string.IsNullOrEmpty(_keywords)) - { - AddModuleMessage(Localizer["MissingKeywords"], MessageType.Warning); - } - else - { - ClearModuleMessage(); - - _currentPage = 0; - await PerformSearch(); - } + NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}")); } private async Task PerformSearch() diff --git a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs index 97a6835e..a65123d4 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs +++ b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs @@ -13,8 +13,6 @@ namespace Oqtane.Modules.Admin.SearchResults Name = "Search Results", Description = "Display Search Results", Version = Constants.Version, - ServerManagerType = "", - SettingsType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client", Resources = new List() { new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Settings.razor b/Oqtane.Client/Modules/Admin/SearchResults/Settings.razor deleted file mode 100644 index 1e98f0f6..00000000 --- a/Oqtane.Client/Modules/Admin/SearchResults/Settings.razor +++ /dev/null @@ -1,48 +0,0 @@ -@namespace Oqtane.Modules.Admin.SearchResults -@inherits ModuleBase -@implements Oqtane.Interfaces.ISettingsControl -@inject ISettingService SettingService -@inject IStringLocalizer Localizer -@inject IStringLocalizer SharedLocalizer - -
-
- -
- -
-
-
- -@code { - private const string SearchDefaultPageSize = "10"; - - private string resourceType = "Oqtane.Modules.Admin.SearchResults.Settings, Oqtane.Client"; // for localization - private string _pageSize; - - protected override void OnInitialized() - { - try - { - _pageSize = SettingService.GetSetting(ModuleState.Settings, "PageSize", SearchDefaultPageSize); - } - catch (Exception ex) - { - AddModuleMessage(ex.Message, MessageType.Error); - } - } - - public async Task UpdateSettings() - { - try - { - var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); - settings = SettingService.SetSetting(settings, "PageSize", _pageSize); - await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); - } - catch (Exception ex) - { - AddModuleMessage(ex.Message, MessageType.Error); - } - } -} diff --git a/Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx index bd07f3f8..d1d1d8fa 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Index.resx @@ -117,40 +117,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ascending - - - Descending - - - Please provide the search keywords. - - - Modification Time - - No results found. + No Content Matches The Criteria Provided - - Relevance - - - Search + + Search: Search - - Search: - - - Sort Direction - - - Sort By - - - Title - \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx deleted file mode 100644 index 1c48707d..00000000 --- a/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Page Size - - \ No newline at end of file From b4aa73fc6439d8841b5376c3ac4364e4f815eda8 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Tue, 11 Jun 2024 15:21:12 +0200 Subject: [PATCH 172/189] Updated SearchResults Categories to Admin --- Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs index a65123d4..7594a524 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs +++ b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs @@ -13,6 +13,7 @@ namespace Oqtane.Modules.Admin.SearchResults Name = "Search Results", Description = "Display Search Results", Version = Constants.Version, + Categories = "Admin", Resources = new List() { new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } From 27356ef7471651c4930698b8f19e33abceab93eb Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 11 Jun 2024 10:38:44 -0400 Subject: [PATCH 173/189] use List instead of IList, remove "List" from method names. remove unnecessary using statements --- .../Managers/Search/ModuleSearchIndexManager.cs | 2 +- .../Managers/Search/PageSearchIndexManager.cs | 6 +----- .../Managers/Search/SearchIndexManagerBase.cs | 2 +- Oqtane.Server/Providers/DatabaseSearchProvider.cs | 6 +++--- .../Repository/SearchContentRepository.cs | 14 +++++++------- Oqtane.Server/Services/SearchService.cs | 8 +++----- Oqtane.Shared/Interfaces/ISearchIndexManager.cs | 5 +---- Oqtane.Shared/Models/SearchContent.cs | 4 ++-- Oqtane.Shared/Models/SearchQuery.cs | 2 +- Oqtane.Shared/Models/SearchResults.cs | 2 +- Oqtane.Shared/Shared/SearchUtils.cs | 4 ++-- 11 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index 8545e86e..23971383 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -36,7 +36,7 @@ namespace Oqtane.Managers.Search public override int Priority => ModuleSearchIndexManagerPriority; - public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) + public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) { var pageModules = _pageModuleRepostory.GetPageModules(siteId).DistinctBy(i => i.ModuleId); var searchContentList = new List(); diff --git a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs index 07601905..7e6e593c 100644 --- a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Mvc.RazorPages; -using System.Reflection.Metadata; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Oqtane.Interfaces; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; @@ -35,7 +31,7 @@ namespace Oqtane.Managers.Search public override int Priority => PageSearchIndexManagerPriority; - public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) + public override int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError) { var startTimeValue = startTime.GetValueOrDefault(DateTime.MinValue); var pages = _pageRepository.GetPages(siteId).Where(i => i.ModifiedOn >= startTimeValue); diff --git a/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs index 779a950d..a3898967 100644 --- a/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs +++ b/Oqtane.Server/Managers/Search/SearchIndexManagerBase.cs @@ -23,7 +23,7 @@ namespace Oqtane.Managers.Search public abstract string Name { get; } - public abstract int IndexContent(int siteId, DateTime? startDate, Action> processSearchContent, Action handleError); + public abstract int IndexContent(int siteId, DateTime? startDate, Action> processSearchContent, Action handleError); public virtual bool IsIndexEnabled(int siteId) { diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index 24e8a7da..3e09638b 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -147,7 +147,7 @@ namespace Oqtane.Providers private float CalculateScore(SearchContent searchContent, SearchQuery searchQuery) { var score = 0f; - foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords)) { score += searchContent.SearchContentWords.Where(i => i.SearchWord.Word.StartsWith(keyword)).Sum(i => i.Count); } @@ -159,7 +159,7 @@ namespace Oqtane.Providers { var content = $"{searchContent.Title} {searchContent.Description} {searchContent.Body}"; var snippet = string.Empty; - foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords)) { if (!string.IsNullOrWhiteSpace(keyword) && content.Contains(keyword, StringComparison.OrdinalIgnoreCase)) { @@ -191,7 +191,7 @@ namespace Oqtane.Providers snippet = content.Substring(0, searchQuery.BodySnippetLength); } - foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords)) { snippet = Regex.Replace(snippet, $"({keyword})", $"$1", RegexOptions.IgnoreCase); } diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index 5bc9eca0..2d4c3bed 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -20,7 +20,7 @@ namespace Oqtane.Repository public async Task> GetSearchContentsAsync(SearchQuery searchQuery) { using var db = _dbContextFactory.CreateDbContext(); - var searchContentList = db.SearchContent.AsNoTracking() + var searchContents = db.SearchContent.AsNoTracking() .Include(i => i.SearchContentProperties) .Include(i => i.SearchContentWords) .ThenInclude(w => w.SearchWord) @@ -28,33 +28,33 @@ namespace Oqtane.Repository if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) { - searchContentList = searchContentList.Where(i => searchQuery.EntityNames.Contains(i.EntityName)); + searchContents = searchContents.Where(i => searchQuery.EntityNames.Contains(i.EntityName)); } if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) { - searchContentList = searchContentList.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); } if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) { - searchContentList = searchContentList.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); } if (searchQuery.Properties != null && searchQuery.Properties.Any()) { foreach (var property in searchQuery.Properties) { - searchContentList = searchContentList.Where(i => i.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value)); + searchContents = searchContents.Where(i => i.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value)); } } var filteredContentList = new List(); if (!string.IsNullOrEmpty(searchQuery.Keywords)) { - foreach (var keyword in SearchUtils.GetKeywordsList(searchQuery.Keywords)) + foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords)) { - filteredContentList.AddRange(await searchContentList.Where(i => i.SearchContentWords.Any(w => w.SearchWord.Word.StartsWith(keyword))).ToListAsync()); + filteredContentList.AddRange(await searchContents.Where(i => i.SearchContentWords.Any(w => w.SearchWord.Word.StartsWith(keyword))).ToListAsync()); } } diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index 8d3fe4ef..f92757da 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; -using HtmlAgilityPack; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -143,7 +141,7 @@ namespace Oqtane.Services _tenantManager.SetAlias(alias); } - private IList GetSearchIndexManagers(Action initManager) + private List GetSearchIndexManagers(Action initManager) { var managers = new List(); var managerTypes = AppDomain.CurrentDomain.GetAssemblies() @@ -160,7 +158,7 @@ namespace Oqtane.Services return managers.OrderBy(i => i.Priority).ToList(); } - private IList GetSearchResultManagers() + private List GetSearchResultManagers() { var managers = new List(); var managerTypes = AppDomain.CurrentDomain.GetAssemblies() @@ -176,7 +174,7 @@ namespace Oqtane.Services return managers.ToList(); } - private void SaveSearchContent(IList searchContentList) + private void SaveSearchContent(List searchContentList) { if(searchContentList.Any()) { diff --git a/Oqtane.Shared/Interfaces/ISearchIndexManager.cs b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs index 0a4123f8..576176fd 100644 --- a/Oqtane.Shared/Interfaces/ISearchIndexManager.cs +++ b/Oqtane.Shared/Interfaces/ISearchIndexManager.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Web; using Oqtane.Models; namespace Oqtane.Services @@ -15,6 +12,6 @@ namespace Oqtane.Services bool IsIndexEnabled(int siteId); - int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError); + int IndexContent(int siteId, DateTime? startTime, Action> processSearchContent, Action handleError); } } diff --git a/Oqtane.Shared/Models/SearchContent.cs b/Oqtane.Shared/Models/SearchContent.cs index f9ecafa4..2a242d7a 100644 --- a/Oqtane.Shared/Models/SearchContent.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -31,9 +31,9 @@ namespace Oqtane.Models public string AdditionalContent { get; set; } - public IList SearchContentProperties { get; set; } + public List SearchContentProperties { get; set; } - public IList SearchContentWords { get; set; } + public List SearchContentWords { get; set; } public override string ToString() { diff --git a/Oqtane.Shared/Models/SearchQuery.cs b/Oqtane.Shared/Models/SearchQuery.cs index cf03e8c1..70e3880d 100644 --- a/Oqtane.Shared/Models/SearchQuery.cs +++ b/Oqtane.Shared/Models/SearchQuery.cs @@ -14,7 +14,7 @@ namespace Oqtane.Models public string Keywords { get; set; } - public IList EntityNames { get; set; } = new List(); + public List EntityNames { get; set; } = new List(); public DateTime BeginModifiedTimeUtc { get; set; } diff --git a/Oqtane.Shared/Models/SearchResults.cs b/Oqtane.Shared/Models/SearchResults.cs index 5642468c..0c167cbd 100644 --- a/Oqtane.Shared/Models/SearchResults.cs +++ b/Oqtane.Shared/Models/SearchResults.cs @@ -4,7 +4,7 @@ namespace Oqtane.Models { public class SearchResults { - public IList Results { get; set; } + public List Results { get; set; } public int TotalResults { get; set; } } diff --git a/Oqtane.Shared/Shared/SearchUtils.cs b/Oqtane.Shared/Shared/SearchUtils.cs index d49c932d..bf5f5986 100644 --- a/Oqtane.Shared/Shared/SearchUtils.cs +++ b/Oqtane.Shared/Shared/SearchUtils.cs @@ -4,14 +4,14 @@ namespace Oqtane.Shared { public sealed class SearchUtils { - private static readonly IList _systemPages; + private static readonly List _systemPages; static SearchUtils() { _systemPages = new List { "login", "register", "profile", "404", "search" }; } - public static IList GetKeywordsList(string keywords) + public static List GetKeywords(string keywords) { var keywordsList = new List(); if(!string.IsNullOrEmpty(keywords)) From af62a89a6b31bc3f7ca7a740db8836d359601e12 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 24 Jun 2024 10:59:34 -0400 Subject: [PATCH 174/189] fix #4339 - add page not redirecting to correct url in subsite --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index b4bc912e..05f8209a 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -449,7 +449,7 @@ await logger.LogInformation("Page Added {Page}", page); if (!string.IsNullOrEmpty(PageState.ReturnUrl)) { - NavigationManager.NavigateTo(page.Path, true); // redirect to page added and reload + NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload } else { From 73abc511a8bb7208c476640bb377834206811878 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 24 Jun 2024 16:26:55 -0400 Subject: [PATCH 175/189] fix #4349 - adding module in subsite in Interactive render mode --- Oqtane.Client/UI/SiteRouter.razor | 4 ++-- Oqtane.Server/Services/SiteService.cs | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 79e2bef6..222344ae 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -213,7 +213,7 @@ return; } - if (PageState == null || refresh || PageState.Page.Path != route.PagePath) + if (refresh || PageState == null || PageState.Page.Path != route.PagePath) { page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); } @@ -275,7 +275,7 @@ } // get modules for current page - if (PageState.Modules == null || (PageState.Modules.Any() && PageState.Modules.First().PageId != page.PageId)) + if (refresh || PageState.Modules == null || !PageState.Modules.Any() || PageState.Modules.First().PageId != page.PageId) { modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId); } diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 2c9c1786..48eb6c37 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -62,7 +62,8 @@ namespace Oqtane.Services public async Task GetSiteAsync(int siteId) { - var site = await _cache.GetOrCreateAsync($"site:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => + var alias = _tenantManager.GetAlias(); + var site = await _cache.GetOrCreateAsync($"site:{alias.SiteKey}", async entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(30); return await GetSite(siteId); @@ -240,7 +241,8 @@ namespace Oqtane.Services public async Task> GetModulesAsync(int siteId, int pageId) { - var sitemodules = await _cache.GetOrCreateAsync($"modules:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => + var alias = _tenantManager.GetAlias(); + var sitemodules = await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(30); return await GetModulesAsync(siteId); @@ -259,7 +261,8 @@ namespace Oqtane.Services public async Task> GetModulesAsync(int siteId) { - return await _cache.GetOrCreateAsync($"modules:{_accessor.HttpContext.GetAlias().SiteKey}", async entry => + var alias = _tenantManager.GetAlias(); + return await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(30); return await GetModules(siteId); From 03f081f3f4daf04e3f64002fd5f8d732d049833e Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 26 Jun 2024 09:09:06 -0400 Subject: [PATCH 176/189] fix #4353 - add defensive logic when sending notifications and improve performance --- Oqtane.Server/Infrastructure/LogManager.cs | 20 ++++++++++--------- .../Interfaces/IUserRoleRepository.cs | 1 + .../Repository/UserRoleRepository.cs | 15 +++++++++++--- Oqtane.Shared/Models/Alias.cs | 4 ++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Oqtane.Server/Infrastructure/LogManager.cs b/Oqtane.Server/Infrastructure/LogManager.cs index 27d4df50..59c51ccd 100644 --- a/Oqtane.Server/Infrastructure/LogManager.cs +++ b/Oqtane.Server/Infrastructure/LogManager.cs @@ -203,19 +203,21 @@ namespace Oqtane.Infrastructure } if (Enum.Parse(log.Level) >= notifylevel) { + var subject = $"Site {log.Level} Notification"; + string body = $"Log Message: {log.Message}"; + var alias = _tenantManager.GetAlias(); - foreach (var userrole in _userRoles.GetUserRoles(log.SiteId.Value)) + if (alias != null) { - if (userrole.Role.Name == RoleNames.Host) - { - var subject = $"{alias.Name} Site {log.Level} Notification"; - var url = $"{_accessor.HttpContext.Request.Scheme}://{alias.Name}/admin/log?id={log.LogId}"; - string body = $"Log Message: {log.Message}

Please visit {url} for more information"; - var notification = new Notification(log.SiteId.Value, userrole.User, subject, body); - _notifications.AddNotification(notification); - } + subject = $"{alias.Name} Site {log.Level} Notification"; + body = $"Log Message: {log.Message}

Please visit {alias.Protocol}://{alias.Name}/admin/log?id={log.LogId} for more information"; } + foreach (var userrole in _userRoles.GetUserRoles(RoleNames.Host, log.SiteId.Value)) + { + var notification = new Notification(log.SiteId.Value, userrole.User, subject, body); + _notifications.AddNotification(notification); + } } } } diff --git a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs index 7bed5f51..7db22b4e 100644 --- a/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/IUserRoleRepository.cs @@ -7,6 +7,7 @@ namespace Oqtane.Repository { IEnumerable GetUserRoles(int siteId); IEnumerable GetUserRoles(int userId, int siteId); + IEnumerable GetUserRoles(string roleName, int siteId); UserRole AddUserRole(UserRole userRole); UserRole UpdateUserRole(UserRole userRole); UserRole GetUserRole(int userRoleId); diff --git a/Oqtane.Server/Repository/UserRoleRepository.cs b/Oqtane.Server/Repository/UserRoleRepository.cs index 68c942ad..57a10031 100644 --- a/Oqtane.Server/Repository/UserRoleRepository.cs +++ b/Oqtane.Server/Repository/UserRoleRepository.cs @@ -23,16 +23,25 @@ namespace Oqtane.Repository return db.UserRole .Include(item => item.Role) // eager load roles .Include(item => item.User) // eager load users - .Where(item => item.Role.SiteId == siteId || item.Role.SiteId == null).ToList(); + .Where(item => item.Role.SiteId == siteId || item.Role.SiteId == null || siteId == -1).ToList(); } public IEnumerable GetUserRoles(int userId, int siteId) { using var db = _dbContextFactory.CreateDbContext(); - return db.UserRole.Where(item => item.UserId == userId) + return db.UserRole .Include(item => item.Role) // eager load roles .Include(item => item.User) // eager load users - .Where(item => item.Role.SiteId == siteId || item.Role.SiteId == null || siteId == -1).ToList(); + .Where(item => (item.Role.SiteId == siteId || item.Role.SiteId == null || siteId == -1) && item.UserId == userId).ToList(); + } + + public IEnumerable GetUserRoles(string roleName, int siteId) + { + using var db = _dbContextFactory.CreateDbContext(); + return db.UserRole + .Include(item => item.Role) // eager load roles + .Include(item => item.User) // eager load users + .Where(item => (item.Role.SiteId == siteId || item.Role.SiteId == null || siteId == -1) && item.Role.Name == roleName).ToList(); } public UserRole AddUserRole(UserRole userRole) diff --git a/Oqtane.Shared/Models/Alias.cs b/Oqtane.Shared/Models/Alias.cs index dcad9e95..92ae3a90 100644 --- a/Oqtane.Shared/Models/Alias.cs +++ b/Oqtane.Shared/Models/Alias.cs @@ -68,10 +68,10 @@ namespace Oqtane.Models } /// - /// Protocol for the request from which the alias was resolved (ie. http:// or https:// ) + /// Protocol for the request from which the alias was resolved (ie. http or https ) /// [NotMapped] - public string Protocol { get; set; } + public string Protocol { get; set; } = "https"; // default value /// /// Base Url for static resources (note that this will only be set for remote clients) From 791a3b67e6de464e7b99fac5b923c45251c91daa Mon Sep 17 00:00:00 2001 From: iJungleboy Date: Thu, 27 Jun 2024 19:47:13 +0200 Subject: [PATCH 177/189] fix docfx build issues --- Oqtane.Client/UI/ModuleInstance.placeholder.cs | 13 +++++++++++++ .../UI/RenderModeBoundary.placeholder.cs | 13 +++++++++++++ Oqtane.Server/Components/_Placeholder.cs | 16 ++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 Oqtane.Client/UI/ModuleInstance.placeholder.cs create mode 100644 Oqtane.Client/UI/RenderModeBoundary.placeholder.cs create mode 100644 Oqtane.Server/Components/_Placeholder.cs diff --git a/Oqtane.Client/UI/ModuleInstance.placeholder.cs b/Oqtane.Client/UI/ModuleInstance.placeholder.cs new file mode 100644 index 00000000..f5899b5e --- /dev/null +++ b/Oqtane.Client/UI/ModuleInstance.placeholder.cs @@ -0,0 +1,13 @@ +// This is just a placeholder file +// It is necessary for the documentation to successfully build this project. +// Reason is that docfx will run the .net compiler and find references +// to this class in the project. +// But since the real class is just a .razor file, ATM docfx will fail. +// +// Note added 2024-06-27 by @iJungleboy. +// We hope that as .net and docfx improve, the razor-compiler will work in that scenario +// as well, and this file can be removed. + +namespace Oqtane.UI; + +public partial class ModuleInstance; diff --git a/Oqtane.Client/UI/RenderModeBoundary.placeholder.cs b/Oqtane.Client/UI/RenderModeBoundary.placeholder.cs new file mode 100644 index 00000000..838231d8 --- /dev/null +++ b/Oqtane.Client/UI/RenderModeBoundary.placeholder.cs @@ -0,0 +1,13 @@ +// This is just a placeholder file +// It is necessary for the documentation to successfully build this project. +// Reason is that docfx will run the .net compiler and find references +// to this class in the project. +// But since the real class is just a .razor file, ATM docfx will fail. +// +// Note added 2024-06-27 by @iJungleboy. +// We hope that as .net and docfx improve, the razor-compiler will work in that scenario +// as well, and this file can be removed. + +namespace Oqtane.UI; + +public partial class RenderModeBoundary; diff --git a/Oqtane.Server/Components/_Placeholder.cs b/Oqtane.Server/Components/_Placeholder.cs new file mode 100644 index 00000000..570e95ac --- /dev/null +++ b/Oqtane.Server/Components/_Placeholder.cs @@ -0,0 +1,16 @@ +// This is just a placeholder file +// It is necessary for the documentation to successfully build this project. +// Reason is that docfx will run the .net compiler and find references +// to this class in the project. +// But since the real class is just a .razor file, ATM docfx will fail. +// +// Note added 2024-06-27 by @iJungleboy. +// We hope that as .net and docfx improve, the razor-compiler will work in that scenario +// as well, and this file can be removed. + +using Oqtane.Documentation; + +namespace Oqtane.Components; + +[PrivateApi] +public class _Placeholder; From 532890674ed0c6b2089c978c0715a1b74f0e0c1c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 27 Jun 2024 17:05:22 -0400 Subject: [PATCH 178/189] eager load Page associated to PageModule --- .../Repository/PageModuleRepository.cs | 20 ++++++++++++++----- Oqtane.Shared/Models/PageModule.cs | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Oqtane.Server/Repository/PageModuleRepository.cs b/Oqtane.Server/Repository/PageModuleRepository.cs index bc24ffa8..a6f81b69 100644 --- a/Oqtane.Server/Repository/PageModuleRepository.cs +++ b/Oqtane.Server/Repository/PageModuleRepository.cs @@ -29,7 +29,8 @@ namespace Oqtane.Repository { using var db = _dbContextFactory.CreateDbContext(); var pagemodules = db.PageModule - .Include(item => item.Module) // eager load modules + .Include(item => item.Module) // eager load module + .Include(item => item.Page) // eager load page .Where(item => item.Module.SiteId == siteId).ToList(); if (pagemodules.Any()) { @@ -70,12 +71,16 @@ namespace Oqtane.Repository PageModule pagemodule; if (tracking) { - pagemodule = db.PageModule.Include(item => item.Module) // eager load modules + pagemodule = db.PageModule + .Include(item => item.Module) // eager load module + .Include(item => item.Page) // eager load page .FirstOrDefault(item => item.PageModuleId == pageModuleId); } else { - pagemodule = db.PageModule.AsNoTracking().Include(item => item.Module) // eager load modules + pagemodule = db.PageModule.AsNoTracking() + .Include(item => item.Module) // eager load module + .Include(item => item.Page) // eager load page .FirstOrDefault(item => item.PageModuleId == pageModuleId); } if (pagemodule != null) @@ -90,7 +95,9 @@ namespace Oqtane.Repository public PageModule GetPageModule(int pageId, int moduleId) { using var db = _dbContextFactory.CreateDbContext(); - var pagemodule = db.PageModule.Include(item => item.Module) // eager load modules + var pagemodule = db.PageModule + .Include(item => item.Module) // eager load module + .Include(item => item.Page) // eager load page .SingleOrDefault(item => item.PageId == pageId && item.ModuleId == moduleId); if (pagemodule != null) { @@ -104,7 +111,9 @@ namespace Oqtane.Repository public void DeletePageModule(int pageModuleId) { using var db = _dbContextFactory.CreateDbContext(); - var pageModule = db.PageModule.Include(item => item.Module) // eager load modules + var pageModule = db.PageModule + .Include(item => item.Module) // eager load module + .Include(item => item.Page) // eager load page .SingleOrDefault(item => item.PageModuleId == pageModuleId); _settings.DeleteSettings(EntityNames.PageModule, pageModuleId); db.PageModule.Remove(pageModule); @@ -140,6 +149,7 @@ namespace Oqtane.Repository } } pageModule.Module.PermissionList = permissions?.ToList(); + return pageModule; } } diff --git a/Oqtane.Shared/Models/PageModule.cs b/Oqtane.Shared/Models/PageModule.cs index e3f0adb9..0e7c812f 100644 --- a/Oqtane.Shared/Models/PageModule.cs +++ b/Oqtane.Shared/Models/PageModule.cs @@ -59,8 +59,12 @@ namespace Oqtane.Models /// /// The itself. - /// TODO: todoc - unclear if this is always populated /// public Module Module { get; set; } + + /// + /// The itself. + /// + public Page Page { get; set; } } } From 22063248ca1a09a69a19c4b9fa44756629e2cf86 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 27 Jun 2024 17:18:41 -0400 Subject: [PATCH 179/189] reposition Search input in themes --- Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor | 2 +- Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index f4b5156d..07d9905e 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -18,9 +18,9 @@
+ -
diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index 344f5dd9..e00dd363 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -7,9 +7,9 @@
- - + +
From 0178e015e320edc137e1842a54d232f020dbafb0 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 28 Jun 2024 15:43:54 -0400 Subject: [PATCH 180/189] breaking search modifications into smaller PRs --- .../Search/ModuleSearchIndexManager.cs | 12 ++---- .../Managers/Search/PageSearchIndexManager.cs | 14 ++++--- .../SearchContentEntityBuilder.cs | 29 +++++++------ .../HtmlText/Manager/HtmlTextManager.cs | 3 +- .../Providers/DatabaseSearchProvider.cs | 16 +++---- .../Interfaces/ISearchContentRepository.cs | 2 +- .../Repository/SearchContentRepository.cs | 8 ++-- Oqtane.Server/Services/SearchService.cs | 20 +++++---- Oqtane.Shared/Models/SearchContent.cs | 42 +++++++++++++++---- 9 files changed, 89 insertions(+), 57 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index 23971383..d1fc3e74 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -95,19 +95,15 @@ namespace Oqtane.Managers.Search searchContent.EntityName = EntityNames.Module; } - if(searchContent.EntityId == 0) + if(string.IsNullOrEmpty(searchContent.EntityId)) { - searchContent.EntityId = pageModule.ModuleId; + searchContent.EntityId = pageModule.ModuleId.ToString(); } - if (searchContent.IsActive) - { - searchContent.IsActive = !pageModule.Module.IsDeleted; - } - if (searchContent.ModifiedTime == DateTime.MinValue) + if (searchContent.ContentModifiedOn == DateTime.MinValue) { - searchContent.ModifiedTime = pageModule.ModifiedOn; + searchContent.ContentModifiedOn = pageModule.ModifiedOn; } if (string.IsNullOrEmpty(searchContent.AdditionalContent)) diff --git a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs index 7e6e593c..9a74263b 100644 --- a/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/PageSearchIndexManager.cs @@ -48,16 +48,18 @@ namespace Oqtane.Managers.Search var searchContent = new SearchContent { - EntityName = EntityNames.Page, - EntityId = page.PageId, SiteId = page.SiteId, - ModifiedTime = page.ModifiedOn, - AdditionalContent = string.Empty, - Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}", + EntityName = EntityNames.Page, + EntityId = page.PageId.ToString(), Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name, Description = string.Empty, Body = $"{page.Name} {page.Title}", - IsActive = !page.IsDeleted && Utilities.IsPageModuleVisible(page.EffectiveDate, page.ExpiryDate) + Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}", + Permissions = $"{EntityNames.Page}:{page.PageId}", + ContentModifiedBy = page.ModifiedBy, + ContentModifiedOn = page.ModifiedOn, + AdditionalContent = string.Empty, + CreatedOn = DateTime.UtcNow }; if (searchContent.SearchContentProperties == null) diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs index 4cf2aa4a..c8082c77 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs @@ -2,11 +2,10 @@ using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Operations; using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; using Oqtane.Databases.Interfaces; -using Oqtane.Models; namespace Oqtane.Migrations.EntityBuilders { - public class SearchContentEntityBuilder : AuditableBaseEntityBuilder + public class SearchContentEntityBuilder : BaseEntityBuilder { private const string _entityTableName = "SearchContent"; private readonly PrimaryKey _primaryKey = new("PK_SearchContent", x => x.SearchContentId); @@ -20,30 +19,30 @@ namespace Oqtane.Migrations.EntityBuilders protected override SearchContentEntityBuilder BuildTable(ColumnsBuilder table) { SearchContentId = AddAutoIncrementColumn(table, "SearchContentId"); + SiteId = AddIntegerColumn(table, "SiteId"); EntityName = AddStringColumn(table, "EntityName", 50); EntityId = AddIntegerColumn(table, "EntityId"); - SiteId = AddIntegerColumn(table, "SiteId"); - Title = AddStringColumn(table, "Title", 255); + Title = AddStringColumn(table, "Title", 200); Description = AddMaxStringColumn(table, "Description"); Body = AddMaxStringColumn(table, "Body"); - Url = AddStringColumn(table, "Url", 255); - ModifiedTime = AddDateTimeColumn(table, "ModifiedTime"); - IsActive = AddBooleanColumn(table, "IsActive"); + Url = AddStringColumn(table, "Url", 500); + Permissions = AddStringColumn(table, "Permissions", 100); + ContentModifiedBy = AddStringColumn(table, "ContentModifiedBy", 256); + ContentModifiedOn = AddDateTimeColumn(table, "ContentModifiedOn"); AdditionalContent = AddMaxStringColumn(table, "AdditionalContent"); - - AddAuditableColumns(table); + CreatedOn = AddDateTimeColumn(table, "CreatedOn"); return this; } public OperationBuilder SearchContentId { get; private set; } + public OperationBuilder SiteId { get; private set; } + public OperationBuilder EntityName { get; private set; } public OperationBuilder EntityId { get; private set; } - public OperationBuilder SiteId { get; private set; } - public OperationBuilder Title { get; private set; } public OperationBuilder Description { get; private set; } @@ -52,10 +51,14 @@ namespace Oqtane.Migrations.EntityBuilders public OperationBuilder Url { get; private set; } - public OperationBuilder ModifiedTime { get; private set; } + public OperationBuilder Permissions { get; private set; } - public OperationBuilder IsActive { get; private set; } + public OperationBuilder ContentModifiedBy { get; private set; } + + public OperationBuilder ContentModifiedOn { get; private set; } public OperationBuilder AdditionalContent { get; private set; } + + public OperationBuilder CreatedOn { get; private set; } } } diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 4c184e4b..7b5ecced 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -62,7 +62,8 @@ namespace Oqtane.Modules.HtmlText.Manager Title = module.Title, Description = string.Empty, Body = htmltext.Content, - ModifiedTime = htmltext.ModifiedOn + ContentModifiedBy = htmltext.ModifiedBy, + ContentModifiedOn = htmltext.ModifiedOn }); } diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index 3e09638b..3b8b037d 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -74,13 +74,13 @@ namespace Oqtane.Providers switch (searchQuery.SortField) { case SearchSortFields.Relevance: - results = results.OrderByDescending(i => i.Score).ThenByDescending(i => i.ModifiedTime); + results = results.OrderByDescending(i => i.Score).ThenByDescending(i => i.ContentModifiedOn); break; case SearchSortFields.Title: - results = results.OrderByDescending(i => i.Title).ThenByDescending(i => i.ModifiedTime); + results = results.OrderByDescending(i => i.Title).ThenByDescending(i => i.ContentModifiedOn); break; default: - results = results.OrderByDescending(i => i.ModifiedTime); + results = results.OrderByDescending(i => i.ContentModifiedOn); break; } } @@ -89,13 +89,13 @@ namespace Oqtane.Providers switch (searchQuery.SortField) { case SearchSortFields.Relevance: - results = results.OrderBy(i => i.Score).ThenByDescending(i => i.ModifiedTime); + results = results.OrderBy(i => i.Score).ThenByDescending(i => i.ContentModifiedOn); break; case SearchSortFields.Title: - results = results.OrderBy(i => i.Title).ThenByDescending(i => i.ModifiedTime); + results = results.OrderBy(i => i.Title).ThenByDescending(i => i.ContentModifiedOn); break; default: - results = results.OrderBy(i => i.ModifiedTime); + results = results.OrderBy(i => i.ContentModifiedOn); break; } } @@ -135,7 +135,9 @@ namespace Oqtane.Providers Description = searchContent.Description, Body = searchContent.Body, Url = searchContent.Url, - ModifiedTime = searchContent.ModifiedTime, + Permissions = searchContent.Permissions, + ContentModifiedBy = searchContent.ContentModifiedBy, + ContentModifiedOn = searchContent.ContentModifiedOn, SearchContentProperties = searchContent.SearchContentProperties, Snippet = BuildSnippet(searchContent, searchQuery), Score = CalculateScore(searchContent, searchQuery) diff --git a/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs index 38e3769c..8511b438 100644 --- a/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs +++ b/Oqtane.Server/Repository/Interfaces/ISearchContentRepository.cs @@ -10,7 +10,7 @@ namespace Oqtane.Repository Task> GetSearchContentsAsync(SearchQuery searchQuery); SearchContent AddSearchContent(SearchContent searchContent); void DeleteSearchContent(int searchContentId); - void DeleteSearchContent(string entityName, int entryId); + void DeleteSearchContent(string entityName, string entryId); void DeleteSearchContent(string uniqueKey); void DeleteAllSearchContent(); diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index 2d4c3bed..0f6071c5 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -24,7 +24,7 @@ namespace Oqtane.Repository .Include(i => i.SearchContentProperties) .Include(i => i.SearchContentWords) .ThenInclude(w => w.SearchWord) - .Where(i => i.SiteId == searchQuery.SiteId && i.IsActive); + .Where(i => i.SiteId == searchQuery.SiteId); if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) { @@ -33,12 +33,12 @@ namespace Oqtane.Repository if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) { - searchContents = searchContents.Where(i => i.ModifiedTime >= searchQuery.BeginModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ContentModifiedOn >= searchQuery.BeginModifiedTimeUtc); } if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) { - searchContents = searchContents.Where(i => i.ModifiedTime <= searchQuery.EndModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ContentModifiedOn <= searchQuery.EndModifiedTimeUtc); } if (searchQuery.Properties != null && searchQuery.Properties.Any()) @@ -88,7 +88,7 @@ namespace Oqtane.Repository db.SaveChanges(); } - public void DeleteSearchContent(string entityName, int entryId) + public void DeleteSearchContent(string entityName, string entryId) { using var db = _dbContextFactory.CreateDbContext(); var searchContent = db.SearchContent.FirstOrDefault(i => i.EntityName == entityName && i.EntityId == entryId); diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index f92757da..86a304be 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -10,6 +10,7 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace Oqtane.Services { @@ -199,17 +200,18 @@ namespace Oqtane.Services private bool Visible(SearchContent searchContent, SearchQuery searchQuery) { - if(!HasViewPermission(searchQuery.SiteId, searchQuery.User, searchContent.EntityName, searchContent.EntityId)) + var visible = true; + foreach (var permission in searchContent.Permissions.Split(',')) { - return false; + var entityName = permission.Split(":")[0]; + var entityId = int.Parse(permission.Split(":")[1]); + if (!HasViewPermission(searchQuery.SiteId, searchQuery.User, entityName, entityId)) + { + visible = false; + break; + } } - - var searchResultManager = GetSearchResultManagers().FirstOrDefault(i => i.Name == searchContent.EntityName); - if (searchResultManager != null) - { - return searchResultManager.Visible(searchContent, searchQuery); - } - return true; + return visible; } private bool HasViewPermission(int siteId, User user, string entityName, int entityId) diff --git a/Oqtane.Shared/Models/SearchContent.cs b/Oqtane.Shared/Models/SearchContent.cs index 2a242d7a..9a3c0cbe 100644 --- a/Oqtane.Shared/Models/SearchContent.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -4,18 +4,15 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json; namespace Oqtane.Models { - public class SearchContent : ModelBase + public class SearchContent { public int SearchContentId { get; set; } - [NotMapped] - public string UniqueKey => $"{EntityName}:{EntityId}"; + public int SiteId { get; set; } public string EntityName { get; set; } - public int EntityId { get; set; } - - public int SiteId { get; set; } + public string EntityId { get; set; } public string Title { get; set; } @@ -25,16 +22,45 @@ namespace Oqtane.Models public string Url { get; set; } - public DateTime ModifiedTime { get; set; } + public string Permissions { get; set; } - public bool IsActive { get; set; } = true; + public string ContentModifiedBy { get; set; } + + public DateTime ContentModifiedOn { get; set; } public string AdditionalContent { get; set; } + public DateTime CreatedOn { get; set; } + public List SearchContentProperties { get; set; } public List SearchContentWords { get; set; } + [NotMapped] + public string UniqueKey => $"{EntityName}:{EntityId}"; + + [NotMapped] + public int TenantId { get; set; } + + // constructors + public SearchContent() { } + + public SearchContent(int siteId, string entityName, string entityId, string title, string description, string body, string url, string permissions, string contentModifiedBy, DateTime contentModifiedOn) + { + SiteId = siteId; + EntityName = entityName; + EntityId = entityId; + Title = title; + Description = description; + Body = body; + Url = url; + Permissions = permissions; + ContentModifiedBy = contentModifiedBy; + ContentModifiedOn = contentModifiedOn; + AdditionalContent = ""; + CreatedOn = DateTime.UtcNow; + } + public override string ToString() { return JsonSerializer.Serialize(this); From 3a9885abd83ec2aa357e511d927949147adc09b8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 28 Jun 2024 16:15:28 -0400 Subject: [PATCH 181/189] modify query property names --- Oqtane.Server/Repository/SearchContentRepository.cs | 8 ++++---- Oqtane.Shared/Models/SearchContent.cs | 2 +- Oqtane.Shared/Models/SearchQuery.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index 0f6071c5..abfcd276 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -31,14 +31,14 @@ namespace Oqtane.Repository searchContents = searchContents.Where(i => searchQuery.EntityNames.Contains(i.EntityName)); } - if (searchQuery.BeginModifiedTimeUtc != DateTime.MinValue) + if (searchQuery.From != DateTime.MinValue) { - searchContents = searchContents.Where(i => i.ContentModifiedOn >= searchQuery.BeginModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ContentModifiedOn >= searchQuery.From); } - if (searchQuery.EndModifiedTimeUtc != DateTime.MinValue) + if (searchQuery.To != DateTime.MinValue) { - searchContents = searchContents.Where(i => i.ContentModifiedOn <= searchQuery.EndModifiedTimeUtc); + searchContents = searchContents.Where(i => i.ContentModifiedOn <= searchQuery.To); } if (searchQuery.Properties != null && searchQuery.Properties.Any()) diff --git a/Oqtane.Shared/Models/SearchContent.cs b/Oqtane.Shared/Models/SearchContent.cs index 9a3c0cbe..a174aa31 100644 --- a/Oqtane.Shared/Models/SearchContent.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -22,7 +22,7 @@ namespace Oqtane.Models public string Url { get; set; } - public string Permissions { get; set; } + public string Permissions { get; set; } // comma delimited EntityName:EntityId,EntityName:EntityId public string ContentModifiedBy { get; set; } diff --git a/Oqtane.Shared/Models/SearchQuery.cs b/Oqtane.Shared/Models/SearchQuery.cs index 70e3880d..691f8491 100644 --- a/Oqtane.Shared/Models/SearchQuery.cs +++ b/Oqtane.Shared/Models/SearchQuery.cs @@ -16,9 +16,9 @@ namespace Oqtane.Models public List EntityNames { get; set; } = new List(); - public DateTime BeginModifiedTimeUtc { get; set; } + public DateTime From { get; set; } - public DateTime EndModifiedTimeUtc { get; set; } + public DateTime To { get; set; } public IDictionary Properties { get; set; } = new Dictionary(); From b6fa0f1ff6c33f5c7c52bc821cbac007fca67e1a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 28 Jun 2024 16:24:56 -0400 Subject: [PATCH 182/189] change EntityId to string --- .../Migrations/EntityBuilders/SearchContentEntityBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs index c8082c77..20c61600 100644 --- a/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs +++ b/Oqtane.Server/Migrations/EntityBuilders/SearchContentEntityBuilder.cs @@ -21,7 +21,7 @@ namespace Oqtane.Migrations.EntityBuilders SearchContentId = AddAutoIncrementColumn(table, "SearchContentId"); SiteId = AddIntegerColumn(table, "SiteId"); EntityName = AddStringColumn(table, "EntityName", 50); - EntityId = AddIntegerColumn(table, "EntityId"); + EntityId = AddStringColumn(table, "EntityId", 50); Title = AddStringColumn(table, "Title", 200); Description = AddMaxStringColumn(table, "Description"); Body = AddMaxStringColumn(table, "Body"); From 3cbe6c1e953cac285d779ed03bde751465042913 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 28 Jun 2024 17:26:46 -0400 Subject: [PATCH 183/189] ensure ModuleDefinition exists --- Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index d1fc3e74..b19ca49a 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -50,7 +50,7 @@ namespace Oqtane.Managers.Search } var module = pageModule.Module; - if (module.ModuleDefinition.ServerManagerType != "") + if (module.ModuleDefinition != null && module.ModuleDefinition.ServerManagerType != "") { _logger.LogDebug($"Search: Begin index module {module.ModuleId}."); var type = Type.GetType(module.ModuleDefinition.ServerManagerType); From 7f6a08ae501f40a07f0e318406da91c26f86671d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 28 Jun 2024 17:31:33 -0400 Subject: [PATCH 184/189] provide default Permissions value --- .../Search/ModuleSearchIndexManager.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index b19ca49a..f1e65614 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -100,6 +100,10 @@ namespace Oqtane.Managers.Search searchContent.EntityId = pageModule.ModuleId.ToString(); } + if (string.IsNullOrEmpty(searchContent.Permissions)) + { + searchContent.Permissions = $"{EntityNames.Module}:{pageModule.ModuleId},{EntityNames.Page}:{pageModule.PageId}"; + } if (searchContent.ContentModifiedOn == DateTime.MinValue) { @@ -111,16 +115,17 @@ namespace Oqtane.Managers.Search searchContent.AdditionalContent = string.Empty; } - var page = _pageRepository.GetPage(pageModule.PageId); - - if (string.IsNullOrEmpty(searchContent.Url) && page != null) + if (pageModule.Page != null) { - searchContent.Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}"; - } + if (string.IsNullOrEmpty(searchContent.Url)) + { + searchContent.Url = $"{(!string.IsNullOrEmpty(pageModule.Page.Path) && !pageModule.Page.Path.StartsWith("/") ? "/" : "")}{pageModule.Page.Path}"; + } - if (string.IsNullOrEmpty(searchContent.Title) && page != null) - { - searchContent.Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name; + if (string.IsNullOrEmpty(searchContent.Title)) + { + searchContent.Title = !string.IsNullOrEmpty(pageModule.Page.Title) ? pageModule.Page.Title : pageModule.Page.Name; + } } if (searchContent.SearchContentProperties == null) From aaf3cdfdac20f028de49d03b1c031f52395bba40 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 1 Jul 2024 16:19:58 -0400 Subject: [PATCH 185/189] ensure UniqueKey is unique by including TenantId and SiteId --- Oqtane.Shared/Models/SearchContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Shared/Models/SearchContent.cs b/Oqtane.Shared/Models/SearchContent.cs index a174aa31..77f803a9 100644 --- a/Oqtane.Shared/Models/SearchContent.cs +++ b/Oqtane.Shared/Models/SearchContent.cs @@ -37,7 +37,7 @@ namespace Oqtane.Models public List SearchContentWords { get; set; } [NotMapped] - public string UniqueKey => $"{EntityName}:{EntityId}"; + public string UniqueKey => $"{TenantId}:{SiteId}:{EntityName}:{EntityId}"; [NotMapped] public int TenantId { get; set; } From 45b1d405a6c9acb51ea870951154e9438fc82273 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Jul 2024 11:05:29 -0400 Subject: [PATCH 186/189] fix #4375 - deleted pages not being filtered --- Oqtane.Server/Services/SiteService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index 48eb6c37..97198373 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -77,6 +77,7 @@ namespace Oqtane.Services pages.Add(page); } } + site.Pages = pages; return site; } From 8969b1273f5c2986f6ad8a98d673888beb18e52a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Jul 2024 14:50:26 -0400 Subject: [PATCH 187/189] use PageModule in ISearchable --- .../Managers/Search/ModuleSearchIndexManager.cs | 15 +++++++-------- .../Modules/HtmlText/Manager/HtmlTextManager.cs | 6 +++--- Oqtane.Shared/Interfaces/ISearchable.cs | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index f1e65614..4764647e 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -49,17 +49,16 @@ namespace Oqtane.Managers.Search continue; } - var module = pageModule.Module; - if (module.ModuleDefinition != null && module.ModuleDefinition.ServerManagerType != "") + if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "") { - _logger.LogDebug($"Search: Begin index module {module.ModuleId}."); - var type = Type.GetType(module.ModuleDefinition.ServerManagerType); + _logger.LogDebug($"Search: Begin index module {pageModule.ModuleId}."); + var type = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType); if (type?.GetInterface(nameof(ISearchable)) != null) { try { var moduleSearch = (ISearchable)ActivatorUtilities.CreateInstance(_serviceProvider, type); - var contentList = moduleSearch.GetSearchContents(module, startTime.GetValueOrDefault(DateTime.MinValue)); + var contentList = moduleSearch.GetSearchContents(pageModule, startTime.GetValueOrDefault(DateTime.MinValue)); if(contentList != null) { foreach(var searchContent in contentList) @@ -73,11 +72,11 @@ namespace Oqtane.Managers.Search } catch(Exception ex) { - _logger.LogError(ex, $"Search: Index module {module.ModuleId} failed."); - handleError($"Search: Index module {module.ModuleId} failed: {ex.Message}"); + _logger.LogError(ex, $"Search: Index module {pageModule.ModuleId} failed."); + handleError($"Search: Index module {pageModule.ModuleId} failed: {ex.Message}"); } } - _logger.LogDebug($"Search: End index module {module.ModuleId}."); + _logger.LogDebug($"Search: End index module {pageModule.ModuleId}."); } } diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 7b5ecced..305fa22d 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -48,18 +48,18 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } - public List GetSearchContents(Module module, DateTime startDate) + public List GetSearchContents(PageModule pageModule, DateTime startDate) { var searchContentList = new List(); - var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId); + var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId); if (htmltexts != null && htmltexts.Any(i => i.CreatedOn >= startDate)) { var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First(); searchContentList.Add(new SearchContent { - Title = module.Title, + Title = pageModule.Module.Title, Description = string.Empty, Body = htmltext.Content, ContentModifiedBy = htmltext.ModifiedBy, diff --git a/Oqtane.Shared/Interfaces/ISearchable.cs b/Oqtane.Shared/Interfaces/ISearchable.cs index 35019c01..33f81d51 100644 --- a/Oqtane.Shared/Interfaces/ISearchable.cs +++ b/Oqtane.Shared/Interfaces/ISearchable.cs @@ -6,6 +6,6 @@ namespace Oqtane.Interfaces { public interface ISearchable { - public List GetSearchContents(Module module, DateTime startTime); + public List GetSearchContents(PageModule pageModule, DateTime startTime); } } From 69a295fe57df202d0e4bada70fcfae729b0cc1dc Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Jul 2024 15:45:44 -0400 Subject: [PATCH 188/189] remove unnecessary database call to GetPage --- Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs index 4764647e..f5f1806b 100644 --- a/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs +++ b/Oqtane.Server/Managers/Search/ModuleSearchIndexManager.cs @@ -43,8 +43,7 @@ namespace Oqtane.Managers.Search foreach(var pageModule in pageModules) { - var page = _pageRepository.GetPage(pageModule.PageId); - if(page == null || SearchUtils.IsSystemPage(page)) + if(pageModule.Page == null || SearchUtils.IsSystemPage(pageModule.Page)) { continue; } From e321998b85d875e800d8302f3e6b38dde7cc24b3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 2 Jul 2024 15:53:26 -0400 Subject: [PATCH 189/189] remove unnecessary using --- Oqtane.Server/Services/SearchService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index 86a304be..3ac21321 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -10,7 +10,6 @@ using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; -using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace Oqtane.Services {