Merge remote-tracking branch 'oqtane/dev' into dev

This commit is contained in:
Mark Davis
2024-07-05 21:53:14 -07:00
183 changed files with 5353 additions and 2178 deletions

View File

@ -16,6 +16,7 @@
@using Oqtane.Shared
@using Oqtane.Themes
@using Oqtane.Extensions
@using System.Globalization
@inject NavigationManager NavigationManager
@inject IAntiforgery Antiforgery
@inject IConfigManager ConfigManager
@ -39,7 +40,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="/" />
<link rel="stylesheet" href="css/app.css" />
@if (!string.IsNullOrEmpty(_PWAScript))
@if (_scripts.Contains("PWA Manifest"))
{
<link id="app-manifest" rel="manifest" />
}
@ -68,20 +69,13 @@
<Routes PageState="@_pageState" RenderMode="@_renderMode" Runtime="@_runtime" AntiForgeryToken="@_antiForgeryToken" AuthorizationToken="@_authorizationToken" Platform="@_platform" @rendermode="InteractiveRenderMode.GetInteractiveRenderMode(_runtime, _prerender)" />
}
@if (!string.IsNullOrEmpty(_reconnectScript))
{
@((MarkupString)_reconnectScript)
}
@if (!string.IsNullOrEmpty(_PWAScript))
{
@((MarkupString)_PWAScript)
}
@((MarkupString)_bodyResources)
<script src="_framework/blazor.web.js"></script>
<script src="js/app.js"></script>
<script src="js/loadjs.min.js"></script>
<script src="js/interop.js"></script>
<script src="_framework/blazor.web.js"></script>
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
}
else
{
@ -105,8 +99,7 @@
private string _headResources = "";
private string _bodyResources = "";
private string _styleSheets = "";
private string _PWAScript = "";
private string _reconnectScript = "";
private string _scripts = "";
private string _message = "";
private PageState _pageState;
@ -139,6 +132,7 @@
_renderMode = site.RenderMode;
_runtime = site.Runtime;
_prerender = site.Prerender;
var modules = new List<Module>();
Route route = new Route(url, alias.Path);
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
@ -163,6 +157,10 @@
{
HandlePageNotFound(site, page, route);
}
else
{
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
}
if (site.VisitorTracking)
{
@ -175,23 +173,25 @@
CreateJwtToken(alias);
}
// include stylesheets to prevent FOUC
var resources = GetPageResources(alias, site, page, int.Parse(route.ModuleId), route.Action);
// includes resources
var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
ManageStyleSheets(resources);
ManageScripts(resources, alias);
// scripts
if (_renderMode == RenderModes.Static)
{
ManageScripts(resources, alias);
}
// generate scripts
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
{
_reconnectScript = CreateReconnectScript();
_scripts += CreateReconnectScript();
}
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{
_PWAScript = CreatePWAScript(alias, site, route);
_scripts += CreatePWAScript(alias, site, route);
}
@if (_renderMode == RenderModes.Static)
{
_scripts += CreateScrollPositionScript();
}
_headResources += ParseScripts(site.HeadContent);
_bodyResources += ParseScripts(site.BodyContent);
@ -220,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,
@ -329,14 +330,26 @@
int? userid = Context.User.UserId();
userid = (userid == -1) ? null : userid;
// check if cookie already exists
// get cookie value
var visitorCookieName = Constants.VisitorCookiePrefix + SiteId.ToString();
var visitorCookieValue = Context.Request.Cookies[visitorCookieName];
DateTime expiry = DateTime.MinValue;
if (visitorCookieValue != null && visitorCookieValue.Contains("|"))
{
var values = visitorCookieValue.Split('|');
int.TryParse(values[0], out _visitorId);
DateTime.TryParseExact(values[1], "M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture, DateTimeStyles.None, out expiry);
}
else // legacy cookie format
{
int.TryParse(visitorCookieValue, out _visitorId);
}
bool setcookie = false;
Visitor visitor = null;
bool addcookie = false;
var VisitorCookie = Constants.VisitorCookiePrefix + SiteId.ToString();
if (!int.TryParse(Context.Request.Cookies[VisitorCookie], out _visitorId))
if (_visitorId <= 0)
{
// if enabled use IP Address correlation
_visitorId = -1;
var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true"));
if (correlate)
{
@ -344,12 +357,12 @@
if (visitor != null)
{
_visitorId = visitor.VisitorId;
addcookie = true;
setcookie = true;
}
}
}
if (_visitorId == -1)
if (_visitorId <= 0)
{
// create new visitor
visitor = new Visitor();
@ -365,52 +378,59 @@
visitor.VisitedOn = DateTime.UtcNow;
visitor = VisitorRepository.AddVisitor(visitor);
_visitorId = visitor.VisitorId;
addcookie = true;
setcookie = true;
}
else
{
if (visitor == null)
// check expiry
if (DateTime.UtcNow > expiry)
{
// get visitor if it was not previously loaded
visitor = VisitorRepository.GetVisitor(_visitorId);
}
if (visitor != null)
{
// update visitor
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
if (!string.IsNullOrEmpty(referrer))
if (visitor == null)
{
visitor.Referrer = referrer;
// get visitor if not previously loaded
visitor = VisitorRepository.GetVisitor(_visitorId);
}
if (userid != null)
if (visitor != null)
{
visitor.UserId = userid;
// update visitor
visitor.IPAddress = _remoteIPAddress;
visitor.UserAgent = useragent;
visitor.Language = language;
visitor.Url = url;
if (!string.IsNullOrEmpty(referrer))
{
visitor.Referrer = referrer;
}
if (userid != null)
{
visitor.UserId = userid;
}
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
setcookie = true;
}
else
{
// remove cookie if visitor does not exist
Context.Response.Cookies.Delete(visitorCookieName);
}
visitor.Visits += 1;
visitor.VisitedOn = DateTime.UtcNow;
VisitorRepository.UpdateVisitor(visitor);
}
else
{
// remove cookie if VisitorId does not exist
Context.Response.Cookies.Delete(VisitorCookie);
}
}
// append cookie
if (addcookie)
// set cookie
if (setcookie)
{
expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5")));
Context.Response.Cookies.Append(
VisitorCookie,
_visitorId.ToString(),
visitorCookieName,
$"{_visitorId}|{expiry.ToString("M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture)}",
new CookieOptions()
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
IsEssential = true
}
{
Expires = DateTimeOffset.UtcNow.AddYears(10),
IsEssential = true
}
);
}
}
@ -432,7 +452,7 @@
private string CreatePWAScript(Alias alias, Site site, Route route)
{
return
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // PWA Manifest" + Environment.NewLine +
" setTimeout(() => {" + Environment.NewLine +
@ -468,14 +488,14 @@
" console.log('ServiceWorker Registration Failed ', err);" + Environment.NewLine +
" });" + Environment.NewLine +
" };" + Environment.NewLine +
"</script>";
"</script>" + Environment.NewLine;
}
private string CreateReconnectScript()
{
return
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // Blazor Server Reconnect" + Environment.NewLine +
" // Interactive Blazor Server Reconnect" + Environment.NewLine +
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
" async function attemptReload() {" + Environment.NewLine +
@ -487,7 +507,26 @@
" setInterval(attemptReload, 5000);" + Environment.NewLine +
" }" + Environment.NewLine +
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
"</script>";
"</script>" + Environment.NewLine;
}
private string CreateScrollPositionScript()
{
return Environment.NewLine +
"<script>" + Environment.NewLine +
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
" window.interceptNavigation = () => {" + Environment.NewLine +
" let currentUrl = window.location.href;" + Environment.NewLine +
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
" let newUrl = window.location.href;" + Environment.NewLine +
" if (currentUrl != newUrl) {" + Environment.NewLine +
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
" }" + Environment.NewLine +
" currentUrl = newUrl;" + Environment.NewLine +
" });" + Environment.NewLine +
" };" + Environment.NewLine +
" document.onload += window.interceptNavigation();" + Environment.NewLine +
"</script>" + Environment.NewLine;
}
private string ParseScripts(string content)
@ -536,7 +575,7 @@
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((resource.ES6Module) ? " type=\"module\"" : "") +
" src =\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
" src=\"" + url + "\"></script>"; // src at end of element due to enhanced navigation patch algorithm
}
else
{
@ -558,7 +597,7 @@
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
}
private List<Resource> GetPageResources(Alias alias, Site site, Page page, int moduleid, string action)
private async Task<List<Resource>> GetPageResources(Alias alias, Site site, Page page, List<Module> modules, int moduleid, string action)
{
var resources = new List<Resource>();
@ -566,13 +605,13 @@
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
if (theme != null)
{
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
}
else
{
// fallback to default Oqtane theme
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
}
var type = Type.GetType(themeType);
if (type != null)
@ -580,16 +619,16 @@
var obj = Activator.CreateInstance(type) as IThemeControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
}
}
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)
{
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
// handle default action
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
@ -635,7 +674,7 @@
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
if (obj != null)
{
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
{
// settings components are embedded within a framework settings module
@ -643,45 +682,51 @@
if (moduletype != null)
{
obj = Activator.CreateInstance(moduletype) as IModuleControl;
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
}
}
}
}
}
// 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.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
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);
}
}
}
return resources;
}
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
{
if (resources != null)
{
foreach (var resource in resources)
{
if (resource.Url.StartsWith("~"))
if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site)
{
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
if (resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
pageresources.Add(resource.Clone(level, name));
// ensure resource does not exist already
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
{
pageresources.Add(resource.Clone(level, name));
}
}
}
}
@ -692,6 +737,7 @@
{
if (resources != null)
{
// include stylesheets to prevent FOUC
string batch = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff");
int count = 0;
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Stylesheet))

View File

@ -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;

View File

@ -760,7 +760,7 @@ namespace Oqtane.Controllers
{
if (!Directory.Exists(folderpath))
{
string path = "";
string path = folderpath.StartsWith(Path.DirectorySeparatorChar) ? Path.DirectorySeparatorChar.ToString() : string.Empty;
var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
string[] folders = folderpath.Split(separators, StringSplitOptions.RemoveEmptyEntries);
foreach (string folder in folders)

View File

@ -76,5 +76,20 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
[HttpDelete]
[Authorize(Roles = RoleNames.Admin)]
public void Delete(string siteId)
{
if (int.TryParse(siteId, out int parsedSiteId) && parsedSiteId == _alias.SiteId)
{
_logs.DeleteLogs(parsedSiteId, 0); // specifying zero for age results in all logs being deleted
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Log Delete Attempt {SiteId}", siteId);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@ -9,7 +9,6 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using Oqtane.Security;
using System.Net;
using System.Security.Policy;
namespace Oqtane.Controllers
{

View File

@ -365,8 +365,8 @@ namespace Oqtane.Controllers
{
{ "FrameworkVersion", moduleDefinition.Version },
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{moduleDefinition.Version}\" />" },
{ "ServerReference", $"<PackageReference Include=\"Oqtane.Server\" Version=\"{moduleDefinition.Version}\" />" },
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Shared\" Version=\"{moduleDefinition.Version}\" />" },
};
});
}

View File

@ -0,0 +1,41 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Documentation;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.ApiRoute)]
public class SearchResultsController : ModuleControllerBase
{
private readonly ISearchService _searchService;
public SearchResultsController(ISearchService searchService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
{
_searchService = searchService;
}
[HttpPost]
[Authorize(Policy = PolicyNames.ViewModule)]
public async Task<Models.SearchResults> Post([FromBody] Models.SearchQuery searchQuery)
{
try
{
return await _searchService.SearchAsync(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;
}
}
}
}

View File

@ -81,5 +81,12 @@ namespace Oqtane.Controllers
{
await _siteService.DeleteSiteAsync(id);
}
// GET api/<controller>/modules/5/6
[HttpGet("modules/{siteId}/{pageId}")]
public async Task<IEnumerable<Module>> GetModules(int siteId, int pageId)
{
return await _siteService.GetModulesAsync(siteId, pageId);
}
}
}

