From 0224fd6d54c4ff3a786cad394198255f1a758495 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 28 May 2024 15:17:27 -0400 Subject: [PATCH 001/142] 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 002/142] 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 003/142] 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 004/142] 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 005/142] 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 006/142] 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 007/142] 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 008/142] #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 009/142] 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 010/142] 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 011/142] 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 012/142] 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 013/142] 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 014/142] 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 015/142] 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 016/142] 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 017/142] 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 018/142] 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 019/142] 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 020/142] 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 021/142] 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 022/142] 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 023/142] 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 024/142] 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 025/142] 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 026/142] 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 027/142] 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 028/142] 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 029/142] 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 030/142] 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 031/142] 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 032/142] 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 033/142] 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 034/142] 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 035/142] 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 036/142] 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 037/142] 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 038/142] 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 039/142] 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 040/142] 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 041/142] 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 042/142] 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 043/142] 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 044/142] 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 045/142] 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 046/142] 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 047/142] 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 048/142] 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 049/142] 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 050/142] 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 051/142] 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 052/142] 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 053/142] 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 054/142] 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 055/142] 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 056/142] 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 057/142] 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 058/142] 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 059/142] 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 060/142] 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 061/142] 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 062/142] 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 063/142] 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 064/142] 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 065/142] 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 066/142] 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 067/142] 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 068/142] 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 069/142] 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 070/142] 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 e00c26177751a8984f0ccb7c217926e60f377b93 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 1 Jul 2024 17:11:26 +0800 Subject: [PATCH 071/142] Fix #4358: RichTextEditor Provider Abstraction. --- Oqtane.Client/Modules/Admin/Site/Index.razor | 344 +++++++++++------- ...xtEditor.razor => QuillJSTextEditor.razor} | 95 +++-- .../Controls/QuillJSTextEditorSettings.razor | 99 +++++ .../Modules/Controls/RichTextEditor.razor | 104 +++--- .../Modules/Controls/TextAreaTextEditor.razor | 31 ++ Oqtane.Client/Modules/HtmlText/Edit.razor | 60 ++- Oqtane.Client/Modules/HtmlText/ModuleInfo.cs | 2 +- Oqtane.Client/Modules/HtmlText/Settings.razor | 61 ---- Oqtane.Client/Modules/ModuleBase.cs | 36 ++ .../Providers/QuillJSTextEditorProvider.cs | 13 + .../Providers/QuillTextEditorProvider.cs | 11 - .../Providers/TextAreaTextEditorProvider.cs | 13 + .../Resources/Modules/Admin/Site/Index.resx | 6 + ...TextEditor.resx => QuillJSTextEditor.resx} | 0 .../Controls/QuillJSTextEditorSettings.resx | 156 ++++++++ Oqtane.Server/Components/App.razor | 6 +- .../OqtaneServiceCollectionExtensions.cs | 3 +- Oqtane.Server/wwwroot/css/app.css | 6 + Oqtane.Shared/Interfaces/IModuleControl.cs | 13 + .../Interfaces/ITextEditorProvider.cs | 5 + Oqtane.Shared/Shared/Constants.cs | 4 +- 21 files changed, 726 insertions(+), 342 deletions(-) rename Oqtane.Client/Modules/Controls/{QuillTextEditor.razor => QuillJSTextEditor.razor} (83%) create mode 100644 Oqtane.Client/Modules/Controls/QuillJSTextEditorSettings.razor create mode 100644 Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor delete mode 100644 Oqtane.Client/Modules/HtmlText/Settings.razor create mode 100644 Oqtane.Client/Providers/QuillJSTextEditorProvider.cs delete mode 100644 Oqtane.Client/Providers/QuillTextEditorProvider.cs create mode 100644 Oqtane.Client/Providers/TextAreaTextEditorProvider.cs rename Oqtane.Client/Resources/Modules/Controls/{QuillTextEditor.resx => QuillJSTextEditor.resx} (100%) create mode 100644 Oqtane.Client/Resources/Modules/Controls/QuillJSTextEditorSettings.resx diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index a5626a5d..5d813d27 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -1,6 +1,7 @@ @namespace Oqtane.Modules.Admin.Site @inherits ModuleBase @using System.Text.RegularExpressions +@using Microsoft.Extensions.DependencyInjection @inject NavigationManager NavigationManager @inject ISiteService SiteService @inject ITenantService TenantService @@ -8,6 +9,7 @@ @inject IAliasService AliasService @inject IThemeService ThemeService @inject ISettingService SettingService +@inject IServiceProvider ServiceProvider @inject IStringLocalizer Localizer @inject INotificationService NotificationService @inject IStringLocalizer SharedLocalizer @@ -123,6 +125,28 @@
+
+ +
+ +
+
+ @if (_textEditorProviderSettings != null) + { +
+
+ @_textEditorProviderSettings +
+
+ }
@@ -438,6 +462,10 @@ private string _tenant = string.Empty; private string _database = string.Empty; private string _connectionstring = string.Empty; + private string _textEditorProvider = ""; + private IEnumerable _textEditorProviders; + private RenderFragment _textEditorProviderSettings; + private ISettingsControl _textEditorProviderSettingsControl; private string _createdby; private DateTime _createdon; private string _modifiedby; @@ -479,6 +507,7 @@ _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; + _textEditorProviders = ServiceProvider.GetServices(); // page content _headcontent = site.HeadContent; @@ -517,6 +546,10 @@ // aliases await GetAliases(); + //text editor + _textEditorProvider = SettingService.GetSetting(settings, "TextEditorProvider", Constants.DefaultTextEditorProvider); + LoadTextEditorProviderSettingsControl(); + // hosting model _rendermode = site.RenderMode; _runtime = site.Runtime; @@ -673,17 +706,17 @@ } } - site = await SiteService.UpdateSiteAsync(site); + site = await SiteService.UpdateSiteAsync(site); // SMTP - var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); + var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); - settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); - settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); - settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); - settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); - settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); - settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); + settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); + settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); + settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); + settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); + settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); + settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true); settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true); settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true); @@ -692,141 +725,149 @@ settings = SettingService.SetSetting(settings, "ImageFiles", (_ImageFiles != Constants.ImageFiles) ? _ImageFiles.Replace(" ", "") : "", false); settings = SettingService.SetSetting(settings, "UploadableFiles", (_UploadableFiles != Constants.UploadableFiles) ? _UploadableFiles.Replace(" ", "") : "", false); - await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); + //text editor + settings = SettingService.SetSetting(settings, "TextEditorProvider", _textEditorProvider); - await logger.LogInformation("Site Settings Saved {Site}", site); + await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); - NavigationManager.NavigateTo(NavigateUrl(), true); // reload - } - } - else - { - AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); - AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error); - } - } - else - { - AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); - } - } + await logger.LogInformation("Site Settings Saved {Site}", site); - private async Task DeleteSite() - { - try - { - var aliases = await AliasService.GetAliasesAsync(); - if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId)) - { - await SiteService.DeleteSiteAsync(PageState.Site.SiteId); - await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId); + if(_textEditorProviderSettingsControl != null) + { + await _textEditorProviderSettingsControl.UpdateSettings(); + } - foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId)) - { - await AliasService.DeleteAliasAsync(alias.AliasId); - } + NavigationManager.NavigateTo(NavigateUrl(), true); // reload + } + } + else + { + AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); + AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error); + } + } + else + { + AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); + } + } - var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId); - NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true); - } - else - { - AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning); - } - } - catch (Exception ex) - { - await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); - AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error); - } - } + private async Task DeleteSite() + { + try + { + var aliases = await AliasService.GetAliasesAsync(); + if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId)) + { + await SiteService.DeleteSiteAsync(PageState.Site.SiteId); + await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId); - private async Task SendEmail() - { - if (_smtphost != "" && _smtpport != "" && _smtpsender != "") - { - try - { - var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); - settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); - settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); - settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); - settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); - settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); - await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); - await logger.LogInformation("Site SMTP Settings Saved"); + foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId)) + { + await AliasService.DeleteAliasAsync(alias.AliasId); + } - await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); - AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); + var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId); + NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true); + } + else + { + AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning); + } + } + catch (Exception ex) + { + await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); + AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error); + } + } + + private async Task SendEmail() + { + if (_smtphost != "" && _smtpport != "" && _smtpsender != "") + { + try + { + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); + settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); + settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); + settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); + settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); + settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); + await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); + await logger.LogInformation("Site SMTP Settings Saved"); + + await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly.")); + AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info); await ScrollToPageTop(); } - catch (Exception ex) - { - await logger.LogError(ex, "Error Testing SMTP Configuration"); - AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error); - } - } - else - { - AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning); - } - } + catch (Exception ex) + { + await logger.LogError(ex, "Error Testing SMTP Configuration"); + AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error); + } + } + else + { + AddModuleMessage(Localizer["Message.Required.Smtp"], MessageType.Warning); + } + } - private void ToggleSMTPPassword() - { - if (_smtppasswordtype == "password") - { - _smtppasswordtype = "text"; - _togglesmtppassword = SharedLocalizer["HidePassword"]; - } - else - { - _smtppasswordtype = "password"; - _togglesmtppassword = SharedLocalizer["ShowPassword"]; - } - } + private void ToggleSMTPPassword() + { + if (_smtppasswordtype == "password") + { + _smtppasswordtype = "text"; + _togglesmtppassword = SharedLocalizer["HidePassword"]; + } + else + { + _smtppasswordtype = "password"; + _togglesmtppassword = SharedLocalizer["ShowPassword"]; + } + } - private async Task GetAliases() - { - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) - { - _aliases = await AliasService.GetAliasesAsync(); - _aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList(); - } - } + private async Task GetAliases() + { + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + _aliases = await AliasService.GetAliasesAsync(); + _aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList(); + } + } - private void AddAlias() - { - _aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false }); - _aliasid = 0; - _aliasname = ""; - _defaultalias = "False"; - StateHasChanged(); - } + private void AddAlias() + { + _aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false }); + _aliasid = 0; + _aliasname = ""; + _defaultalias = "False"; + StateHasChanged(); + } - private void EditAlias(Alias alias) - { - _aliasid = alias.AliasId; - _aliasname = alias.Name; - _defaultalias = alias.IsDefault.ToString(); - StateHasChanged(); - } + private void EditAlias(Alias alias) + { + _aliasid = alias.AliasId; + _aliasname = alias.Name; + _defaultalias = alias.IsDefault.ToString(); + StateHasChanged(); + } - private async Task DeleteAlias(Alias alias) - { - if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) - { - await AliasService.DeleteAliasAsync(alias.AliasId); - await GetAliases(); - StateHasChanged(); - } - } + private async Task DeleteAlias(Alias alias) + { + if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) + { + await AliasService.DeleteAliasAsync(alias.AliasId); + await GetAliases(); + StateHasChanged(); + } + } private async Task SaveAlias() { @@ -878,11 +919,42 @@ } } - private async Task CancelAlias() - { - await GetAliases(); - _aliasid = -1; - _aliasname = ""; - StateHasChanged(); - } + private async Task CancelAlias() + { + await GetAliases(); + _aliasid = -1; + _aliasname = ""; + StateHasChanged(); + } + + private void TextEditorProviderChanged(ChangeEventArgs e) + { + _textEditorProvider = e.Value.ToString(); + LoadTextEditorProviderSettingsControl(); + + StateHasChanged(); + } + + private void LoadTextEditorProviderSettingsControl() + { + var provider = _textEditorProviders.FirstOrDefault(i => i.EditorType == _textEditorProvider); + var settingsType = provider != null && !string.IsNullOrEmpty(provider.SettingsType) ? Type.GetType(provider.SettingsType) : null; + if (settingsType != null) + { + _textEditorProviderSettings = builder => + { + builder.OpenComponent(0, settingsType); + builder.AddComponentReferenceCapture(1, (c) => + { + _textEditorProviderSettingsControl = (ISettingsControl)c; + }); + builder.CloseComponent(); + }; + } + else + { + _textEditorProviderSettings = null; + _textEditorProviderSettingsControl = null; + } + } } diff --git a/Oqtane.Client/Modules/Controls/QuillTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor similarity index 83% rename from Oqtane.Client/Modules/Controls/QuillTextEditor.razor rename to Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor index c64a5c6f..e0e86139 100644 --- a/Oqtane.Client/Modules/Controls/QuillTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor @@ -1,11 +1,12 @@ @namespace Oqtane.Modules.Controls @inherits ModuleControlBase @implements ITextEditor -@inject IStringLocalizer Localizer +@inject ISettingService SettingService +@inject IStringLocalizer Localizer
- @if (AllowRichText) + @if (_allowRichText) { @if (_richfilemanager) @@ -15,7 +16,7 @@
}
- @if (AllowFileManagement) + @if (_allowFileManagement) { } @@ -28,9 +29,9 @@
- @if (ToolbarContent != null) + @if (!string.IsNullOrEmpty(_toolbarContent)) { - @ToolbarContent + @((MarkupString)_toolbarContent) } else { @@ -66,7 +67,7 @@
} - @if (AllowRawHtml) + @if (_allowRawHtml) { @if (_rawfilemanager) @@ -76,7 +77,7 @@
}
- @if (AllowFileManagement) + @if (_allowFileManagement) { } @@ -106,6 +107,14 @@ private FileManager _fileManager; private string _activetab = "Rich"; + private bool _allowFileManagement = false; + private bool _allowRawHtml = false; + private bool _allowRichText = false; + private string _theme = "snow"; + private string _debugLevel = "info"; + private string _toolbarContent = string.Empty; + private bool _settingsLoaded; + private ElementReference _editorElement; private ElementReference _toolBar; private bool _richfilemanager = false; @@ -121,38 +130,22 @@ 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; } [Parameter] public string Placeholder { get; set; } - [Parameter] - public string Theme { get; set; } = "snow"; - - [Parameter] - public string DebugLevel { get; set; } = "info"; - - [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 } + new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.bubble.css" }, + new Resource { ResourceType = ResourceType.Stylesheet, Url = "css/quill/quill.snow.css" } }; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { interop = new QuillEditorInterop(JSRuntime); @@ -160,11 +153,13 @@ { Placeholder = Localizer["Placeholder"]; } + + await LoadSettings(); } protected override void OnParametersSet() { - if (!AllowRichText) + if (!_allowRichText) { _activetab = "Raw"; } @@ -174,17 +169,17 @@ { await base.OnAfterRenderAsync(firstRender); - if (AllowRichText) + if (_allowRichText) { - if (firstRender) + if (_settingsLoaded && !_initialized) { await interop.CreateEditor( _editorElement, _toolBar, ReadOnly, Placeholder, - Theme, - DebugLevel); + _theme, + _debugLevel); await interop.LoadEditorContent(_editorElement, _richhtml); @@ -202,6 +197,8 @@ // reload editor if Content passed to component has changed await interop.LoadEditorContent(_editorElement, _richhtml); _originalrichhtml = await interop.GetHtml(_editorElement); + + _contentchanged = false; } else { @@ -215,8 +212,6 @@ } } } - - _contentchanged = false; } } @@ -224,10 +219,14 @@ { _richhtml = content; _rawhtml = content; - _originalrawhtml = _rawhtml; // preserve for comparison later _originalrichhtml = ""; _richhtml = content; - _contentchanged = content != _originalrawhtml; + if (!_contentchanged) + { + _contentchanged = content != _originalrawhtml; + } + + _originalrawhtml = _rawhtml; // preserve for comparison later StateHasChanged(); } @@ -243,7 +242,7 @@ { var richhtml = ""; - if (AllowRichText) + if (_allowRichText) { richhtml = await interop.GetHtml(_editorElement); } @@ -290,7 +289,7 @@ { var richhtml = ""; - if (AllowRichText) + if (_allowRichText) { richhtml = await interop.GetHtml(_editorElement); } @@ -362,4 +361,24 @@ } StateHasChanged(); } + + private async Task LoadSettings() + { + try + { + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + _allowFileManagement = SettingService.GetSetting(settings, "QuillTextEditor_AllowFileManagement", "true") == "true"; + _allowRawHtml = SettingService.GetSetting(settings, "QuillTextEditor_AllowRawHtml", "true") == "true"; + _allowRichText = SettingService.GetSetting(settings, "QuillTextEditor_AllowRichText", "true") == "true"; + _theme = SettingService.GetSetting(settings, "QuillTextEditor_Theme", "snow"); + _debugLevel = SettingService.GetSetting(settings, "QuillTextEditor_DebugLevel", "info"); + _toolbarContent = SettingService.GetSetting(settings, "QuillTextEditor_ToolbarContent", string.Empty); + + _settingsLoaded = true; + } + catch (Exception ex) + { + AddModuleMessage(ex.Message, MessageType.Error); + } + } } diff --git a/Oqtane.Client/Modules/Controls/QuillJSTextEditorSettings.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditorSettings.razor new file mode 100644 index 00000000..fb41a2ef --- /dev/null +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditorSettings.razor @@ -0,0 +1,99 @@ +@namespace Oqtane.Modules.Controls +@inherits ModuleBase +@inject ISettingService SettingService +@implements Oqtane.Interfaces.ISettingsControl +@inject IStringLocalizer Localizer +@inject IStringLocalizer SharedLocalizer + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+

