@namespace Oqtane.Components @using System.Net @using System.Security.Claims @using Microsoft.AspNetCore.Http @using Microsoft.AspNetCore.Http.Extensions @using Microsoft.AspNetCore.Antiforgery @using Microsoft.AspNetCore.Localization @using Microsoft.Net.Http.Headers @using Microsoft.Extensions.Primitives @using Microsoft.AspNetCore.Authentication @using Oqtane.Client @using Oqtane.UI @using Oqtane.Repository @using Oqtane.Infrastructure @using Oqtane.Security @using Oqtane.Models @using Oqtane.Shared @using Oqtane.Themes @using Oqtane.Extensions @using System.Globalization @inject NavigationManager NavigationManager @inject IAntiforgery Antiforgery @inject IConfigManager ConfigManager @inject ITenantManager TenantManager @inject ISiteService SiteService @inject IPageRepository PageRepository @inject IThemeRepository ThemeRepository @inject ILanguageRepository LanguageRepository @inject ILocalizationManager LocalizationManager @inject IAliasRepository AliasRepository @inject IUrlMappingRepository UrlMappingRepository @inject IVisitorRepository VisitorRepository @inject IJwtManager JwtManager @inject ICookieConsentService CookieConsentService @inject ISettingService SettingService @if (_initialized) { @if (_scripts.Contains("PWA Manifest")) { } @((MarkupString)_styleSheets) @if (_renderMode == RenderModes.Static) { } else { } @((MarkupString)_headResources) @if (string.IsNullOrEmpty(_message)) { @if (_renderMode == RenderModes.Static) { } else { } @((MarkupString)_scripts) @((MarkupString)_bodyResources) @if (_renderMode == RenderModes.Static) { } } else {
@_message
} } @code { private bool _initialized = false; private string _renderMode = RenderModes.Interactive; private string _runtime = Runtimes.Server; private bool _prerender = true; private string _fingerprint = ""; private int _visitorId = -1; private string _antiForgeryToken = ""; private string _remoteIPAddress = ""; private string _platform = ""; private string _authorizationToken = ""; private string _language = "en"; private string _headResources = ""; private string _bodyResources = ""; private string _styleSheets = ""; private string _scripts = ""; private string _message = ""; private bool _allowCookies; private PageState _pageState; // CascadingParameter is required to access HttpContext [CascadingParameter] HttpContext Context { get; set; } protected override async Task OnInitializedAsync() { _antiForgeryToken = Antiforgery.GetAndStoreTokens(Context).RequestToken; _remoteIPAddress = Context.Connection.RemoteIpAddress?.ToString() ?? ""; _platform = System.Runtime.InteropServices.RuntimeInformation.OSDescription; // if framework is installed if (ConfigManager.IsInstalled()) { var alias = TenantManager.GetAlias(); if (alias != null) { var url = WebUtility.UrlDecode(Context.Request.GetEncodedUrl()); if (!alias.IsDefault) { HandleDefaultAliasRedirect(alias, url); } var site = await SiteService.GetSiteAsync(alias.SiteId); if (site != null && (!site.IsDeleted || url.Contains("admin/site")) && site.RenderMode != RenderModes.Headless) { _renderMode = site.RenderMode; _runtime = site.Runtime; _prerender = site.Prerender; _fingerprint = site.Fingerprint; var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty); _allowCookies = string.IsNullOrEmpty(cookieConsentSettings) || await CookieConsentService.CanTrackAsync(cookieConsentSettings == "optout"); var modules = new List(); Route route = new Route(url, alias.Path); var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase)); if (page == null && route.PagePath == "") // naked path refers to site home page { if (site.HomePageId != null) { page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId); } if (page == null) { // fallback to use the first page in the collection page = site.Pages.FirstOrDefault(); } } if (page == null) { // personalized pages need to be retrieved using path page = PageRepository.GetPage(route.PagePath, site.SiteId); } if (page == null || page.IsDeleted) { HandlePageNotFound(site, page, route); } else { modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId); } if (site.VisitorTracking && _allowCookies) { TrackVisitor(site.SiteId); } // get jwt token for downstream APIs if (Context.User.Identity.IsAuthenticated) { await GetJwtToken(alias); } // includes resources var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action); ManageStyleSheets(resources); ManageScripts(resources, alias); // generate scripts if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null) { _scripts += CreatePWAScript(alias, site, route); } @if (_renderMode == RenderModes.Static) { _scripts += CreateScrollPositionScript(); } // set culture if not specified string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName]; if (cultureCookie == null) { // get default language for site if (site.Languages.Any()) { // use default language if specified otherwise use first language in collection cultureCookie = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code; } else { // fallback language cultureCookie = LocalizationManager.GetDefaultCulture(); } // convert language code to culture cookie format (ie. "c=en|uic=en") cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(cultureCookie)); SetLocalizationCookie(cultureCookie); } // set language for page if (!string.IsNullOrEmpty(cultureCookie)) { _language = Shared.CookieRequestCultureProvider.ParseCookieValue(cultureCookie).Culture.Name; } // create initial PageState _pageState = new PageState { Alias = alias, Site = site, Page = page, Modules = modules, User = null, Uri = new Uri(url, UriKind.Absolute), Route = route, QueryString = Utilities.ParseQueryString(route.Query), UrlParameters = route.UrlParameters, ModuleId = -1, Action = "", EditMode = false, LastSyncDate = DateTime.MinValue, RenderMode = _renderMode, Runtime = (Shared.Runtime)Enum.Parse(typeof(Shared.Runtime), _runtime), VisitorId = _visitorId, RemoteIPAddress = _remoteIPAddress, ReturnUrl = "", IsInternalNavigation = false, RenderId = Guid.NewGuid(), Refresh = true, AllowCookies = _allowCookies }; } else { _message = "Site Is Disabled"; } } else { _message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name"; } } _initialized = true; } private void HandleDefaultAliasRedirect(Alias alias, string url) { // get aliases for site and tenant var aliases = AliasRepository.GetAliases().Where(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId); // get first default alias var defaultAlias = aliases.Where(item => item.IsDefault).FirstOrDefault(); if (defaultAlias != null) { // redirect to default alias NavigationManager.NavigateTo(url.Replace(alias.Name, defaultAlias.Name), true); } else // no default alias specified - use first alias { defaultAlias = aliases.FirstOrDefault(); if (defaultAlias != null && alias.Name.Trim() != defaultAlias.Name.Trim()) { // redirect to first alias NavigationManager.NavigateTo(url.Replace(alias.Name, defaultAlias.Name), true); } } } private void HandlePageNotFound(Site site, Page page, Route route) { // page not found - look for url mapping var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath); if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) { // redirect to mapped url var url = (urlMapping.MappedUrl.StartsWith("http")) ? urlMapping.MappedUrl : route.SiteUrl + "/" + urlMapping.MappedUrl; NavigationManager.NavigateTo(url, true); } else // no url mapping exists { if (route.PagePath != "404") { // redirect to 404 page NavigationManager.NavigateTo(route.SiteUrl + "/404", true); } } } private void TrackVisitor(int SiteId) { try { // get request attributes string useragent = (Context.Request.Headers[HeaderNames.UserAgent] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.UserAgent] : "(none)"; useragent = (useragent.Length > 256) ? useragent.Substring(0, 256) : useragent; string language = (Context.Request.Headers[HeaderNames.AcceptLanguage] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.AcceptLanguage] : ""; language = (language.Contains(",")) ? language.Substring(0, language.IndexOf(",")) : language; language = (language.Contains(";")) ? language.Substring(0, language.IndexOf(";")) : language; language = (language.Trim().Length == 0) ? "??" : language; // filter var settings = Context.GetSiteSettings(); var filter = settings.GetValue("VisitorFilter", Constants.DefaultVisitorFilter); foreach (string term in filter.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sValue => sValue.Trim()).ToArray()) { if (_remoteIPAddress.ToLower().Contains(term) || useragent.ToLower().Contains(term) || language.ToLower().Contains(term)) { return; } } // get other request attributes string url = Context.Request.GetEncodedUrl(); string referrer = (Context.Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.Referer] : ""; int? userid = Context.User.UserId(); userid = (userid == -1) ? null : userid; // 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; if (_visitorId <= 0) { // if enabled use IP Address correlation var correlate = bool.Parse(settings.GetValue("VisitorCorrelation", "true")); if (correlate) { visitor = VisitorRepository.GetVisitor(SiteId, _remoteIPAddress); if (visitor != null) { _visitorId = visitor.VisitorId; setcookie = true; } } } if (_visitorId <= 0) { // create new visitor visitor = new Visitor(); visitor.SiteId = SiteId; visitor.IPAddress = _remoteIPAddress; visitor.UserAgent = useragent; visitor.Language = language; visitor.Url = url; visitor.Referrer = referrer; visitor.UserId = userid; visitor.Visits = 1; visitor.CreatedOn = DateTime.UtcNow; visitor.VisitedOn = DateTime.UtcNow; visitor = VisitorRepository.AddVisitor(visitor); _visitorId = visitor.VisitorId; setcookie = true; } else { // check expiry if (DateTime.UtcNow > expiry) { if (visitor == null) { // get visitor if 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)) { 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); } } } // set cookie if (setcookie) { expiry = DateTime.UtcNow.AddMinutes(int.Parse(settings.GetValue("VisitorDuration", "5"))); Context.Response.Cookies.Append( visitorCookieName, $"{_visitorId}|{expiry.ToString("M/d/yyyy hh:mm:ss tt", CultureInfo.InvariantCulture)}", new CookieOptions() { Expires = DateTimeOffset.UtcNow.AddYears(10), IsEssential = true, SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute Secure = true, // Ensure the cookie is only sent over HTTPS HttpOnly = true // Optional: Helps mitigate XSS attacks } ); } } catch { // error tracking visitor } } private async Task GetJwtToken(Alias alias) { // bearer token may have been provided by remote Identity Provider and persisted using SaveTokens = true _authorizationToken = await Context.GetTokenAsync("access_token"); if (string.IsNullOrEmpty(_authorizationToken)) { // generate bearer token if a secret has been configured in User Settings var sitesettings = Context.GetSiteSettings(); var secret = sitesettings.GetValue("JwtOptions:Secret", ""); if (!string.IsNullOrEmpty(secret)) { _authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20"))); } } } private string CreatePWAScript(Alias alias, Site site, Route route) { return Environment.NewLine + "" + Environment.NewLine + "" + Environment.NewLine; } private string CreateScrollPositionScript() { return Environment.NewLine + "" + Environment.NewLine; } private void AddScript(Resource resource, Alias alias) { var script = CreateScript(resource, alias); if (resource.Location == Shared.ResourceLocation.Head && resource.LoadBehavior != ResourceLoadBehavior.BlazorPageScript) { if (!_headResources.Contains(script)) { _headResources += script + Environment.NewLine; } } else { if (!_bodyResources.Contains(script)) { _bodyResources += script + Environment.NewLine; } } } private string CreateScript(Resource resource, Alias alias) { if (resource.LoadBehavior == ResourceLoadBehavior.BlazorPageScript) { return ""; } else { var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url; var dataAttributes = ""; if (!resource.DataAttributes.ContainsKey("data-reload")) { switch (resource.LoadBehavior) { case ResourceLoadBehavior.Once: dataAttributes += " data-reload=\"once\""; break; case ResourceLoadBehavior.Always: dataAttributes += " data-reload=\"always\""; break; } } if (resource.DataAttributes != null && resource.DataAttributes.Count > 0) { foreach (var attribute in resource.DataAttributes) { dataAttributes += " " + attribute.Key + "=\"" + attribute.Value + "\""; } } return ""; } } private void SetLocalizationCookie(string cookieValue) { var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1), SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute Secure = true, // Ensure the cookie is only sent over HTTPS HttpOnly = false // cookie is updated using JS Interop in Interactive render mode }; Context.Response.Cookies.Append( Shared.CookieRequestCultureProvider.DefaultCookieName, cookieValue, cookieOptions ); } private async Task> GetPageResources(Alias alias, Site site, Page page, List modules, int moduleid, string action) { var resources = new List(); var themeType = (string.IsNullOrEmpty(page.ThemeType)) ? site.DefaultThemeType : page.ThemeType; 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), theme.Fingerprint, 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), theme.Fingerprint, site.RenderMode); } var type = Type.GetType(themeType); if (type != null) { var obj = Activator.CreateInstance(type) as IThemeControl; if (obj != null) { resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, theme.Fingerprint, site.RenderMode); } } // theme settings components are dynamically loaded within the framework Page Management module if (page.Path == "admin/pages" && action.ToLower() == "edit" && theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType)) { var settingsType = Type.GetType(theme.ThemeSettingsType); if (settingsType != null) { var objSettings = Activator.CreateInstance(settingsType) as IModuleControl; resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Fingerprint, site.RenderMode); } } 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), module.ModuleDefinition.Fingerprint, site.RenderMode); // handle default action if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction)) { action = module.ModuleDefinition.DefaultAction; } // get typename template typename = module.ModuleDefinition.ControlTypeTemplate; if (module.ModuleDefinition.ControlTypeRoutes != "") { // process custom action routes foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(';', StringSplitOptions.RemoveEmptyEntries)) { if (route.StartsWith(action + "=")) { typename = route.Replace(action + "=", ""); break; } } } } // create typename if (Constants.DefaultModuleActions.Contains(action, StringComparer.OrdinalIgnoreCase)) { typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, action); } else { typename = typename.Replace(Constants.ActionToken, action); } // ensure component exists and implements IModuleControl module.ModuleType = ""; var moduletype = Type.GetType(typename, false, true); // case insensitive if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl))) { module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name } if (moduletype != null && module.ModuleType != "") { var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; if (moduleobject != null) { resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode); // settings components are dynamically loaded within the framework Settings module if (action.ToLower() == "settings" && module.ModuleDefinition != null) { // module settings component var settingsType = ""; if (!string.IsNullOrEmpty(module.ModuleDefinition.SettingsType)) { // module settings type explicitly declared in IModule interface settingsType = module.ModuleDefinition.SettingsType; } else { // legacy support - module settings type determined by convention settingsType = module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action); } moduletype = Type.GetType(settingsType, false, true); if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode); } // container settings component if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType)) { moduletype = Type.GetType(theme.ContainerSettingsType); if (moduletype != null) { moduleobject = Activator.CreateInstance(moduletype) as IModuleControl; resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Fingerprint, site.RenderMode); } } } } } } if (site.RenderMode == RenderModes.Interactive) { // 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()) { 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), module.ModuleDefinition.Fingerprint, site.RenderMode); } } } return resources; } private List AddResources(List pageresources, List resources, ResourceLevel level, Alias alias, string type, string name, string fingerprint, string rendermode) { if (resources != null) { foreach (var resource in resources) { if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site) { 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, fingerprint)); } } } } return pageresources; } private void ManageStyleSheets(List resources) { 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)) { count++; string id = "id=\"app-stylesheet-" + ResourceLevel.Page.ToString().ToLower() + "-" + batch + "-" + count.ToString("00") + "\" "; _styleSheets += "" + Environment.NewLine; // href at end of element due to enhanced navigation patch algorithm } } } private void ManageScripts(List resources, Alias alias) { if (resources != null) { foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Script)) { if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Static) { AddScript(resource, alias); } } } } }