View File

@ -34,6 +34,7 @@ namespace Oqtane.Controllers
case "environment":
systeminfo.Add("CLRVersion", Environment.Version.ToString());
systeminfo.Add("OSVersion", Environment.OSVersion.ToString());
systeminfo.Add("Process", (Environment.Is64BitProcess) ? "64 Bit" : "32 Bit");
systeminfo.Add("MachineName", Environment.MachineName);
systeminfo.Add("WorkingSet", Environment.WorkingSet.ToString());
systeminfo.Add("TickCount", Environment.TickCount64.ToString());

View File

@ -8,6 +8,7 @@ using Oqtane.Infrastructure;
using Oqtane.Repository;
using System.Net;
using System;
using System.Globalization;
namespace Oqtane.Controllers
{
@ -33,7 +34,7 @@ namespace Oqtane.Controllers
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate));
return _visitors.GetVisitors(SiteId, DateTime.ParseExact(fromdate, "yyyy-MM-dd", CultureInfo.InvariantCulture));
}
else
{

View File

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Routing;
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Antiforgery;
namespace OqtaneSSR.Extensions
{
@ -23,6 +24,7 @@ namespace OqtaneSSR.Extensions
{
routeEndpointBuilder.Metadata.Add(new RootComponentMetadata(typeof(App)));
routeEndpointBuilder.Metadata.Add(new ComponentTypeMetadata(typeof(App)));
routeEndpointBuilder.Metadata.Add(new RequireAntiforgeryTokenAttribute());
});
}
}

View File

@ -19,8 +19,10 @@ 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;
using Oqtane.Repository;
using Oqtane.Security;
using Oqtane.Services;
@ -97,6 +99,10 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<IUrlMappingService, UrlMappingService>();
services.AddScoped<IVisitorService, VisitorService>();
services.AddScoped<ISyncService, SyncService>();
services.AddScoped<ISearchResultsService, SearchResultsService>();
services.AddScoped<ISearchService, SearchService>();
services.AddScoped<ISearchProvider, DatabaseSearchProvider>();
return services;
}
@ -131,6 +137,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<ILanguageRepository, LanguageRepository>();
services.AddTransient<IVisitorRepository, VisitorRepository>();
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
services.AddTransient<ISearchContentRepository, SearchContentRepository>();
// managers
services.AddTransient<IDBContextDependencies, DBContextDependencies>();
@ -143,6 +150,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<IUserManager, UserManager>();
// providers
services.AddTransient<ITextEditorProvider, QuillTextEditorProvider>();
// obsolete - replaced by ITenantManager
services.AddTransient<ITenantResolver, TenantResolver>();

View File

@ -539,6 +539,8 @@ namespace Oqtane.Infrastructure
var identityUserManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var tenant = tenants.GetTenants().FirstOrDefault(item => item.Name == install.TenantName);
var rendermode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value;
var runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value;
site = new Site
{
@ -556,9 +558,9 @@ namespace Oqtane.Infrastructure
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
SiteTemplateType = install.SiteTemplate,
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value,
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value,
Prerender = true,
RenderMode = rendermode,
Runtime = runtime,
Prerender = (rendermode == RenderModes.Interactive),
Hybrid = false
};
site = sites.AddSite(site);

View File

@ -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

View File