+ +

+ +@code { + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; + + private string _enabled; + private string _lastIndexedOn; + private string _ignorePaths; + private string _ignoreEntities; + private string _minimumWordLength; + private string _ignoreWords; + + protected override async Task OnInitializedAsync() + { + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + _enabled = SettingService.GetSetting(settings, "Search_Enabled", "True"); + _lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", ""); + _ignorePaths = SettingService.GetSetting(settings, "Search_IgnorePaths", ""); + _ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", ""); + _minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3"); + _ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", ""); + } + + private async Task Save() + { + try + { + var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); + settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true); + settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true); + settings = SettingService.SetSetting(settings, "Search_IgnorePaths", _ignorePaths, true); + settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true); + settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true); + settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, 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); + } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Services/SiteService.cs b/Oqtane.Client/Services/SiteService.cs index 38dc5dee..4f1658f4 100644 --- a/Oqtane.Client/Services/SiteService.cs +++ b/Oqtane.Client/Services/SiteService.cs @@ -1,7 +1,6 @@ using Oqtane.Models; using System.Threading.Tasks; using System.Net.Http; -using System.Linq; using System.Collections.Generic; using Oqtane.Shared; using System; diff --git a/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs b/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs index c70962ad..76b5bef9 100644 --- a/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs +++ b/Oqtane.Server/Infrastructure/SiteTemplates/AdminSiteTemplate.cs @@ -150,7 +150,7 @@ namespace Oqtane.SiteTemplates Parent = "", Path = "search", Order = seed + 9, - Icon = "oi oi-magnifying-glass", + Icon = Icons.MagnifyingGlass, IsNavigation = false, IsPersonalizable = false, PermissionList = new List { @@ -159,7 +159,7 @@ namespace Oqtane.SiteTemplates new Permission(PermissionNames.Edit, RoleNames.Admin, true) }, PageTemplateModules = new List { - new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default, + new PageTemplateModule { ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SearchResults.Index).ToModuleDefinitionName(), Title = "Search", Pane = PaneNames.Default, PermissionList = new List { new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Everyone, true), @@ -481,14 +481,43 @@ namespace Oqtane.SiteTemplates } }); - // host pages + pageTemplates.Add(new PageTemplate + { + Name = "Search Settings", + Parent = "Admin", + Order = 19, + Path = "admin/search", + Icon = Icons.MagnifyingGlass, + 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.Search.Index).ToModuleDefinitionName(), Title = "Search Settings", Pane = PaneNames.Default, + PermissionList = new List + { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + }, + Content = "" + } + } + }); + + // host pages (order starts at 51) pageTemplates.Add(new PageTemplate { Name = "Event Log", Parent = "Admin", - Order = 19, + Order = 51, Path = "admin/log", - Icon = Icons.MagnifyingGlass, + Icon = Icons.List, IsNavigation = false, IsPersonalizable = false, PermissionList = new List @@ -514,7 +543,7 @@ namespace Oqtane.SiteTemplates { Name = "Site Management", Parent = "Admin", - Order = 21, + Order = 53, Path = "admin/sites", Icon = Icons.Globe, IsNavigation = false, @@ -542,7 +571,7 @@ namespace Oqtane.SiteTemplates { Name = "Module Management", Parent = "Admin", - Order = 23, + Order = 55, Path = "admin/modules", Icon = Icons.Browser, IsNavigation = false, @@ -570,7 +599,7 @@ namespace Oqtane.SiteTemplates { Name = "Theme Management", Parent = "Admin", - Order = 25, + Order = 57, Path = "admin/themes", Icon = Icons.Brush, IsNavigation = false, @@ -598,7 +627,7 @@ namespace Oqtane.SiteTemplates { Name = "Language Management", Parent = "Admin", - Order = 27, + Order = 59, Path = "admin/languages", Icon = Icons.Text, IsNavigation = false, @@ -630,7 +659,7 @@ namespace Oqtane.SiteTemplates { Name = "Scheduled Jobs", Parent = "Admin", - Order = 29, + Order = 61, Path = "admin/jobs", Icon = Icons.Timer, IsNavigation = false, @@ -658,7 +687,7 @@ namespace Oqtane.SiteTemplates { Name = "Sql Management", Parent = "Admin", - Order = 31, + Order = 63, Path = "admin/sql", Icon = Icons.Spreadsheet, IsNavigation = false, @@ -686,7 +715,7 @@ namespace Oqtane.SiteTemplates { Name = "System Info", Parent = "Admin", - Order = 33, + Order = 65, Path = "admin/system", Icon = Icons.MedicalCross, IsNavigation = false, @@ -714,7 +743,7 @@ namespace Oqtane.SiteTemplates { Name = "System Update", Parent = "Admin", - Order = 35, + Order = 67, Path = "admin/update", Icon = Icons.Aperture, IsNavigation = false, diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 903c612d..aa32a669 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -139,71 +139,69 @@ namespace Oqtane.Infrastructure private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope) { - var pageTemplates = new List(); - - pageTemplates.Add(new PageTemplate + var pageTemplates = new List { - Name = "Url Mappings", - Parent = "Admin", - Order = 33, - Path = "admin/urlmappings", - Icon = Icons.LinkBroken, - IsNavigation = false, - IsPersonalizable = false, - PermissionList = new List + new PageTemplate { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - PageTemplateModules = new List - { - new PageTemplateModule + Update = false, + Name = "Url Mappings", + Parent = "Admin", + Order = 33, + Path = "admin/urlmappings", + Icon = Icons.LinkBroken, + IsNavigation = false, + IsPersonalizable = false, + PermissionList = new List { - 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) + }, + PageTemplateModules = new List + { + new PageTemplateModule { - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) - }, - Content = "" + 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 = "" + } + } + }, + new PageTemplate + { + Update = false, + Name = "Visitor Management", + Parent = "Admin", + Order = 35, + 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 = "" + } } } - }); + }; - pageTemplates.Add(new PageTemplate - { - Name = "Visitor Management", - Parent = "Admin", - Order = 35, - 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 = "" - } - } - }); - - var sites = scope.ServiceProvider.GetRequiredService(); - foreach (Site site in sites.GetSites().ToList()) - { - sites.CreatePages(site, pageTemplates, null); - } + AddPagesToSites(scope, pageTemplates); } private void Upgrade_3_1_3(Tenant tenant, IServiceScope scope) @@ -386,49 +384,69 @@ namespace Oqtane.Infrastructure Debug.WriteLine($"Oqtane Error: Error In 5.1.0 Upgrade Logic - {ex}"); } } - } 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 + var pageTemplates = new List { - Name = "Search", - 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) + new PageTemplate + { + Update = false, + Name = "Search", + Parent = "", + Path = "search", + Icon = Icons.MagnifyingGlass, + 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 = typeof(Oqtane.Modules.Admin.SearchResults.Index).ToModuleDefinitionName(), Title = "Search", 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) + } + } + } }, - 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.Everyone, true), - new Permission(PermissionNames.View, RoleNames.Admin, true), - new Permission(PermissionNames.Edit, RoleNames.Admin, true) + new PageTemplate + { + Update = false, + Name = "Search Settings", + Parent = "", + Path = "admin/search", + Icon = Icons.MagnifyingGlass, + 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.Search.Index).ToModuleDefinitionName(), Title = "Search Settings", Pane = PaneNames.Default, + PermissionList = new List { + new Permission(PermissionNames.View, RoleNames.Admin, true), + new Permission(PermissionNames.Edit, RoleNames.Admin, true) + } } } } - }); + }; - var pages = scope.ServiceProvider.GetRequiredService(); + AddPagesToSites(scope, pageTemplates); + } + + private void AddPagesToSites(IServiceScope scope, List pageTemplates) + { 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); - } + sites.CreatePages(site, pageTemplates, null); } } } From 90ef3f6c94d0a0e2e9376b9b549baa86143f6ca6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 11:13:27 -0400 Subject: [PATCH 098/142] performance improvement in Control Panel to only load list of pages when necessary --- .../Themes/Controls/Theme/ControlPanelInteractive.razor | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index b6096044..15185991 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -283,7 +283,6 @@ if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList)) { LoadSettingsAsync(); - _pages = await PageService.GetPagesAsync(PageState.Page.SiteId); _containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType); _containerType = PageState.Site.DefaultContainerType; _allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId); @@ -315,9 +314,13 @@ StateHasChanged(); } - private void ModuleTypeChanged(ChangeEventArgs e) + private async Task ModuleTypeChanged(ChangeEventArgs e) { _moduleType = (string)e.Value; + if (_moduleType != "new") + { + _pages = await PageService.GetPagesAsync(PageState.Page.SiteId); + } _pageId = "-"; _moduleId = "-"; } From e9f6a85cad03255bdf783373d8472721e7113d49 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 11:20:27 -0400 Subject: [PATCH 099/142] revert #4250 which disabled prerendering by default for static rendered sites --- Oqtane.Client/Modules/Admin/Site/Index.razor | 19 +------------------ .../Infrastructure/DatabaseManager.cs | 2 +- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 62fa5fc0..44e0234c 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -336,7 +336,7 @@
- @@ -598,23 +598,6 @@ } } - 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.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index ad472f37..fca34c38 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -560,7 +560,7 @@ namespace Oqtane.Infrastructure SiteTemplateType = install.SiteTemplate, RenderMode = rendermode, Runtime = runtime, - Prerender = (rendermode == RenderModes.Interactive), + Prerender = true, Hybrid = false }; site = sites.AddSite(site); From 5610a14e49e8613f1c4c9ed1c1f6f8fbd4bd6a77 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 11:34:01 -0400 Subject: [PATCH 100/142] add missing properties to Clone method --- Oqtane.Client/Modules/Admin/Modules/Settings.razor | 9 ++++++--- Oqtane.Shared/Models/Site.cs | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Modules/Settings.razor b/Oqtane.Client/Modules/Admin/Modules/Settings.razor index 4d39a638..ffe021d9 100644 --- a/Oqtane.Client/Modules/Admin/Modules/Settings.razor +++ b/Oqtane.Client/Modules/Admin/Modules/Settings.razor @@ -80,11 +80,14 @@ } else { - foreach (Page p in _pages) + if (_pages != null) { - if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList)) + foreach (Page p in _pages) { - + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList)) + { + + } } } } diff --git a/Oqtane.Shared/Models/Site.cs b/Oqtane.Shared/Models/Site.cs index 8b173552..d5508858 100644 --- a/Oqtane.Shared/Models/Site.cs +++ b/Oqtane.Shared/Models/Site.cs @@ -220,6 +220,10 @@ namespace Oqtane.Models ImageFiles = site.ImageFiles, UploadableFiles = site.UploadableFiles, SiteTemplateType = site.SiteTemplateType, + CreatedBy = site.CreatedBy, + CreatedOn = site.CreatedOn, + ModifiedBy = site.ModifiedBy, + ModifiedOn = site.ModifiedOn, Settings = site.Settings.ToDictionary(), Pages = site.Pages.ToList(), Languages = site.Languages.ToList(), From b942a84b156550b5b7e6d2dc587fc9774273e9d8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 11:53:04 -0400 Subject: [PATCH 101/142] improve PageState trimming --- .../Controls/Container/ModuleActions.razor | 8 ++++---- .../Controls/Container/ModuleActionsBase.cs | 3 ++- .../Container/ModuleActionsPageState.cs | 12 ------------ .../Themes/Controls/Theme/ControlPanel.razor | 12 ++++++------ .../Theme/ControlPanelInteractive.razor | 2 +- .../Controls/Theme/ControlPanelPageState.cs | 19 ------------------- Oqtane.Client/UI/ModuleInstance.razor | 2 +- Oqtane.Client/UI/PageState.cs | 4 ++-- 8 files changed, 16 insertions(+), 46 deletions(-) delete mode 100644 Oqtane.Client/Themes/Controls/Container/ModuleActionsPageState.cs delete mode 100644 Oqtane.Client/Themes/Controls/Theme/ControlPanelPageState.cs diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor b/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor index 3172d540..4b973c48 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActions.razor @@ -6,21 +6,21 @@ { @if (PageState.RenderMode == RenderModes.Interactive) { - + } else { - + } } @code { - private ModuleActionsPageState _moduleActionsPageState; + private PageState _pageState; protected override void OnParametersSet() { // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries - _moduleActionsPageState = new ModuleActionsPageState + _pageState = new PageState { Alias = PageState.Alias, Page = PageState.Page, diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs index bcbfadae..93e935b9 100644 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs +++ b/Oqtane.Client/Themes/Controls/Container/ModuleActionsBase.cs @@ -9,6 +9,7 @@ using Oqtane.Services; using Oqtane.Shared; using System.Net; using Microsoft.Extensions.Localization; +using Oqtane.UI; // ReSharper disable UnassignedGetOnlyAutoProperty // ReSharper disable MemberCanBePrivate.Global @@ -22,7 +23,7 @@ namespace Oqtane.Themes.Controls [Inject] public IModuleService ModuleService { get; set; } [Inject] public IStringLocalizer Localizer { get; set; } - [Parameter] public ModuleActionsPageState PageState { get; set; } + [Parameter] public PageState PageState { get; set; } [Parameter] public Module ModuleState { get; set; } public List Actions; diff --git a/Oqtane.Client/Themes/Controls/Container/ModuleActionsPageState.cs b/Oqtane.Client/Themes/Controls/Container/ModuleActionsPageState.cs deleted file mode 100644 index fd8ef018..00000000 --- a/Oqtane.Client/Themes/Controls/Container/ModuleActionsPageState.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Oqtane.Models; - -namespace Oqtane.Themes.Controls -{ - public class ModuleActionsPageState - { - public Alias Alias { get; set; } - public Page Page { get; set; } - public User User { get; set; } - public bool EditMode { get; set; } - } -} diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index 71af7603..83087013 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -32,11 +32,11 @@ { @if (PageState.RenderMode == RenderModes.Interactive) { - + } else { - + } } @@ -59,7 +59,7 @@ [Parameter] public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right - private ControlPanelPageState _controlPanelPageState; + private PageState _pageState; private bool _canViewAdminDashboard = false; private bool _showEditMode = false; @@ -85,14 +85,14 @@ } // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries - _controlPanelPageState = new ControlPanelPageState + _pageState = new PageState { Alias = PageState.Alias, Site = new Site { - DefaultContainerType = PageState.Site.DefaultContainerType, + DefaultContainerType = PageState.Site.DefaultContainerType, Settings = PageState.Site.Settings, - Themes = PageState.Site.Themes + Themes = PageState.Site.Themes }, Page = PageState.Page, User = PageState.User, diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 15185991..730e7dd8 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -228,7 +228,7 @@ public SiteState SiteState { get; set; } [Parameter] - public ControlPanelPageState PageState { get; set; } + public PageState PageState { get; set; } [Parameter] public string ButtonClass { get; set; } diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelPageState.cs b/Oqtane.Client/Themes/Controls/Theme/ControlPanelPageState.cs deleted file mode 100644 index f0be1833..00000000 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelPageState.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System; -using Oqtane.Models; -using Oqtane.UI; - -namespace Oqtane.Themes.Controls -{ - public class ControlPanelPageState - { - public Alias Alias { get; set; } - public Site Site { get; set; } - public Page Page { get; set; } - public User User { get; set; } - public Uri Uri { get; set; } - public Route Route { get; set; } - public string RenderMode { get; set; } - public Shared.Runtime Runtime { get; set; } - } -} diff --git a/Oqtane.Client/UI/ModuleInstance.razor b/Oqtane.Client/UI/ModuleInstance.razor index 15948e83..dc85e180 100644 --- a/Oqtane.Client/UI/ModuleInstance.razor +++ b/Oqtane.Client/UI/ModuleInstance.razor @@ -43,7 +43,7 @@ { // trim PageState to mitigate page bloat caused by Blazor serializing/encrypting state when crossing render mode boundaries // please note that this performance optimization results in the PageState.Pages property not being available for use in Interactive components - PageState.Site.Pages = new List(); + PageState.Site.Pages = null; } } diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index b8a3bffc..0c17c530 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -30,11 +30,11 @@ namespace Oqtane.UI public List Pages { - get { return Site.Pages; } + get { return Site?.Pages; } } public List Languages { - get { return Site.Languages; } + get { return Site?.Languages; } } } } From 25ea518266d2e2c3873df5a1000f5bb330f54a14 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 17 Jul 2024 12:12:58 -0400 Subject: [PATCH 102/142] Revert "revert #4250 which disabled prerendering by default for static rendered sites" --- Oqtane.Client/Modules/Admin/Site/Index.razor | 19 ++++++++++++++++++- .../Infrastructure/DatabaseManager.cs | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index 44e0234c..62fa5fc0 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -336,7 +336,7 @@
- @@ -598,6 +598,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.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index fca34c38..ad472f37 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -560,7 +560,7 @@ namespace Oqtane.Infrastructure SiteTemplateType = install.SiteTemplate, RenderMode = rendermode, Runtime = runtime, - Prerender = true, + Prerender = (rendermode == RenderModes.Interactive), Hybrid = false }; site = sites.AddSite(site); From 71e472f330e15dba62c145bfd2764f726219b0f8 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 13:57:47 -0400 Subject: [PATCH 103/142] search optimizations --- .../Modules/Admin/SearchResults/Index.razor | 11 +-- .../Modules/Admin/SearchResults/ModuleInfo.cs | 23 ------ .../Infrastructure/Jobs/SearchIndexJob.cs | 1 - .../Providers/DatabaseSearchProvider.cs | 71 +++++++++---------- .../Repository/SearchContentRepository.cs | 10 ++- .../Module.css | 3 - Oqtane.Shared/Interfaces/ISearchProvider.cs | 3 - Oqtane.Shared/Models/SearchQuery.cs | 4 +- Oqtane.Shared/Shared/SearchUtils.cs | 12 ---- 9 files changed, 52 insertions(+), 86 deletions(-) delete mode 100644 Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs delete mode 100644 Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index 21d358b3..b86f8698 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -37,12 +37,12 @@ if (_searchResults.Results.Any()) { + Format="Grid" + Columns="1" + Toolbar="Bottom" + Parameters="@($"q={_keywords}")"> -
+

@context.Title

@((MarkupString)context.Snippet)

@@ -61,6 +61,7 @@
+ @code { public override string RenderMode => RenderModes.Static; diff --git a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs b/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs deleted file mode 100644 index 7594a524..00000000 --- a/Oqtane.Client/Modules/Admin/SearchResults/ModuleInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using Oqtane.Documentation; -using Oqtane.Models; -using Oqtane.Shared; - -namespace Oqtane.Modules.Admin.SearchResults -{ - [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 = Constants.Version, - Categories = "Admin", - Resources = new List() - { - new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } - } - }; - } -} diff --git a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs index 142f3d3b..8d8e987a 100644 --- a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs @@ -58,7 +58,6 @@ namespace Oqtane.Infrastructure var currentTime = DateTime.UtcNow; var lastIndexedOn = Convert.ToDateTime(siteSettings.GetValue(SearchLastIndexedOnSetting, DateTime.MinValue.ToString())); - log += $"Index Date: {lastIndexedOn}
"; var ignorePaths = siteSettings.GetValue(SearchIgnorePathsSetting, "").Split(','); var ignoreEntities = siteSettings.GetValue(SearchIgnoreEntitiesSetting, "").Split(','); diff --git a/Oqtane.Server/Providers/DatabaseSearchProvider.cs b/Oqtane.Server/Providers/DatabaseSearchProvider.cs index 96fa1b1f..a2313c59 100644 --- a/Oqtane.Server/Providers/DatabaseSearchProvider.cs +++ b/Oqtane.Server/Providers/DatabaseSearchProvider.cs @@ -129,6 +129,36 @@ namespace Oqtane.Providers return Task.CompletedTask; } + 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); + } + private void AnalyzeSearchContent(SearchContent searchContent, Dictionary siteSettings) { var ignoreWords = IgnoreWords.Split(','); @@ -180,14 +210,15 @@ namespace Oqtane.Providers private static Dictionary GetWords(string content, string[] ignoreWords, int minimumWordLength) { - content = FormatText(content); + content = FormatContent(content); var words = new Dictionary(); if (!string.IsNullOrEmpty(content)) { - foreach (var word in content.Split(' ')) + foreach (var term in content.Split(' ')) { + var word = term.ToLower().Trim(); if (word.Length >= minimumWordLength && !ignoreWords.Contains(word)) { if (!words.ContainsKey(word)) @@ -205,48 +236,16 @@ namespace Oqtane.Providers return words; } - private static string FormatText(string text) + private static string FormatContent(string text) { text = HtmlEntity.DeEntitize(text); - foreach (var punctuation in ".?!,;:-_()[]{}'\"/\\".ToCharArray()) + foreach (var punctuation in ".?!,;:_()[]{}'\"/\\".ToCharArray()) { text = text.Replace(punctuation, ' '); } - text = text.Replace(" ", " ").ToLower().Trim(); - 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); - } - public Task ResetIndex() { _searchContentRepository.DeleteAllSearchContent(); diff --git a/Oqtane.Server/Repository/SearchContentRepository.cs b/Oqtane.Server/Repository/SearchContentRepository.cs index abfcd276..cdc07513 100644 --- a/Oqtane.Server/Repository/SearchContentRepository.cs +++ b/Oqtane.Server/Repository/SearchContentRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; using Oqtane.Models; using Oqtane.Shared; @@ -26,9 +27,14 @@ namespace Oqtane.Repository .ThenInclude(w => w.SearchWord) .Where(i => i.SiteId == searchQuery.SiteId); - if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any()) + if (!string.IsNullOrEmpty(searchQuery.IncludeEntities)) { - searchContents = searchContents.Where(i => searchQuery.EntityNames.Contains(i.EntityName)); + searchContents = searchContents.Where(i => searchQuery.IncludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(i.EntityName)); + } + + if (!string.IsNullOrEmpty(searchQuery.ExcludeEntities)) + { + searchContents = searchContents.Where(i => !searchQuery.ExcludeEntities.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(i.EntityName)); } if (searchQuery.From != DateTime.MinValue) diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css deleted file mode 100644 index 71194e38..00000000 --- a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.SearchResults/Module.css +++ /dev/null @@ -1,3 +0,0 @@ -.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.Shared/Interfaces/ISearchProvider.cs b/Oqtane.Shared/Interfaces/ISearchProvider.cs index 4b9a451d..43981e8d 100644 --- a/Oqtane.Shared/Interfaces/ISearchProvider.cs +++ b/Oqtane.Shared/Interfaces/ISearchProvider.cs @@ -1,7 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Oqtane.Models; diff --git a/Oqtane.Shared/Models/SearchQuery.cs b/Oqtane.Shared/Models/SearchQuery.cs index 00a83533..d3f8ac0c 100644 --- a/Oqtane.Shared/Models/SearchQuery.cs +++ b/Oqtane.Shared/Models/SearchQuery.cs @@ -12,7 +12,9 @@ namespace Oqtane.Models public string Keywords { get; set; } - public List EntityNames { get; set; } = new List(); + public string IncludeEntities { get; set; } = ""; // comma delimited entities to include + + public string ExcludeEntities { get; set; } = ""; // comma delimited entities to exclude public DateTime From { get; set; } diff --git a/Oqtane.Shared/Shared/SearchUtils.cs b/Oqtane.Shared/Shared/SearchUtils.cs index bf5f5986..553275a5 100644 --- a/Oqtane.Shared/Shared/SearchUtils.cs +++ b/Oqtane.Shared/Shared/SearchUtils.cs @@ -4,13 +4,6 @@ namespace Oqtane.Shared { public sealed class SearchUtils { - private static readonly List _systemPages; - - static SearchUtils() - { - _systemPages = new List { "login", "register", "profile", "404", "search" }; - } - public static List GetKeywords(string keywords) { var keywordsList = new List(); @@ -27,10 +20,5 @@ namespace Oqtane.Shared return keywordsList; } - - public static bool IsSystemPage(Models.Page page) - { - return page.Path.Contains("admin") || _systemPages.Contains(page.Path); - } } } From befa13eaf236b7b64d722900f6eb93e201790924 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 15:10:40 -0400 Subject: [PATCH 104/142] update to .NET 8.0.7 --- Oqtane.Client/Oqtane.Client.csproj | 6 +++--- Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj | 4 ++-- .../Oqtane.Database.PostgreSQL.csproj | 2 +- .../Oqtane.Database.SqlServer.csproj | 2 +- .../Oqtane.Database.Sqlite.csproj | 2 +- Oqtane.Server/Oqtane.Server.csproj | 14 +++++++------- Oqtane.Shared/Oqtane.Shared.csproj | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 1599df40..9d0e91b3 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -22,9 +22,9 @@ - - - + + + diff --git a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj index 4f58db37..44bf4e03 100644 --- a/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj +++ b/Oqtane.Database.MySQL/Oqtane.Database.MySQL.csproj @@ -33,8 +33,8 @@ - - + + diff --git a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj index a8eb4718..240f6698 100644 --- a/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj +++ b/Oqtane.Database.PostgreSQL/Oqtane.Database.PostgreSQL.csproj @@ -34,7 +34,7 @@ - + diff --git a/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj b/Oqtane.Database.SqlServer/Oqtane.Database.SqlServer.csproj index 9755e6ea..53385f93 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 4a788d7e..ad4e0361 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.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 7937ca07..68369c9d 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -34,19 +34,19 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 60802f49..c74cf7e2 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -19,8 +19,8 @@ - - + + From 5a2af6d0f921aae4db905fdda22cb63523bb5f19 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 16:17:10 -0400 Subject: [PATCH 105/142] update module template to .NET 8.0.7 --- .../HtmlText/Manager/HtmlTextManager.cs | 4 +-- .../[Owner].Module.[Module]/Index.razor | 3 +- .../[Owner].Module.[Module].Client.csproj | 6 ++-- .../Server/Manager/[Module]Manager.cs | 33 ++++++++++--------- .../[Owner].Module.[Module].Server.csproj | 8 ++--- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index 6c61c25e..d0a9f302 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -66,8 +66,8 @@ namespace Oqtane.Modules.HtmlText.Manager Title = pageModule.Module.Title, Description = string.Empty, Body = htmltext.Content, - ContentModifiedBy = htmltext.ModifiedBy, - ContentModifiedOn = htmltext.ModifiedOn + ContentModifiedBy = htmltext.CreatedBy, + ContentModifiedOn = htmltext.CreatedOn }); } } diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor index ac84a97e..4c68ada6 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Index.razor @@ -38,8 +38,7 @@ else } @code { - // uncomment the following line to use Static render mode for this component - // public override string RenderMode => RenderModes.Static; + public override string RenderMode => RenderModes.Static; public override List Resources => new List() { 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 4181f2df..1360e50b 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/Manager/[Module]Manager.cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/Manager/[Module]Manager.cs index dcd9e432..63945d3d 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 @@ -9,6 +9,7 @@ using Oqtane.Interfaces; using Oqtane.Enums; using Oqtane.Repository; using [Owner].Module.[Module].Repository; +using System.Threading.Tasks; namespace [Owner].Module.[Module].Manager { @@ -60,23 +61,25 @@ namespace [Owner].Module.[Module].Manager } } - public List GetSearchContents(Oqtane.Models.Module module, DateTime startTime) + public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) { - var searchContentList = new List(); + 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 - }); - } + foreach (var [Module] in _[Module]Repository.Get[Module]s(pageModule.ModuleId)) + { + if ([Module].ModifiedOn >= lastIndexedOn) + { + searchContentList.Add(new SearchContent + { + Title = pageModule.Module.Title, + Body = [Module].Name, + ContentModifiedBy = [Module].ModifiedBy, + ContentModifiedOn = [Module].ModifiedOn + }); + } + } - return searchContentList; + return Task.FromResult(searchContentList); } -} + } } 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 43859ecc..e76e299d 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 @@ - - - - + + + + From d822225465f192473aaebf150c7cb9b00f5baca3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 16:22:01 -0400 Subject: [PATCH 106/142] use Task.FromResult() --- Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs | 8 ++------ Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs | 6 ++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs index 2f600b3a..03926ea8 100644 --- a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs +++ b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs @@ -1,6 +1,5 @@ using Oqtane.Models; using Oqtane.Repository; -using Oqtane.Documentation; using Oqtane.Interfaces; using System.Collections.Generic; using System; @@ -22,10 +21,8 @@ namespace Oqtane.Modules.Admin.Files.Manager _fileRepository = fileRepository; } - public async Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) + public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) { - await Task.CompletedTask; - var searchContents = new List(); var folders = _folderRepository.GetFolders(pageModule.Module.SiteId); @@ -72,11 +69,10 @@ namespace Oqtane.Modules.Admin.Files.Manager }; searchContents.Add(searchContent); } - } } - return searchContents; + return Task.FromResult(searchContents); } } } diff --git a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs index d0a9f302..744fb7b5 100644 --- a/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs +++ b/Oqtane.Server/Modules/HtmlText/Manager/HtmlTextManager.cs @@ -49,10 +49,8 @@ namespace Oqtane.Modules.HtmlText.Manager return content; } - public async Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) + public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn) { - await Task.CompletedTask; - var searchContents = new List(); var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId); @@ -72,7 +70,7 @@ namespace Oqtane.Modules.HtmlText.Manager } } - return searchContents; + return Task.FromResult(searchContents); } public void ImportModule(Module module, string content, string version) From efbe4d697c50f29dcce472b5770978bd835d07d6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 16:23:15 -0400 Subject: [PATCH 107/142] update theme template to .NET 8.0.7 --- .../External/Client/[Owner].Theme.[Theme].Client.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 706882e3..767bd1c0 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -13,9 +13,9 @@ - - - + + + From d7a290c5959df63e3c8013fafe92ac2966eeadfa Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 16:34:02 -0400 Subject: [PATCH 108/142] add localization to search settings --- .../Modules/Admin/Search/Index.razor | 6 +- .../Resources/Modules/Admin/Search/Index.resx | 162 ++++++++++++++++++ 2 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 Oqtane.Client/Resources/Modules/Admin/Search/Index.resx diff --git a/Oqtane.Client/Modules/Admin/Search/Index.razor b/Oqtane.Client/Modules/Admin/Search/Index.razor index 9ef34411..23304e8d 100644 --- a/Oqtane.Client/Modules/Admin/Search/Index.razor +++ b/Oqtane.Client/Modules/Admin/Search/Index.razor @@ -82,12 +82,12 @@ settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true); settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, true); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); - AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success); + AddModuleMessage(Localizer["Success.Save"], MessageType.Success); } catch (Exception ex) { - await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); - AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); + await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message); + AddModuleMessage(Localizer["Error.Save"], MessageType.Error); } } } \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx new file mode 100644 index 00000000..b43928bb --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Enabled? + + + Specify if search indexing is enabled + + + Last Indexed: + + + The date/time which the site was last indexed on + + + Ignore Paths: + + + Comma delimited list of page paths which should be ignored + + + Ignore Entities: + + + Comma delimited list of entities which should be ignored + + + Word Length: + + + Minimum length of a word to be indexed + + + Ignore Words: + + + Comma delimited list of words which should be ignored + + + Search Settings Saved Successfully + + + Error Saving Search Settings + + \ No newline at end of file From 0c80e28754dd2757f3d3009dda44c139127d96b9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 17:12:24 -0400 Subject: [PATCH 109/142] add Refresh option for Job Logs --- Oqtane.Client/Modules/Admin/Jobs/Index.razor | 22 ++++++++++++------- Oqtane.Client/Modules/Admin/Jobs/Log.razor | 14 ++++++++++++ .../Resources/Modules/Admin/Jobs/Index.resx | 4 ++-- .../Resources/Modules/Admin/Jobs/Log.resx | 3 +++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor index ce5f2b55..81c36430 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor @@ -10,9 +10,7 @@ } else { - - -
+
@@ -44,21 +42,29 @@ else + +
+ } @code { private List _jobs; - public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } - + public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } } + protected override async Task OnInitializedAsync() { - _jobs = await JobService.GetJobsAsync(); + await GetJobs(); if (_jobs.Count == 0) { AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning); } - } + } + + private async Task GetJobs() + { + _jobs = await JobService.GetJobsAsync(); + } private string DisplayStatus(bool isEnabled, bool isExecuting) { @@ -146,7 +152,7 @@ else private async Task Refresh() { - _jobs = await JobService.GetJobsAsync(); + await GetJobs(); StateHasChanged(); } } diff --git a/Oqtane.Client/Modules/Admin/Jobs/Log.razor b/Oqtane.Client/Modules/Admin/Jobs/Log.razor index 23b605c2..e82ea9ae 100644 --- a/Oqtane.Client/Modules/Admin/Jobs/Log.razor +++ b/Oqtane.Client/Modules/Admin/Jobs/Log.razor @@ -10,6 +10,9 @@ } else { + +

+
@SharedLocalizer["Name"] @@ -35,6 +38,11 @@ else public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; protected override async Task OnParametersSetAsync() + { + await GetJobLogs(); + } + + private async Task GetJobLogs() { _jobLogs = await JobLogService.GetJobLogsAsync(); @@ -67,4 +75,10 @@ else return status; } + + private async Task Refresh() + { + await GetJobLogs(); + StateHasChanged(); + } } diff --git a/Oqtane.Client/Resources/Modules/Admin/Jobs/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Jobs/Index.resx index d4c6b52a..60e347c5 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Jobs/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Jobs/Index.resx @@ -144,8 +144,8 @@ Month(s) - - View Logs + + View All Logs Frequency diff --git a/Oqtane.Client/Resources/Modules/Admin/Jobs/Log.resx b/Oqtane.Client/Resources/Modules/Admin/Jobs/Log.resx index d86eb2c2..91571889 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Jobs/Log.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Jobs/Log.resx @@ -132,4 +132,7 @@ Failed + + Refresh + \ No newline at end of file From 45afbbdac62d2c420eb439b5862967f176d0ad27 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 19:34:19 -0400 Subject: [PATCH 110/142] allow search content permissions to support roles --- Oqtane.Server/Services/SearchService.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Oqtane.Server/Services/SearchService.cs b/Oqtane.Server/Services/SearchService.cs index 06f8c46b..29323071 100644 --- a/Oqtane.Server/Services/SearchService.cs +++ b/Oqtane.Server/Services/SearchService.cs @@ -90,12 +90,23 @@ namespace Oqtane.Services var visible = true; foreach (var permission in searchContent.Permissions.Split(',')) { - var entityName = permission.Split(":")[0]; - var entityId = int.Parse(permission.Split(":")[1]); - if (!_userPermissions.IsAuthorized(_accessor.HttpContext.User, searchQuery.SiteId, entityName, entityId, PermissionNames.View)) + if (permission.Contains(":")) // permission { - visible = false; - break; + var entityName = permission.Split(":")[0]; + var entityId = int.Parse(permission.Split(":")[1]); + if (!_userPermissions.IsAuthorized(_accessor.HttpContext.User, searchQuery.SiteId, entityName, entityId, PermissionNames.View)) + { + visible = false; + break; + } + } + else // role name + { + if (!_accessor.HttpContext.User.IsInRole(permission)) + { + visible = false; + break; + } } } return visible; From 7ee6775251c29f2dd1823daa9e68ec95b228ceb9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 19:52:44 -0400 Subject: [PATCH 111/142] remove hardcoded names when using GetInterface() --- Oqtane.Server/Controllers/ModuleDefinitionController.cs | 2 +- Oqtane.Server/Infrastructure/DatabaseManager.cs | 2 +- Oqtane.Server/Pages/Sitemap.cshtml.cs | 2 +- Oqtane.Server/Repository/ModuleDefinitionRepository.cs | 2 +- Oqtane.Server/Repository/ModuleRepository.cs | 4 ++-- Oqtane.Server/Repository/SiteRepository.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 2176c9f3..a637be69 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -194,7 +194,7 @@ namespace Oqtane.Controllers { try { - if (moduletype.GetInterface("IInstallable") != null) + if (moduletype.GetInterface(nameof(IInstallable)) != null) { _tenantManager.SetTenant(tenant.TenantId); var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index ad472f37..3d9be5de 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -452,7 +452,7 @@ namespace Oqtane.Infrastructure { try { - if (moduleType.GetInterface("IInstallable") != null) + if (moduleType.GetInterface(nameof(IInstallable)) != null) { tenantManager.SetTenant(tenant.TenantId); var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable; diff --git a/Oqtane.Server/Pages/Sitemap.cshtml.cs b/Oqtane.Server/Pages/Sitemap.cshtml.cs index 2435f831..96501294 100644 --- a/Oqtane.Server/Pages/Sitemap.cshtml.cs +++ b/Oqtane.Server/Pages/Sitemap.cshtml.cs @@ -73,7 +73,7 @@ namespace Oqtane.Pages if (moduleDefinition != null && moduleDefinition.ServerManagerType != "") { Type moduletype = Type.GetType(moduleDefinition.ServerManagerType); - if (moduletype != null && moduletype.GetInterface("ISitemap") != null) + if (moduletype != null && moduletype.GetInterface(nameof(ISitemap)) != null) { try { diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index f20c3d13..db20cd58 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -340,7 +340,7 @@ namespace Oqtane.Repository if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) { Type servertype = Type.GetType(moduledefinition.ServerManagerType); - if (servertype != null && servertype.GetInterface("IPortable") != null) + if (servertype != null && servertype.GetInterface(nameof(IPortable)) != null) { moduledefinition.IsPortable = true; } diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs index 4d4a7e4e..5885ae9c 100644 --- a/Oqtane.Server/Repository/ModuleRepository.cs +++ b/Oqtane.Server/Repository/ModuleRepository.cs @@ -107,7 +107,7 @@ namespace Oqtane.Repository if (moduledefinition.ServerManagerType != "") { Type moduletype = Type.GetType(moduledefinition.ServerManagerType); - if (moduletype != null && moduletype.GetInterface("IPortable") != null) + if (moduletype != null && moduletype.GetInterface(nameof(IPortable)) != null) { try { @@ -152,7 +152,7 @@ namespace Oqtane.Repository if (moduledefinition.ServerManagerType != "") { Type moduletype = Type.GetType(moduledefinition.ServerManagerType); - if (moduletype != null && moduletype.GetInterface("IPortable") != null) + if (moduletype != null && moduletype.GetInterface(nameof(IPortable)) != null) { try { diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index 37eba499..4e4ae06d 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -542,7 +542,7 @@ namespace Oqtane.Repository if (pageTemplateModule.Content != "" && moduleDefinition.ServerManagerType != "") { Type moduletype = Type.GetType(moduleDefinition.ServerManagerType); - if (moduletype != null && moduletype.GetInterface("IPortable") != null) + if (moduletype != null && moduletype.GetInterface(nameof(IPortable)) != null) { try { From f2c5dca5e7287d14a6b5c8054f28c907ed0e95bf Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Jul 2024 20:52:13 -0400 Subject: [PATCH 112/142] set Prerender on Login component --- Oqtane.Client/Modules/Admin/Login/Index.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 8ce6ea86..9b4f059b 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -93,6 +93,7 @@ private string _code = string.Empty; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; + public override bool? Prerender => true; public override List Resources => new List() { From 6a1014d8c1654ccf2d6d06593dfb150e24116439 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 18 Jul 2024 11:01:01 -0400 Subject: [PATCH 113/142] QuillJSTextEditor setting improvements --- .../Modules/Controls/QuillJSTextEditor.razor | 267 ++++++++++++------ .../Modules/Controls/QuillJSTextEditor.resx | 3 + Oqtane.Client/Resources/SharedResources.resx | 12 + 3 files changed, 189 insertions(+), 93 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor index 95f9fb0c..167514ae 100644 --- a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor @@ -6,7 +6,7 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer -
+
@if (_allowRichText) { @@ -63,12 +63,6 @@ } - @if (_hasAdminPermission) - { - - - - }
@@ -94,10 +88,6 @@ @((MarkupString)"  ") } - else if(_hasAdminPermission) - { - - }
@if (ReadOnly) { @@ -109,66 +99,93 @@ } } + @if (_allowSettings) + { + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
@@ -61,7 +61,7 @@ private string _searchProvider; private string _enabled; private string _lastIndexedOn; - private string _ignorePaths; + private string _ignorePages; private string _ignoreEntities; private string _minimumWordLength; private string _ignoreWords; @@ -72,7 +72,7 @@ _searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName); _enabled = SettingService.GetSetting(settings, "Search_Enabled", "True"); _lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", ""); - _ignorePaths = SettingService.GetSetting(settings, "Search_IgnorePaths", ""); + _ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", ""); _ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", ""); _minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3"); _ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", ""); @@ -86,7 +86,7 @@ settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider); settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true); settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true); - settings = SettingService.SetSetting(settings, "Search_IgnorePaths", _ignorePaths, true); + settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true); settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true); settings = SettingService.SetSetting(settings, "Search_MininumWordLength", _minimumWordLength, true); settings = SettingService.SetSetting(settings, "Search_IgnoreWords", _ignoreWords, true); diff --git a/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx index 06f2a834..0e0ce755 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Search/Index.resx @@ -129,11 +129,11 @@ The date/time which the site was last indexed on - - Ignore Paths: + + Ignore Pages: - - Comma delimited list of page paths which should be ignored + + Comma delimited list of pages which should be ignored (based on page path) Ignore Entities: diff --git a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs index 0374cd2d..12f3957d 100644 --- a/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/SearchIndexJob.cs @@ -16,7 +16,7 @@ namespace Oqtane.Infrastructure { private const string SearchLastIndexedOnSetting = "Search_LastIndexedOn"; private const string SearchEnabledSetting = "Search_Enabled"; - private const string SearchIgnorePathsSetting = "Search_IgnorePaths"; + private const string SearchIgnorePagesSetting = "Search_IgnorePages"; private const string SearchIgnoreEntitiesSetting = "Search_IgnoreEntities"; public SearchIndexJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) @@ -59,7 +59,7 @@ namespace Oqtane.Infrastructure var currentTime = DateTime.UtcNow; var lastIndexedOn = Convert.ToDateTime(siteSettings.GetValue(SearchLastIndexedOnSetting, DateTime.MinValue.ToString())); - var ignorePaths = siteSettings.GetValue(SearchIgnorePathsSetting, "").Split(','); + var ignorePages = siteSettings.GetValue(SearchIgnorePagesSetting, "").Split(','); var ignoreEntities = siteSettings.GetValue(SearchIgnoreEntitiesSetting, "").Split(','); var pages = pageRepository.GetPages(site.SiteId); @@ -69,7 +69,7 @@ namespace Oqtane.Infrastructure // index pages foreach (var page in pages) { - if (!string.IsNullOrEmpty(page.Path) && (Constants.InternalPagePaths.Contains(page.Path) || ignorePaths.Contains(page.Path))) + if (!string.IsNullOrEmpty(page.Path) && (Constants.InternalPagePaths.Contains(page.Path) || ignorePages.Contains(page.Path))) { continue; } From 0fea8365b8c5fd56b0a3bcc4370d35d003f175a6 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Sun, 21 Jul 2024 09:18:41 -0400 Subject: [PATCH 129/142] add documentation --- Oqtane.Server/Extensions/HttpContextExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Oqtane.Server/Extensions/HttpContextExtensions.cs b/Oqtane.Server/Extensions/HttpContextExtensions.cs index f5fc5597..36e70de1 100644 --- a/Oqtane.Server/Extensions/HttpContextExtensions.cs +++ b/Oqtane.Server/Extensions/HttpContextExtensions.cs @@ -7,6 +7,7 @@ namespace Oqtane.Extensions { public static class HttpContextExtensions { + // this method should only be used in scenarios where HttpContent exists (ie. within Controllers) public static Alias GetAlias(this HttpContext context) { if (context != null && context.Items.ContainsKey(Constants.HttpContextAliasKey)) @@ -16,6 +17,7 @@ namespace Oqtane.Extensions return null; } + // this method should only be used in scenarios where HttpContent exists (ie. within Controllers) public static Dictionary GetSiteSettings(this HttpContext context) { if (context != null && context.Items.ContainsKey(Constants.HttpContextSiteSettingsKey)) From 1c01087eda8161db2ec3f344740339a6116b0003 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Jul 2024 08:11:09 -0400 Subject: [PATCH 130/142] fix #4450 QuillJSTextEditor settings --- Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor index a291dacb..05a40d9e 100644 --- a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor @@ -257,8 +257,6 @@ { Placeholder = Localizer["Placeholder"]; } - - LoadSettings(); } protected override void OnParametersSet() @@ -267,6 +265,8 @@ { _activetab = "Raw"; } + + LoadSettings(); } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -280,8 +280,6 @@ await base.OnAfterRenderAsync(firstRender); - LoadSettings(); - if (_allowRichText) { if (firstRender) From 8b2e55a96978cc0994da2ada6256f0b8f71430a3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Jul 2024 13:31:24 -0400 Subject: [PATCH 131/142] remove ITextEditorProvider interface --- .../OqtaneServiceCollectionExtensions.cs | 5 ++++ Oqtane.Client/Modules/Admin/Site/Index.razor | 24 +++++++++++-------- .../Modules/Controls/QuillJSTextEditor.razor | 2 ++ .../Modules/Controls/RichTextEditor.razor | 16 ++++++------- .../Modules/Controls/TextAreaTextEditor.razor | 2 ++ .../Providers/QuillJSTextEditorProvider.cs | 11 --------- .../Providers/TextAreaTextEditorProvider.cs | 11 --------- .../Resources/Modules/Admin/Site/Index.resx | 4 ++-- .../OqtaneServiceCollectionExtensions.cs | 9 ++++--- Oqtane.Shared/Interfaces/ITextEditor.cs | 2 ++ .../Interfaces/ITextEditorProvider.cs | 18 -------------- Oqtane.Shared/Shared/Constants.cs | 2 +- 12 files changed, 40 insertions(+), 66 deletions(-) delete mode 100644 Oqtane.Client/Providers/QuillJSTextEditorProvider.cs delete mode 100644 Oqtane.Client/Providers/TextAreaTextEditorProvider.cs delete mode 100644 Oqtane.Shared/Interfaces/ITextEditorProvider.cs diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index 7dcb2575..8facf91f 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components.Authorization; +using Oqtane.Interfaces; using Oqtane.Providers; using Oqtane.Services; using Oqtane.Shared; @@ -51,6 +52,10 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); + // providers + services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index d51aa4a1..b708149c 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -131,14 +131,14 @@
- +
- + @if (_textEditors != null) { - @foreach (var provider in _textEditorProviders) + @foreach (var textEditor in _textEditors) { - + } } @@ -428,8 +428,8 @@ private string _containertype = ""; private string _admincontainertype = ""; - private IEnumerable _textEditorProviders; - private string _textEditorProvider = ""; + private Dictionary _textEditors = new Dictionary(); + private string _textEditor = ""; private string _imageFiles = string.Empty; private string _uploadableFiles = string.Empty; @@ -515,8 +515,12 @@ _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; // functionality - _textEditorProviders = ServiceProvider.GetServices(); - _textEditorProvider = SettingService.GetSetting(settings, "TextEditorProvider", Constants.DefaultTextEditorProvider); + var textEditors = ServiceProvider.GetServices(); + foreach (var textEditor in textEditors) + { + _textEditors.Add(textEditor.Name, Utilities.GetFullTypeName(textEditor.GetType().AssemblyQualifiedName)); + } + _textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor); _imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles); _imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles; _uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles); @@ -724,7 +728,7 @@ settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true); // functionality - settings = SettingService.SetSetting(settings, "TextEditorProvider", _textEditorProvider); + settings = SettingService.SetSetting(settings, "TextEditor", _textEditor); settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false); settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false); diff --git a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor index 05a40d9e..db89b181 100644 --- a/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor @@ -177,6 +177,8 @@
@code { + public string Name => "QuillJS"; + private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client"; private bool _settingsLoaded; diff --git a/Oqtane.Client/Modules/Controls/RichTextEditor.razor b/Oqtane.Client/Modules/Controls/RichTextEditor.razor index 4a369952..630b1ed7 100644 --- a/Oqtane.Client/Modules/Controls/RichTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/RichTextEditor.razor @@ -14,7 +14,7 @@
@code { - private string _textEditorProvider; + private string _textEditorType; private RenderFragment _textEditorComponent; private ITextEditor _textEditor; @@ -35,7 +35,7 @@ protected override async Task OnInitializedAsync() { - _textEditorProvider = await GetTextEditorType(); + _textEditorType = await GetTextEditorType(); } protected override void OnParametersSet() @@ -63,9 +63,9 @@ private void CreateTextEditor(RenderTreeBuilder builder) { - if(!string.IsNullOrEmpty(_textEditorProvider)) + if(!string.IsNullOrEmpty(_textEditorType)) { - var editorType = Type.GetType(_textEditorProvider); + var editorType = Type.GetType(_textEditorType); if (editorType != null) { builder.OpenComponent(0, editorType); @@ -111,18 +111,18 @@ private async Task GetTextEditorType() { - const string EditorSettingName = "TextEditorProvider"; + const string EditorSettingName = "TextEditor"; if(!string.IsNullOrEmpty(Provider)) { - var provider = ServiceProvider.GetServices().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase)); + var provider = ServiceProvider.GetServices().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase)); if(provider != null) { - return provider.EditorType; + return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName); } } var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); - return SettingService.GetSetting(settings, EditorSettingName, Constants.DefaultTextEditorProvider); + return SettingService.GetSetting(settings, EditorSettingName, Constants.DefaultTextEditor); } } diff --git a/Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor b/Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor index d2e0185c..f8bb9565 100644 --- a/Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/TextAreaTextEditor.razor @@ -7,6 +7,8 @@
@code { + public string Name => "TextArea"; + private ElementReference _editor; private string _content; diff --git a/Oqtane.Client/Providers/QuillJSTextEditorProvider.cs b/Oqtane.Client/Providers/QuillJSTextEditorProvider.cs deleted file mode 100644 index 311743f8..00000000 --- a/Oqtane.Client/Providers/QuillJSTextEditorProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Oqtane.Interfaces; - -namespace Oqtane.Providers -{ - public class QuillJSTextEditorProvider : ITextEditorProvider - { - public string Name => "QuillJS"; - - public string EditorType => "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client"; - } -} diff --git a/Oqtane.Client/Providers/TextAreaTextEditorProvider.cs b/Oqtane.Client/Providers/TextAreaTextEditorProvider.cs deleted file mode 100644 index 3fde4231..00000000 --- a/Oqtane.Client/Providers/TextAreaTextEditorProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Oqtane.Interfaces; - -namespace Oqtane.Providers -{ - public class TextAreaTextEditorProvider : ITextEditorProvider - { - public string Name => "TextArea"; - - public string EditorType => "Oqtane.Modules.Controls.TextAreaTextEditor, Oqtane.Client"; - } -} diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx index 392a68a6..b232fe61 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx @@ -426,10 +426,10 @@ Interactivity: - + Select the text editor for the site - + Text Editor: diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 883d5033..d9b31fd9 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -102,7 +102,10 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); - + + // providers + services.AddScoped(); + services.AddScoped(); return services; } @@ -150,10 +153,6 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); - // providers - services.AddTransient(); - services.AddTransient(); - // obsolete - replaced by ITenantManager services.AddTransient(); diff --git a/Oqtane.Shared/Interfaces/ITextEditor.cs b/Oqtane.Shared/Interfaces/ITextEditor.cs index 67f4efab..f67441e5 100644 --- a/Oqtane.Shared/Interfaces/ITextEditor.cs +++ b/Oqtane.Shared/Interfaces/ITextEditor.cs @@ -7,6 +7,8 @@ namespace Oqtane.Interfaces /// public interface ITextEditor { + string Name { get; } + /// /// initializes the editor with the initialize content. /// diff --git a/Oqtane.Shared/Interfaces/ITextEditorProvider.cs b/Oqtane.Shared/Interfaces/ITextEditorProvider.cs deleted file mode 100644 index a00c6817..00000000 --- a/Oqtane.Shared/Interfaces/ITextEditorProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -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; } - } -} diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index bcd3dc36..4fef964f 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -81,7 +81,7 @@ namespace Oqtane.Shared public const string DefaultSearchProviderName = "DatabaseSearchProvider"; public static readonly string[] InternalPagePaths = { "login", "register", "reset", "404" }; - public const string DefaultTextEditorProvider = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client"; + public const string DefaultTextEditor = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client"; // Obsolete constants From 8ca2f0a49f151321b26fde3cf92a0fb21be858f9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Jul 2024 21:09:35 -0400 Subject: [PATCH 132/142] fix #4284 - handle user role effective and expiry date --- .../Modules/Admin/SearchResults/Index.razor | 4 +- Oqtane.Client/Services/UserService.cs | 2 +- .../Themes/Controls/Theme/Search.razor | 21 +++---- Oqtane.Client/UI/ModuleInstance.razor | 2 +- Oqtane.Client/UI/SiteRouter.razor | 4 +- Oqtane.Server/Controllers/UserController.cs | 50 +++++++++++---- .../Extensions/ClaimsPrincipalExtensions.cs | 10 +-- Oqtane.Server/Managers/UserManager.cs | 63 ++++++++++++------- .../Repository/UserRoleRepository.cs | 41 ++++++++++-- Oqtane.Server/Security/UserPermissions.cs | 28 ++++++--- Oqtane.Shared/Models/User.cs | 2 +- 11 files changed, 153 insertions(+), 74 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index ec21ebc1..4043761a 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -11,7 +11,7 @@
-
+
@Localizer["SearchLabel"] @@ -79,7 +79,7 @@ private SearchResults _searchResults; private bool _loading; - [SupplyParameterFromForm(FormName = "SearchInputForm")] + [SupplyParameterFromForm(FormName = "SearchResultsForm")] public string KeyWords { get => ""; set => _keywords = value; } protected override async Task OnInitializedAsync() diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 9070f884..286fc2d4 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -31,7 +31,7 @@ namespace Oqtane.Services public async Task GetUserAsync(string username, int siteId) { - return await GetUserAsync(username, "", siteId); + return await GetJsonAsync($"{Apiurl}/username/{username}?siteid={siteId}"); } public async Task GetUserAsync(string username, string email, int siteId) diff --git a/Oqtane.Client/Themes/Controls/Theme/Search.razor b/Oqtane.Client/Themes/Controls/Theme/Search.razor index 1c64fa44..69270239 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Search.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Search.razor @@ -4,16 +4,15 @@ @inherits ThemeControlBase @inject IStringLocalizer Localizer @inject NavigationManager NavigationManager -@inject IHttpContextAccessor HttpContext @if (_searchResultsPage != null) { - +
- +
diff --git a/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx new file mode 100644 index 00000000..21ce2c77 --- /dev/null +++ b/Oqtane.Client/Resources/Modules/Admin/SearchResults/Settings.resx @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The number of characters displayed for each search result summary. The default is 255 characters. + + + Body Size: + + + Enter the date range for search results. The default includes all content. + + + Date Range: + + + Descending + + + Comma delimited list of entities to exclude from search results. By default no entities will be excluded. + + + Exlude Entities: + + + Comma delimited list of entities to include in the search results. By default all entities will be included. + + + Include Entities: + + + LastModified + + + The maximum number of search results to retrieve. The default is unlimited. + + + Page Size: + + + Relevance + + + Specify the default sort field + + + Sort By: + + + Specify the default sort order + + + Sort Order: + + + Title + + + To + + \ No newline at end of file From ab5225111655a4db8cf3216fbedc97d382fb0bb4 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 24 Jul 2024 12:59:40 -0400 Subject: [PATCH 139/142] resolve issue with default Blazor theme --- Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor | 3 +-- .../wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css | 10 +++++----- .../wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css | 9 ++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor index 07d9905e..8d682ac9 100644 --- a/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/BlazorTheme/Themes/Default.razor @@ -11,14 +11,13 @@
- + diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css index 481fa649..2e8a7265 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css @@ -129,9 +129,8 @@ display: none !important; } - .app-search input + button{ + .app-search input + button { position: initial; - color: #fff; padding-top: 7px; padding-bottom: 7px; } @@ -139,6 +138,7 @@ .app-search:active, .app-search:hover { display: block; position: fixed; + color: #fff; top: 0; min-height: 60px; width: 100%; @@ -147,18 +147,18 @@ border-radius: 0; } - .app-search:active .app-form-inline, .app-search:hover .app-form-inline{ + .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{ + .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{ + .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; diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css index 862165b4..6bb1aacb 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css @@ -137,9 +137,8 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu display: none !important; } - .app-search input + button{ + .app-search input + button { position: initial; - color: #fff; padding-top: 7px; padding-bottom: 7px; } @@ -155,18 +154,18 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu border-radius: 0; } - .app-search:active .app-form-inline, .app-search:hover .app-form-inline{ + .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{ + .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{ + .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; From d090f446c9de35f4a3bff7fa3f45d084290919a9 Mon Sep 17 00:00:00 2001 From: Leigh Pointer Date: Thu, 25 Jul 2024 13:11:05 +0200 Subject: [PATCH 140/142] Small Notification Job update This update adds the NotificationId to the log to help track down any errors. --- Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 777bb912..b47c5732 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -149,7 +149,7 @@ namespace Oqtane.Infrastructure catch (Exception ex) { // error - log += ex.Message + "
"; + log += $"NotificationId: {notification.NotificationId} - {ex.Message}
"; } } } From 3648f999204a6748d186ac476cc20ebe0952d768 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 25 Jul 2024 09:24:21 -0400 Subject: [PATCH 141/142] resolve localization issue in ActionDialog --- Oqtane.Client/Modules/Controls/ActionDialog.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Oqtane.Client/Modules/Controls/ActionDialog.razor b/Oqtane.Client/Modules/Controls/ActionDialog.razor index b2308d3a..be192a6a 100644 --- a/Oqtane.Client/Modules/Controls/ActionDialog.razor +++ b/Oqtane.Client/Modules/Controls/ActionDialog.razor @@ -162,7 +162,6 @@ else { Text = Action; } - _openText = Text; if (string.IsNullOrEmpty(Class)) { @@ -196,6 +195,7 @@ else Header = Localize(nameof(Header), Header); Message = Localize(nameof(Message), Message); + _openText = Text; _permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList; _authorized = IsAuthorized(); From 40999c3ff487413b660da56eac172857a2a7528a Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 25 Jul 2024 11:12:58 -0400 Subject: [PATCH 142/142] remove Settings button logic from QuillJS text editor interop --- Oqtane.Server/wwwroot/js/quill-interop.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Oqtane.Server/wwwroot/js/quill-interop.js b/Oqtane.Server/wwwroot/js/quill-interop.js index 3b1fbc69..4598d7aa 100644 --- a/Oqtane.Server/wwwroot/js/quill-interop.js +++ b/Oqtane.Server/wwwroot/js/quill-interop.js @@ -19,11 +19,6 @@ Oqtane.RichTextEditor = { }; new Quill(quillElement, options); - - var settingsButton = document.querySelector('.ql-toolbar button.ql-settings'); - if (settingsButton !== null) { - settingsButton.innerHTML = ''; - } }, getQuillContent: function (editorElement) { return JSON.stringify(editorElement.__quill.getContents());