@ -197,6 +197,12 @@ namespace Oqtane.Infrastructure
string[] segments = entry.FullName.Split('/'); // ZipArchiveEntries always use unix path separator
string filename = Path.Combine(folder, string.Join(Path.DirectorySeparatorChar, segments, ignoreLeadingSegments, segments.Length - ignoreLeadingSegments));
// validate path to prevent path traversal
if (!Path.GetFullPath(filename).StartsWith(folder + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
{
return "";
}
try
{
if (!Directory.Exists(Path.GetDirectoryName(filename)))
@ -227,6 +233,7 @@ namespace Oqtane.Infrastructure
// an error occurred extracting the file
filename = "";
}
return filename;
}

View File

@ -0,0 +1,90 @@
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
{
private const string SearchIndexStartTimeSettingName = "SearchIndex_StartTime";
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<ISiteRepository>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
var logRepository = provider.GetRequiredService<ILogRepository>();
var searchService = provider.GetRequiredService<ISearchService>();
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}<br />");
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}<br />");
}
return logs.ToString();
}
private DateTime? GetSearchStartTime(int siteId, ISettingRepository settingRepository)
{
var setting = settingRepository.GetSetting(EntityNames.Site, siteId, 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, SearchIndexStartTimeSettingName);
if (setting == null)
{
setting = new Setting
{
EntityName = EntityNames.Site,
EntityId = siteId,
SettingName = SearchIndexStartTimeSettingName,
SettingValue = Convert.ToString(startTime),
};
settingRepository.AddSetting(setting);
}
else
{
setting.SettingValue = Convert.ToString(startTime);
settingRepository.UpdateSetting(setting);
}
}
}
}

View File

@ -203,19 +203,21 @@ namespace Oqtane.Infrastructure
}
if (Enum.Parse<LogLevel>(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}<br /><br />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}<br /><br />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);
}
}
}
}

View File

@ -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<PageTemplate> CreateSite(Site site)
{
var pageTemplates = new List<PageTemplate>();
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission> {
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
}
}
});
// admin pages
pageTemplates.Add(new PageTemplate
{
Name = "Admin",
Parent = "",
Path = "admin",
Order = seed + 51,
Icon = "",
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
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<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
Content = ""
}
}
});
return pageTemplates;
}
}
}

View File

@ -93,6 +93,7 @@ namespace Oqtane.Infrastructure
}
var result = new StringBuilder();
source = source.Replace("[[", "[$_["); //avoid nested square bracket issue.
foreach (Match match in this.TokenizerRegex.Matches(source))
{
var key = match.Result("${key}");
@ -126,7 +127,7 @@ namespace Oqtane.Infrastructure
result.Append(match.Result("${text}"));
}
}
result.Replace("[$_", "["); //restore the changes.
return result.ToString();
}

View File

@ -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<PageTemplate>();
pageTemplates.Add(new PageTemplate
{
Name = "Search",
Parent = "",
Path = "search",
Icon = "oi oi-magnifying-glass",
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.SearchResults, Oqtane.Client", Title = "Search", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
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<IPageRepository>();
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (var site in sites.GetSites().ToList())
{
if (!pages.GetPages(site.SiteId).ToList().Where(item => item.Path == "search").Any())
{
sites.CreatePages(site, pageTemplates, null);
}
}
}
}
}

View File

@ -0,0 +1,145 @@
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
{
public const int ModuleSearchIndexManagerPriority = 200;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ModuleSearchIndexManager> _logger;
private readonly IPageModuleRepository _pageModuleRepostory;
private readonly IPageRepository _pageRepository;
public ModuleSearchIndexManager(
IServiceProvider serviceProvider,
IPageModuleRepository pageModuleRepostory,
ILogger<ModuleSearchIndexManager> logger,
IPageRepository pageRepository)
: base(serviceProvider)
{
_serviceProvider = serviceProvider;
_logger = logger;
_pageModuleRepostory = pageModuleRepostory;
_pageRepository = pageRepository;
}
public override string Name => EntityNames.Module;
public override int Priority => ModuleSearchIndexManagerPriority;
public override int IndexContent(int siteId, DateTime? startTime, Action<List<SearchContent>> processSearchContent, Action<string> handleError)
{
var pageModules = _pageModuleRepostory.GetPageModules(siteId).DistinctBy(i => i.ModuleId);
var searchContentList = new List<SearchContent>();
foreach(var pageModule in pageModules)
{
if(pageModule.Page == null || SearchUtils.IsSystemPage(pageModule.Page))
{
continue;
}
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
{
_logger.LogDebug($"Search: Begin index module {pageModule.ModuleId}.");
var type = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
if (type?.GetInterface(nameof(ISearchable)) != null)
{
try
{
var moduleSearch = (ISearchable)ActivatorUtilities.CreateInstance(_serviceProvider, type);
var contentList = moduleSearch.GetSearchContents(pageModule, startTime.GetValueOrDefault(DateTime.MinValue));
if(contentList != null)
{
foreach(var searchContent in contentList)
{
SaveModuleMetaData(searchContent, pageModule);
searchContentList.Add(searchContent);
}
}
}
catch(Exception ex)
{
_logger.LogError(ex, $"Search: Index module {pageModule.ModuleId} failed.");
handleError($"Search: Index module {pageModule.ModuleId} failed: {ex.Message}");
}
}
_logger.LogDebug($"Search: End index module {pageModule.ModuleId}.");
}
}
processSearchContent(searchContentList);
return searchContentList.Count;
}
private void SaveModuleMetaData(SearchContent searchContent, PageModule pageModule)
{
searchContent.SiteId = pageModule.Module.SiteId;
if(string.IsNullOrEmpty(searchContent.EntityName))
{
searchContent.EntityName = EntityNames.Module;
}
if(string.IsNullOrEmpty(searchContent.EntityId))
{
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)
{
searchContent.ContentModifiedOn = pageModule.ModifiedOn;
}
if (string.IsNullOrEmpty(searchContent.AdditionalContent))
{
searchContent.AdditionalContent = string.Empty;
}
if (pageModule.Page != null)
{
if (string.IsNullOrEmpty(searchContent.Url))
{
searchContent.Url = $"{(!string.IsNullOrEmpty(pageModule.Page.Path) && !pageModule.Page.Path.StartsWith("/") ? "/" : "")}{pageModule.Page.Path}";
}
if (string.IsNullOrEmpty(searchContent.Title))
{
searchContent.Title = !string.IsNullOrEmpty(pageModule.Page.Title) ? pageModule.Page.Title : pageModule.Page.Name;
}
}
if (searchContent.SearchContentProperties == null)
{
searchContent.SearchContentProperties = new List<SearchContentProperty>();
}
if(!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchPageIdPropertyName))
{
searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = pageModule.PageId.ToString() });
}
if (!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchModuleIdPropertyName))
{
searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchModuleIdPropertyName, Value = pageModule.ModuleId.ToString() });
}
}
}
}

View File

@ -0,0 +1,60 @@
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 => EntityNames.Module;
private readonly IServiceProvider _serviceProvider;
public ModuleSearchResultManager(
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public string GetUrl(SearchResult searchResult, SearchQuery searchQuery)
{
var pageRepository = _serviceProvider.GetRequiredService<IPageRepository>();
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);
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(SearchContent searchResult, SearchQuery searchQuery)
{
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);
}
return false;
}
private bool CanViewPage(int pageId, User user)
{
var pageRepository = _serviceProvider.GetRequiredService<IPageRepository>();
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));
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Managers.Search
{
public class PageSearchIndexManager : SearchIndexManagerBase
{
private const int PageSearchIndexManagerPriority = 100;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<ModuleSearchIndexManager> _logger;
private readonly IPageRepository _pageRepository;
public PageSearchIndexManager(
IServiceProvider serviceProvider,
ILogger<ModuleSearchIndexManager> logger,
IPageRepository pageRepository)
: base(serviceProvider)
{
_serviceProvider = serviceProvider;
_logger = logger;
_pageRepository = pageRepository;
}
public override string Name => EntityNames.Page;
public override int Priority => PageSearchIndexManagerPriority;
public override int IndexContent(int siteId, DateTime? startTime, Action<List<SearchContent>> processSearchContent, Action<string> handleError)
{
var startTimeValue = startTime.GetValueOrDefault(DateTime.MinValue);
var pages = _pageRepository.GetPages(siteId).Where(i => i.ModifiedOn >= startTimeValue);
var searchContentList = new List<SearchContent>();
foreach(var page in pages)
{
try
{
if(SearchUtils.IsSystemPage(page))
{
continue;
}
var searchContent = new SearchContent
{
SiteId = page.SiteId,
EntityName = EntityNames.Page,
EntityId = page.PageId.ToString(),
Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name,
Description = string.Empty,
Body = $"{page.Name} {page.Title}",
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)
{
searchContent.SearchContentProperties = new List<SearchContentProperty>();
}
if (!searchContent.SearchContentProperties.Any(i => i.Name == Constants.SearchPageIdPropertyName))
{
searchContent.SearchContentProperties.Add(new SearchContentProperty { Name = Constants.SearchPageIdPropertyName, Value = page.PageId.ToString() });
}
searchContentList.Add(searchContent);
}
catch(Exception ex)
{
_logger.LogError(ex, $"Search: Index page {page.PageId} failed.");
handleError($"Search: Index page {page.PageId} failed: {ex.Message}");
}
}
processSearchContent(searchContentList);
return searchContentList.Count;
}
}
}

View File

@ -0,0 +1,36 @@
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 const string SearchIndexManagerEnabledSettingFormat = "SearchIndexManager_{0}_Enabled";
private readonly IServiceProvider _serviceProvider;
public SearchIndexManagerBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public abstract int Priority { get; }
public abstract string Name { get; }
public abstract int IndexContent(int siteId, DateTime? startDate, Action<List<SearchContent>> processSearchContent, Action<string> handleError);
public virtual bool IsIndexEnabled(int siteId)
{
var settingName = string.Format(SearchIndexManagerEnabledSettingFormat, Name);
var settingRepository = _serviceProvider.GetRequiredService<ISettingRepository>();
var setting = settingRepository.GetSetting(EntityNames.Site, siteId, settingName);
return setting == null || setting.SettingValue == "true";
}
}
}

View File

@ -201,56 +201,54 @@ namespace Oqtane.Managers
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
{
var valid = true;
if (!string.IsNullOrEmpty(user.Password))
{
var validator = new PasswordValidator<IdentityUser>();
var result = await validator.ValidateAsync(_identityUserManager, null, user.Password);
valid = result.Succeeded;
if (valid)
if (result.Succeeded)
{
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
await _identityUserManager.UpdateAsync(identityuser);
}
}
if (valid)
{
if (!string.IsNullOrEmpty(user.Password))
else
{
await _identityUserManager.UpdateAsync(identityuser); // requires password to be provided
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username);
return null;
}
if (user.Email != identityuser.Email)
{
await _identityUserManager.SetEmailAsync(identityuser, user.Email);
// if email address changed and user is not administrator, email verification is required for new email address
if (!user.EmailConfirmed)
{
var alias = _tenantManager.GetAlias();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
else
{
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
}
}
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
}
else
if (user.Email != identityuser.Email)
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username);
user = null;
await _identityUserManager.SetEmailAsync(identityuser, user.Email);
// if email address changed and it is not confirmed, verification is required for new email address
if (!user.EmailConfirmed)
{
var alias = _tenantManager.GetAlias();
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
_notifications.AddNotification(notification);
}
}
if (user.EmailConfirmed)
{
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
}
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_tenantManager.GetAlias(), EntityNames.User, user.UserId, SyncEventActions.Reload);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
}
else
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. User Does Not Exist.", user.Username);
user = null;
}
return user;

View File

@ -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 SearchContentEntityBuilder : BaseEntityBuilder<SearchContentEntityBuilder>
{
private const string _entityTableName = "SearchContent";
private readonly PrimaryKey<SearchContentEntityBuilder> _primaryKey = new("PK_SearchContent", x => x.SearchContentId);
public SearchContentEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
}
protected override SearchContentEntityBuilder BuildTable(ColumnsBuilder table)
{
SearchContentId = AddAutoIncrementColumn(table, "SearchContentId");
SiteId = AddIntegerColumn(table, "SiteId");
EntityName = AddStringColumn(table, "EntityName", 50);
EntityId = AddStringColumn(table, "EntityId", 50);
Title = AddStringColumn(table, "Title", 200);
Description = AddMaxStringColumn(table, "Description");
Body = AddMaxStringColumn(table, "Body");
Url = AddStringColumn(table, "Url", 500);
Permissions = AddStringColumn(table, "Permissions", 100);
ContentModifiedBy = AddStringColumn(table, "ContentModifiedBy", 256);
ContentModifiedOn = AddDateTimeColumn(table, "ContentModifiedOn");
AdditionalContent = AddMaxStringColumn(table, "AdditionalContent");
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
return this;
}
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
public OperationBuilder<AddColumnOperation> EntityName { get; private set; }
public OperationBuilder<AddColumnOperation> EntityId { get; private set; }
public OperationBuilder<AddColumnOperation> Title { get; private set; }
public OperationBuilder<AddColumnOperation> Description { get; private set; }
public OperationBuilder<AddColumnOperation> Body { get; private set; }
public OperationBuilder<AddColumnOperation> Url { get; private set; }
public OperationBuilder<AddColumnOperation> Permissions { get; private set; }
public OperationBuilder<AddColumnOperation> ContentModifiedBy { get; private set; }
public OperationBuilder<AddColumnOperation> ContentModifiedOn { get; private set; }
public OperationBuilder<AddColumnOperation> AdditionalContent { get; private set; }
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
}
}

View File

@ -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<SearchContentPropertyEntityBuilder>
{
private const string _entityTableName = "SearchContentProperty";
private readonly PrimaryKey<SearchContentPropertyEntityBuilder> _primaryKey = new("PK_SearchContentProperty", x => x.PropertyId);
private readonly ForeignKey<SearchContentPropertyEntityBuilder> _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<AddColumnOperation> PropertyId { get; private set; }
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
public OperationBuilder<AddColumnOperation> Name { get; private set; }
public OperationBuilder<AddColumnOperation> Value { get; private set; }
}
}

View File

@ -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<SearchContentWordEntityBuilder>
{
private const string _entityTableName = "SearchContentWord";
private readonly PrimaryKey<SearchContentWordEntityBuilder> _primaryKey = new("PK_SearchContentWord", x => x.SearchContentWordId);
private readonly ForeignKey<SearchContentWordEntityBuilder> _foreignKey1 = new("FK_SearchContentWord_SearchContent", x => x.SearchContentId, "SearchContent", "SearchContentId", ReferentialAction.Cascade);
private readonly ForeignKey<SearchContentWordEntityBuilder> _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<AddColumnOperation> SearchContentWordId { get; private set; }
public OperationBuilder<AddColumnOperation> SearchContentId { get; private set; }
public OperationBuilder<AddColumnOperation> SearchWordId { get; private set; }
public OperationBuilder<AddColumnOperation> Count { get; private set; }
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
public OperationBuilder<AddColumnOperation> ModifiedOn { get; private set; }
}
}

View File

@ -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<SearchWordEntityBuilder>
{
private const string _entityTableName = "SearchWord";
private readonly PrimaryKey<SearchWordEntityBuilder> _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<AddColumnOperation> SearchWordId { get; private set; }
public OperationBuilder<AddColumnOperation> Word { get; private set; }
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
}
}

View File

@ -0,0 +1,50 @@
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 searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentEntityBuilder.Create();
var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentPropertyEntityBuilder.Create();
var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase);
searchWordEntityBuilder.Create();
searchWordEntityBuilder.AddIndex("IX_SearchWord", "Word", true);
var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentWordEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
var searchContentWordEntityBuilder = new SearchContentWordEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentWordEntityBuilder.Drop();
var searchWordEntityBuilder = new SearchWordEntityBuilder(migrationBuilder, ActiveDatabase);
searchWordEntityBuilder.DropIndex("IX_SearchWord");
searchWordEntityBuilder.Drop();
var searchContentPropertyEntityBuilder = new SearchContentPropertyEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentPropertyEntityBuilder.Drop();
var searchContentEntityBuilder = new SearchContentEntityBuilder(migrationBuilder, ActiveDatabase);
searchContentEntityBuilder.Drop();
}
}
}

View File

@ -7,20 +7,30 @@ using Oqtane.Repository;
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, ISearchable
{
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;
@ -29,14 +39,37 @@ namespace Oqtane.Modules.HtmlText.Manager
public string ExportModule(Module module)
{
string content = "";
var htmlText = _htmlText.GetHtmlText(module.ModuleId);
if (htmlText != null)
var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId);
if (htmltexts != null && htmltexts.Any())
{
content = WebUtility.HtmlEncode(htmlText.Content);
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
content = WebUtility.HtmlEncode(htmltext.Content);
}
return content;
}
public List<SearchContent> GetSearchContents(PageModule pageModule, DateTime startDate)
{
var searchContentList = new List<SearchContent>();
var htmltexts = _htmlText.GetHtmlTexts(pageModule.ModuleId);
if (htmltexts != null && htmltexts.Any(i => i.CreatedOn >= startDate))
{
var htmltext = htmltexts.OrderByDescending(item => item.CreatedOn).First();
searchContentList.Add(new SearchContent
{
Title = pageModule.Module.Title,
Description = string.Empty,
Body = htmltext.Content,
ContentModifiedBy = htmltext.ModifiedBy,
ContentModifiedOn = htmltext.ModifiedOn
});
}
return searchContentList;
}
public void ImportModule(Module module, string content, string version)
{
content = WebUtility.HtmlDecode(content);

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>5.1.0</Version>
<Version>5.2.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@ -11,7 +11,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.1.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace>
@ -33,19 +33,20 @@
<EmbeddedResource Include="Scripts\MigrateTenant.sql" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PackageReference Include="HtmlAgilityPack" Version="1.11.61" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.3" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.6" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.6" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.8" />
</ItemGroup>
<ItemGroup>

View File

@ -46,15 +46,27 @@ namespace Oqtane.Pages
{
var sitemap = new List<Sitemap>();
// internal pages which should not be indexed
string[] internalPaths = { "login", "register", "reset", "404" };
// build site map
var rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/")));
var moduleDefinitions = _moduleDefinitions.GetModuleDefinitions(_alias.SiteId).ToList();
var pageModules = _pageModules.GetPageModules(_alias.SiteId);
foreach (var page in _pages.GetPages(_alias.SiteId))
{
if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.PermissionList) && page.IsNavigation)
if (_userPermissions.IsAuthorized(null, PermissionNames.View, page.PermissionList) && !internalPaths.Contains(page.Path))
{
var rooturl = _alias.Protocol + (string.IsNullOrEmpty(_alias.Path) ? _alias.Name : _alias.Name.Substring(0, _alias.Name.IndexOf("/")));
sitemap.Add(new Sitemap { Url = rooturl + Utilities.NavigateUrl(_alias.Path, page.Path, ""), ModifiedOn = DateTime.UtcNow });
var pageurl = rooturl;
if (string.IsNullOrEmpty(page.Url))
{
pageurl += Utilities.NavigateUrl(_alias.Path, page.Path, "");
}
else
{
pageurl += (page.Url.StartsWith("/") ? "" : "/") + page.Url;
}
sitemap.Add(new Sitemap { Url = pageurl, ModifiedOn = DateTime.UtcNow });
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{

View File

@ -0,0 +1,311 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Services;
using Oqtane.Shared;
namespace Oqtane.Providers
{
public class DatabaseSearchProvider : ISearchProvider
{
private readonly ISearchContentRepository _searchContentRepository;
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(ISearchContentRepository searchContentRepository)
{
_searchContentRepository = searchContentRepository;
}
public void Commit()
{
}
public void DeleteSearchContent(string id)
{
_searchContentRepository.DeleteSearchContent(id);
}
public bool Optimize()
{
return true;
}
public void ResetIndex()
{
_searchContentRepository.DeleteAllSearchContent();
}
public void SaveSearchContent(SearchContent searchContent, bool autoCommit = false)
{
//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
AnalyzeSearchContent(searchContent);
}
public async Task<SearchResults> SearchAsync(SearchQuery searchQuery, Func<SearchContent, SearchQuery, bool> validateFunc)
{
var totalResults = 0;
var searchContentList = await _searchContentRepository.GetSearchContentsAsync(searchQuery);
//convert the search content to search results.
var results = searchContentList
.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.ContentModifiedOn);
break;
case SearchSortFields.Title:
results = results.OrderByDescending(i => i.Title).ThenByDescending(i => i.ContentModifiedOn);
break;
default:
results = results.OrderByDescending(i => i.ContentModifiedOn);
break;
}
}
else
{
switch (searchQuery.SortField)
{
case SearchSortFields.Relevance:
results = results.OrderBy(i => i.Score).ThenByDescending(i => i.ContentModifiedOn);
break;
case SearchSortFields.Title:
results = results.OrderBy(i => i.Title).ThenByDescending(i => i.ContentModifiedOn);
break;
default:
results = results.OrderBy(i => i.ContentModifiedOn);
break;
}
}
//remove duplicated results based on page id for Page and Module types
results = results.DistinctBy(i =>
{
if (i.EntityName == EntityNames.Page || i.EntityName == EntityNames.Module)
{
var pageId = i.SearchContentProperties.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(SearchContent searchContent, SearchQuery searchQuery)
{
var searchResult = new SearchResult()
{
SearchContentId = searchContent.SearchContentId,
SiteId = searchContent.SiteId,
EntityName = searchContent.EntityName,
EntityId = searchContent.EntityId,
Title = searchContent.Title,
Description = searchContent.Description,
Body = searchContent.Body,
Url = searchContent.Url,
Permissions = searchContent.Permissions,
ContentModifiedBy = searchContent.ContentModifiedBy,
ContentModifiedOn = searchContent.ContentModifiedOn,
SearchContentProperties = searchContent.SearchContentProperties,
Snippet = BuildSnippet(searchContent, searchQuery),
Score = CalculateScore(searchContent, searchQuery)
};
return searchResult;
}
private float CalculateScore(SearchContent searchContent, SearchQuery searchQuery)
{
var score = 0f;
foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords))
{
score += searchContent.SearchContentWords.Where(i => i.SearchWord.Word.StartsWith(keyword)).Sum(i => i.Count);
}
return score / 100;
}
private string BuildSnippet(SearchContent searchContent, SearchQuery searchQuery)
{
var content = $"{searchContent.Title} {searchContent.Description} {searchContent.Body}";
var snippet = string.Empty;
foreach (var keyword in SearchUtils.GetKeywords(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.GetKeywords(searchQuery.Keywords))
{
snippet = Regex.Replace(snippet, $"({keyword})", $"<b>$1</b>", RegexOptions.IgnoreCase);
}
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 existingSearchContentWords = _searchContentRepository.GetSearchContentWords(searchContent.SearchContentId);
foreach (var kvp in words)
{
var searchContentWord = existingSearchContentWords.FirstOrDefault(i => i.SearchWord.Word == kvp.Key);
if (searchContentWord != null)
{
searchContentWord.Count = kvp.Value;
searchContentWord.ModifiedOn = DateTime.UtcNow;
_searchContentRepository.UpdateSearchContentWord(searchContentWord);
}
else
{
var searchWord = _searchContentRepository.GetSearchWord(kvp.Key);
if (searchWord == null)
{
searchWord = _searchContentRepository.AddSearchWord(new SearchWord { Word = kvp.Key, CreatedOn = DateTime.UtcNow });
}
searchContentWord = new SearchContentWord
{
SearchContentId = searchContent.SearchContentId,
SearchWordId = searchWord.SearchWordId,
Count = kvp.Value,
CreatedOn = DateTime.UtcNow,
ModifiedOn = DateTime.UtcNow
};
_searchContentRepository.AddSearchContentWord(searchContentWord);
}
}
}
private static Dictionary<string, int> GetWords(string content, int minLength)
{
content = FormatText(content);
var words = new Dictionary<string, int>();
var ignoreWords = IgnoreWords.Split(',');
if (!string.IsNullOrEmpty(content))
{
foreach (var word in content.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;
}
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);
}
}
}

View File

@ -29,5 +29,9 @@ namespace Oqtane.Repository
public virtual DbSet<Language> Language { get; set; }
public virtual DbSet<Visitor> Visitor { get; set; }
public virtual DbSet<UrlMapping> UrlMapping { get; set; }
public virtual DbSet<SearchContent> SearchContent { get; set; }
public virtual DbSet<SearchContentProperty> SearchContentProperty { get; set; }
public virtual DbSet<SearchContentWord> SearchContentWord { get; set; }
public virtual DbSet<SearchWord> SearchWord { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface ISearchContentRepository
{
Task<IEnumerable<SearchContent>> GetSearchContentsAsync(SearchQuery searchQuery);
SearchContent AddSearchContent(SearchContent searchContent);
void DeleteSearchContent(int searchContentId);
void DeleteSearchContent(string entityName, string entryId);
void DeleteSearchContent(string uniqueKey);
void DeleteAllSearchContent();
SearchWord GetSearchWord(string word);
SearchWord AddSearchWord(SearchWord searchWord);
IEnumerable<SearchContentWord> GetSearchContentWords(int searchContentId);
SearchContentWord AddSearchContentWord(SearchContentWord searchContentWord);
SearchContentWord UpdateSearchContentWord(SearchContentWord searchContentWord);
}
}

View File

@ -7,6 +7,7 @@ namespace Oqtane.Repository
{
IEnumerable<UserRole> GetUserRoles(int siteId);
IEnumerable<UserRole> GetUserRoles(int userId, int siteId);
IEnumerable<UserRole> GetUserRoles(string roleName, int siteId);
UserRole AddUserRole(UserRole userRole);
UserRole UpdateUserRole(UserRole userRole);
UserRole GetUserRole(int userRoleId);

View File

@ -59,14 +59,14 @@ namespace Oqtane.Repository
// delete logs in batches of 100 records
var count = 0;
var purgedate = DateTime.UtcNow.AddDays(-age);
var logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate)
var logs = db.Log.Where(item => item.SiteId == siteId && item.LogDate < purgedate)
.OrderBy(item => item.LogDate).Take(100).ToList();
while (logs.Count > 0)
{
count += logs.Count;
db.Log.RemoveRange(logs);
db.SaveChanges();
logs = db.Log.Where(item => item.SiteId == siteId && item.Level != "Error" && item.LogDate < purgedate)
logs = db.Log.Where(item => item.SiteId == siteId && item.LogDate < purgedate)
.OrderBy(item => item.LogDate).Take(100).ToList();
}
return count;

View File

@ -287,7 +287,9 @@ namespace Oqtane.Repository
{
ModuleDefinition moduledefinition;
Type[] moduletypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModule))).ToArray();
Type[] modulecontroltypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IModuleControl))).ToArray();
foreach (Type modulecontroltype in modulecontroltypes)
{
// Check if type should be ignored
@ -299,12 +301,9 @@ namespace Oqtane.Repository
int index = moduledefinitions.FindIndex(item => item.ModuleDefinitionName == qualifiedModuleType);
if (index == -1)
{
// determine if this module implements IModule
Type moduletype = assembly
.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace == modulecontroltype.Namespace || item.Namespace.StartsWith(modulecontroltype.Namespace + "."))
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(IModule)));
// determine if this component is part of a module which implements IModule
Type moduletype = moduletypes.FirstOrDefault(item => item.Namespace == modulecontroltype.Namespace);
if (moduletype != null)
{
// get property values from IModule
@ -399,6 +398,22 @@ namespace Oqtane.Repository
moduledefinitions[index] = moduledefinition;
}
// process modules without UI components
foreach (var moduletype in moduletypes.Where(m1 => !modulecontroltypes.Any(m2 => m1.Namespace == m2.Namespace)))
{
// get property values from IModule
var moduleobject = Activator.CreateInstance(moduletype) as IModule;
moduledefinition = moduleobject.ModuleDefinition;
moduledefinition.ModuleDefinitionName = moduletype.Namespace + ", " + moduletype.Assembly.GetName().Name;
moduledefinition.AssemblyName = assembly.GetName().Name;
moduledefinition.Categories = "Headless";
moduledefinition.PermissionList = new List<Permission>
{
new Permission(PermissionNames.Utilize, RoleNames.Host, true)
};
moduledefinitions.Add(moduledefinition);
}
return moduledefinitions;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,169 @@
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<TenantDBContext> _dbContextFactory;
public SearchContentRepository(IDbContextFactory<TenantDBContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public async Task<IEnumerable<SearchContent>> GetSearchContentsAsync(SearchQuery searchQuery)
{
using var db = _dbContextFactory.CreateDbContext();
var searchContents = db.SearchContent.AsNoTracking()
.Include(i => i.SearchContentProperties)
.Include(i => i.SearchContentWords)
.ThenInclude(w => w.SearchWord)
.Where(i => i.SiteId == searchQuery.SiteId);
if (searchQuery.EntityNames != null && searchQuery.EntityNames.Any())
{
searchContents = searchContents.Where(i => searchQuery.EntityNames.Contains(i.EntityName));
}
if (searchQuery.From != DateTime.MinValue)
{
searchContents = searchContents.Where(i => i.ContentModifiedOn >= searchQuery.From);
}
if (searchQuery.To != DateTime.MinValue)
{
searchContents = searchContents.Where(i => i.ContentModifiedOn <= searchQuery.To);
}
if (searchQuery.Properties != null && searchQuery.Properties.Any())
{
foreach (var property in searchQuery.Properties)
{
searchContents = searchContents.Where(i => i.SearchContentProperties.Any(p => p.Name == property.Key && p.Value == property.Value));
}
}
var filteredContentList = new List<SearchContent>();
if (!string.IsNullOrEmpty(searchQuery.Keywords))
{
foreach (var keyword in SearchUtils.GetKeywords(searchQuery.Keywords))
{
filteredContentList.AddRange(await searchContents.Where(i => i.SearchContentWords.Any(w => w.SearchWord.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.SearchContentProperties != null && searchContent.SearchContentProperties.Any())
{
foreach(var property in searchContent.SearchContentProperties)
{
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, string 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 SearchWord GetSearchWord(string word)
{
if(string.IsNullOrEmpty(word))
{
return null;
}
using var db = _dbContextFactory.CreateDbContext();
return db.SearchWord.FirstOrDefault(i => i.Word == word);
}
public SearchWord AddSearchWord(SearchWord searchWord)
{
using var db = _dbContextFactory.CreateDbContext();
db.SearchWord.Add(searchWord);
db.SaveChanges();
return searchWord;
}
public IEnumerable<SearchContentWord> GetSearchContentWords(int searchContentId)
{
using var db = _dbContextFactory.CreateDbContext();
return db.SearchContentWord
.Include(i => i.SearchWord)
.Where(i => i.SearchContentId == searchContentId).ToList();
}
public SearchContentWord AddSearchContentWord(SearchContentWord searchContentWord)
{
using var db = _dbContextFactory.CreateDbContext();
db.SearchContentWord.Add(searchContentWord);
db.SaveChanges();
return searchContentWord;
}
public SearchContentWord UpdateSearchContentWord(SearchContentWord searchContentWord)
{
using var db = _dbContextFactory.CreateDbContext();
db.Entry(searchContentWord).State = EntityState.Modified;
db.SaveChanges();
return searchContentWord;
}
}
}

View File

@ -165,22 +165,23 @@ namespace Oqtane.Repository
if (!serverstate.IsInitialized)
{
var site = GetSite(alias.SiteId);
// initialize theme Assemblies
site.Themes = _themeRepository.GetThemes().ToList();
// initialize module Assemblies
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// execute migrations
var version = ProcessSiteMigrations(alias, site);
version = ProcessPageTemplates(alias, site, moduleDefinitions, version);
if (site.Version != version)
if (site != null)
{
site.Version = version;
UpdateSite(site);
}
// initialize theme Assemblies
site.Themes = _themeRepository.GetThemes().ToList();
// initialize module Assemblies
var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId);
// execute migrations
var version = ProcessSiteMigrations(alias, site);
version = ProcessPageTemplates(alias, site, moduleDefinitions, version);
if (site.Version != version)
{
site.Version = version;
UpdateSite(site);
}
}
serverstate.IsInitialized = true;
}
}
@ -328,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<PageTemplate> adminPageTemplates = ((ISiteTemplate)siteTemplateObject).CreateSite(site);
CreatePages(site, adminPageTemplates, null);
// process site template
if (string.IsNullOrEmpty(site.SiteTemplateType))
{
var section = _config.GetSection("Installation:SiteTemplate");
@ -348,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<PageTemplate> 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<PageTemplate> pageTemplates, Alias alias)
@ -411,7 +415,7 @@ namespace Oqtane.Repository
}
else
{
parent = pages.FirstOrDefault(item => item.Path.ToLower() == pageTemplate.Parent.ToLower());
parent = pages.FirstOrDefault(item => item.Path.ToLower() == ((pageTemplate.Parent == "/") ? "" : pageTemplate.Parent.ToLower()));
}
page.ParentId = (parent != null) ? parent.PageId : null;
page.Path = page.Path.ToLower();
@ -487,7 +491,11 @@ namespace Oqtane.Repository
pageModule.Order = (pageTemplateModule.Order == 0) ? 1 : pageTemplateModule.Order;
pageModule.ContainerType = pageTemplateModule.ContainerType;
pageModule.IsDeleted = pageTemplateModule.IsDeleted;
pageModule.Module.PermissionList = pageTemplateModule.PermissionList;
pageModule.Module.PermissionList = new List<Permission>();
foreach (var permission in pageTemplateModule.PermissionList)
{
pageModule.Module.PermissionList.Add(permission.Clone(permission));
}
pageModule.Module.AllPages = false;
pageModule.Module.IsDeleted = false;
try
@ -539,8 +547,11 @@ namespace Oqtane.Repository
try
{
var module = _moduleRepository.GetModule(pageModule.ModuleId);
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version);
if (module != null)
{
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
((IPortable)moduleobject).ImportModule(module, pageTemplateModule.Content, moduleDefinition.Version);
}
}
catch (Exception ex)
{
@ -564,698 +575,5 @@ namespace Oqtane.Repository
}
}
}
private List<PageTemplate> CreateAdminPages(List<PageTemplate> pageTemplates = null)
{
if (pageTemplates == null) pageTemplates = new List<PageTemplate>();
// user pages
pageTemplates.Add(new PageTemplate
{
Name = "Login",
Parent = "",
Path = "login",
Icon = Icons.LockLocked,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "User Login", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Register.Index).ToModuleDefinitionName(), Title = "User Registration", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Reset.Index).ToModuleDefinitionName(), Title = "Password Reset", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UserProfile.Index).ToModuleDefinitionName(), Title = "User Profile", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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 = "Not Found",
Parent = "",
Path = "404",
Icon = Icons.X,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Not Found", Pane = PaneNames.Default,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = "<p>The page you requested does not exist or you do not have sufficient rights to view it.</p>"
}
}
});
// admin pages
pageTemplates.Add(new PageTemplate
{
Name = "Admin",
Parent = "",
Path = "admin",
Icon = "",
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Dashboard.Index).ToModuleDefinitionName(), Title = "Admin Dashboard", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Site.Index).ToModuleDefinitionName(), Title = "Site Settings", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Pages.Index).ToModuleDefinitionName(), Title = "Page Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Users.Index).ToModuleDefinitionName(), Title = "User Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Profiles.Index).ToModuleDefinitionName(), Title = "Profile Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Roles.Index).ToModuleDefinitionName(), Title = "Role Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Files.Index).ToModuleDefinitionName(), Title = "File Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.RecycleBin.Index).ToModuleDefinitionName(), Title = "Recycle Bin", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Logs.Index).ToModuleDefinitionName(), Title = "Event Log", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sites.Index).ToModuleDefinitionName(), Title = "Site Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.ModuleDefinitions.Index).ToModuleDefinitionName(), Title = "Module Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Themes.Index).ToModuleDefinitionName(), Title = "Theme Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
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<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Jobs.Index).ToModuleDefinitionName(), Title = "Scheduled Jobs", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Sql.Index).ToModuleDefinitionName(), Title = "Sql Management", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.SystemInfo.Index).ToModuleDefinitionName(), Title = "System Info", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
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<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Upgrade.Index).ToModuleDefinitionName(), Title = "System Update", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
},
Content = ""
}
}
});
return pageTemplates;
}
}
}

View File

@ -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<SiteTemplate> LoadSiteTemplatesFromAssembly(List<SiteTemplate> 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;
}

View File

@ -13,6 +13,7 @@ using Oqtane.Shared;
using Oqtane.Themes;
using System.Reflection.Metadata;
using Oqtane.Migrations.Master;
using Oqtane.Modules;
namespace Oqtane.Repository
{
@ -224,9 +225,11 @@ namespace Oqtane.Repository
private List<Theme> LoadThemesFromAssembly(List<Theme> themes, Assembly assembly)
{
Theme theme;
List<Type> themeTypes = new List<Type>();
Type[] themeTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(ITheme))).ToArray();
Type[] themeControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IThemeControl))).ToArray();
Type[] containerControlTypes = assembly.GetTypes().Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type themeControlType in themeControlTypes)
{
// Check if type should be ignored
@ -240,16 +243,9 @@ namespace Oqtane.Repository
int index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType);
if (index == -1)
{
// Find all types in the assembly with the same namespace root
themeTypes = assembly.GetTypes()
.Where(item => !item.IsOqtaneIgnore())
.Where(item => item.Namespace != null)
.Where(item => item.Namespace == themeControlType.Namespace || item.Namespace.StartsWith(themeControlType.Namespace + "."))
.ToList();
// determine if this component is part of a theme which implements ITheme
Type themetype = themeTypes.FirstOrDefault(item => item.Namespace == themeControlType.Namespace);
// determine if this theme implements ITheme
Type themetype = themeTypes
.FirstOrDefault(item => item.GetInterfaces().Contains(typeof(ITheme)));
if (themetype != null)
{
var themeobject = Activator.CreateInstance(themetype) as ITheme;
@ -285,6 +281,7 @@ namespace Oqtane.Repository
}
theme = themes[index];
// add theme control
var themecontrolobject = Activator.CreateInstance(themeControlType) as IThemeControl;
theme.Themes.Add(
new ThemeControl
@ -296,14 +293,12 @@ namespace Oqtane.Repository
}
);
// containers
Type[] containertypes = themeTypes
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes)
if (!theme.Containers.Any())
{
var containerobject = Activator.CreateInstance(containertype) as IThemeControl;
if (theme.Containers.FirstOrDefault(item => item.TypeName == containertype.FullName + ", " + themeControlType.Assembly.GetName().Name) == null)
// add container controls
foreach (Type containertype in containerControlTypes.Where(item => item.Namespace == themeControlType.Namespace))
{
var containerobject = Activator.CreateInstance(containertype) as IThemeControl;
theme.Containers.Add(
new ThemeControl
{

View File

@ -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<UserRole> 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<UserRole> 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)

View File

@ -0,0 +1,233 @@
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.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<SearchService> _logger;
private readonly IMemoryCache _cache;
public SearchService(
IServiceProvider serviceProvider,
ITenantManager tenantManager,
IAliasRepository aliasRepository,
ISettingRepository settingRepository,
IPermissionRepository permissionRepository,
ILogger<SearchService> logger,
IMemoryCache cache)
{
_tenantManager = tenantManager;
_aliasRepository = aliasRepository;
_settingRepository = settingRepository;
_permissionRepository = permissionRepository;
_serviceProvider = serviceProvider;
_logger = logger;
_cache = cache;
}
public void IndexContent(int siteId, DateTime? startTime, Action<string> logNote, Action<string> handleError)
{
var searchEnabled = SearchEnabled(siteId);
if(!searchEnabled)
{
logNote($"Search: Search is disabled on site {siteId}.<br />");
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.<br />");
}
else
{
_logger.LogDebug($"Search: Begin Index {searchIndexManager.Name}");
var count = searchIndexManager.IndexContent(siteId, startTime, SaveSearchContent, handleError);
logNote($"Search: Indexer {searchIndexManager.Name} processed {count} search content.<br />");
_logger.LogDebug($"Search: End Index {searchIndexManager.Name}");
}
}
}
public async Task<SearchResults> SearchAsync(SearchQuery searchQuery)
{
var searchProvider = GetSearchProvider(searchQuery.SiteId);
var searchResults = await searchProvider.SearchAsync(searchQuery, Visible);
//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<ISearchProvider>();
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, SearchProviderSettingName);
if(!string.IsNullOrEmpty(setting?.SettingValue))
{
return setting.SettingValue;
}
return Constants.DefaultSearchProviderName;
}
private bool SearchEnabled(int siteId)
{
var setting = _settingRepository.GetSetting(EntityNames.Site, siteId, 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 List<ISearchIndexManager> GetSearchIndexManagers(Action<ISearchIndexManager> initManager)
{
var managers = new List<ISearchIndexManager>();
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 List<ISearchResultManager> GetSearchResultManagers()
{
var managers = new List<ISearchResultManager>();
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 SaveSearchContent(List<SearchContent> searchContentList)
{
if(searchContentList.Any())
{
var searchProvider = GetSearchProvider(searchContentList.First().SiteId);
foreach (var searchContent in searchContentList)
{
try
{
searchProvider.SaveSearchContent(searchContent);
}
catch(Exception ex)
{
_logger.LogError(ex, $"Search: Save search content {searchContent.UniqueKey} failed.");
}
}
//commit the index changes
searchProvider.Commit();
}
}
private bool Visible(SearchContent searchContent, SearchQuery searchQuery)
{
var visible = true;
foreach (var permission in searchContent.Permissions.Split(','))
{
var entityName = permission.Split(":")[0];
var entityId = int.Parse(permission.Split(":")[1]);
if (!HasViewPermission(searchQuery.SiteId, searchQuery.User, entityName, entityId))
{
visible = false;
break;
}
}
return visible;
}
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.EntityName);
if(searchResultManager != null)
{
return searchResultManager.GetUrl(result, searchQuery);
}
return string.Empty;
}
}
}

View File

@ -62,35 +62,24 @@ namespace Oqtane.Services
public async Task<Site> GetSiteAsync(int siteId)
{
if (!_accessor.HttpContext.User.Identity.IsAuthenticated)
var alias = _tenantManager.GetAlias();
var site = await _cache.GetOrCreateAsync($"site:{alias.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<Page>();
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);
}
}
site.Pages = pages;
return site;
}
private async Task<Site> GetSite(int siteid)
@ -115,62 +104,17 @@ namespace Oqtane.Services
site.Pages = new List<Page>();
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<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(site.SiteId).ToList();
settings = _settings.GetSettings(EntityNames.Module).ToList();
site.Modules = new List<Module>();
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 +135,46 @@ namespace Oqtane.Services
return site;
}
private static List<Page> GetPagesHierarchy(List<Page> pages)
{
List<Page> hierarchy = new List<Page>();
Action<List<Page>, Page> getPath = null;
getPath = (pageList, page) =>
{
IEnumerable<Page> 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<Site> AddSiteAsync(Site site)
{
if (_accessor.HttpContext.User.IsInRole(RoleNames.Host))
@ -256,44 +240,81 @@ namespace Oqtane.Services
}
}
private static List<Page> GetPagesHierarchy(List<Page> pages)
public async Task<List<Module>> GetModulesAsync(int siteId, int pageId)
{
List<Page> hierarchy = new List<Page>();
Action<List<Page>, Page> getPath = null;
getPath = (pageList, page) =>
var alias = _tenantManager.GetAlias();
var sitemodules = await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry =>
{
IEnumerable<Page> 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<Module>();
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<List<Module>> GetModulesAsync(int siteId)
{
var alias = _tenantManager.GetAlias();
return await _cache.GetOrCreateAsync($"modules:{alias.SiteKey}", async entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return await GetModules(siteId);
});
}
private async Task<List<Module>> GetModules(int siteId)
{
await Task.Yield(); // force method to async
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(siteId).ToList();
var settings = _settings.GetSettings(EntityNames.Module).ToList();
var modules = new List<Module>();
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)]

View File

@ -22,6 +22,7 @@ using Oqtane.UI;
using OqtaneSSR.Extensions;
using Microsoft.AspNetCore.Components.Authorization;
using Oqtane.Providers;
using Microsoft.AspNetCore.Cors.Infrastructure;
namespace Oqtane
{
@ -135,7 +136,7 @@ namespace Oqtane
{
// allow .NET MAUI client cross origin calls
policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0")
.AllowAnyHeader().AllowCredentials();
.AllowAnyHeader().AllowAnyMethod().AllowCredentials();
});
});
@ -169,7 +170,7 @@ namespace Oqtane
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ILogger<Startup> logger)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger<Startup> logger)
{
if (!string.IsNullOrEmpty(_configureServicesErrors))
{
@ -198,7 +199,16 @@ namespace Oqtane
app.UseOqtaneLocalization();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
OnPrepareResponse = (ctx) =>
{
var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy)
.ConfigureAwait(false).GetAwaiter().GetResult();
corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response);
}
});
app.UseExceptionMiddleWare();
app.UseTenantResolution();
app.UseJwtAuthorization();
@ -206,6 +216,7 @@ namespace Oqtane
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
if (_useSwagger)
{

View File

@ -0,0 +1,3 @@
.search-result-container ul.pagination li label, .search-result-container ul.dropdown-menu li label {
cursor: pointer;
}

View File

@ -13,9 +13,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup>
<ItemGroup>

View File

@ -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 List<SearchContent> GetSearchContents(Oqtane.Models.Module module, DateTime startTime)
{
var searchContentList = new List<SearchContent>();
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;
}
}
}

View File

@ -19,10 +19,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,6 @@
{
"Title": "Default Module Template",
"Type": "External",
"Version": "5.1.0",
"Version": "5.2.0",
"Namespace": "[Owner].Module.[Module]"
}

View File

@ -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) {

View File

@ -79,6 +79,10 @@ body {
top: -2px;
}
.app-search input{
width: auto;
}
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
margin: .5rem;
@ -126,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;
}
}

View File

@ -108,12 +108,12 @@
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
if (_login != "-")
{
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login, true);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Login", _login);
}
if (_register != "-")
{
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register, true);
settings = SettingService.SetSetting(settings, GetType().Namespace + ":Register", _register);
}
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
}

View File

@ -9,12 +9,13 @@
<Product>[Owner].Theme.[Theme]</Product>
<Copyright>[Owner]</Copyright>
<AssemblyName>[Owner].Theme.[Theme].Client.Oqtane</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
</ItemGroup>
<ItemGroup>

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<AccelerateBuildsInVisualStudio>false</AccelerateBuildsInVisualStudio>
</PropertyGroup>
<ItemGroup>

View File

@ -1,6 +1,6 @@
{
"Title": "Default Theme Template",
"Type": "External",
"Version": "5.1.0",
"Version": "5.2.0",
"Namespace": "[Owner].Theme.[Theme]"
}

View File

@ -35,6 +35,9 @@ app {
}
/* Action Dialog */
.app-actiondialog{
position: absolute;
}
.app-actiondialog .modal {
position: fixed; /* Stay in place */
z-index: 9999; /* Sit on top */
@ -232,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;
}

View File

@ -35,13 +35,15 @@ Oqtane.RichTextEditor = {
enableQuillEditor: function (editorElement, mode) {
editorElement.__quill.enable(mode);
},
insertQuillImage: function (quillElement, imageURL, altText) {
var Delta = Quill.import('delta');
editorIndex = 0;
getCurrentCursor: function (quillElement) {
var editorIndex = 0;
if (quillElement.__quill.getSelection() !== null) {
editorIndex = quillElement.__quill.getSelection().index;
}
return editorIndex;
},
insertQuillImage: function (quillElement, imageURL, altText, editorIndex) {
var Delta = Quill.import('delta');
return quillElement.__quill.updateContents(
new Delta()