@using System.Diagnostics.CodeAnalysis @using System.Runtime.InteropServices @namespace Oqtane.UI @inject AuthenticationStateProvider AuthenticationStateProvider @inject SiteState SiteState @inject NavigationManager NavigationManager @inject INavigationInterception NavigationInterception @inject IAliasService AliasService @inject ITenantService TenantService @inject ISiteService SiteService @inject IPageService PageService @inject IUserService UserService @inject IModuleService ModuleService @inject ILogService LogService @implements IHandleAfterRender @DynamicComponent @code { private string _absoluteUri; private bool _navigationInterceptionEnabled; private PageState _pagestate; [CascadingParameter] PageState PageState { get; set; } [Parameter] public Action OnStateChange { get; set; } private RenderFragment DynamicComponent { get; set; } protected override void OnInitialized() { _absoluteUri = NavigationManager.Uri; NavigationManager.LocationChanged += LocationChanged; DynamicComponent = builder => { if (PageState != null) { builder.OpenComponent(0, Type.GetType(Constants.PageComponent)); builder.CloseComponent(); } }; } public void Dispose() { NavigationManager.LocationChanged -= LocationChanged; } protected override async Task OnParametersSetAsync() { if (PageState == null) { // misconfigured api calls should not be processed through the router if (!_absoluteUri.Contains("~/api/")) { await Refresh(); } else { System.Diagnostics.Debug.WriteLine(GetType().FullName + ": Error: API call to " + _absoluteUri + " is not mapped to a Controller"); } } } [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] private async Task Refresh() { Alias alias = null; Site site; List pages; Page page; User user = null; List modules; var moduleid = -1; var action = ""; var editmode = false; var reload = Reload.None; var lastsyncdate = DateTime.UtcNow; var runtime = GetRuntime(); Uri uri = new Uri(_absoluteUri); // get path var path = uri.LocalPath.Substring(1); // parse querystring var querystring = ParseQueryString(uri.Query); // the reload parameter is used during user login/logout if (querystring.ContainsKey("reload")) { reload = Reload.Site; } if (PageState != null) { editmode = PageState.EditMode; lastsyncdate = PageState.LastSyncDate; } alias = await AliasService.GetAliasAsync(path, lastsyncdate); SiteState.Alias = alias; // set state for services lastsyncdate = alias.SyncDate; // process any sync events for site or page if (reload != Reload.Site && alias.SyncEvents.Any()) { if (PageState != null && alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Page && item.EntityId == PageState.Page.PageId)) { reload = Reload.Page; } if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.Site && item.EntityId == alias.SiteId)) { reload = Reload.Site; } } if (reload == Reload.Site || PageState == null || alias.SiteId != PageState.Alias.SiteId) { site = await SiteService.GetSiteAsync(alias.SiteId); reload = Reload.Site; } else { site = PageState.Site; } if (site != null) { if (PageState == null || reload == Reload.Site) { // get user var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); if (authState.User.Identity.IsAuthenticated) { user = await UserService.GetUserAsync(authState.User.Identity.Name, site.SiteId); } } else { user = PageState.User; } // process any sync events for user if (reload != Reload.Site && user != null && alias.SyncEvents.Any()) { if (alias.SyncEvents.Exists(item => item.EntityName == EntityNames.User && item.EntityId == user.UserId)) { reload = Reload.Site; } } if (PageState == null || reload >= Reload.Site) { pages = await PageService.GetPagesAsync(site.SiteId); } else { pages = PageState.Pages; } // format path and remove alias path = path.Replace("//", "/"); if (!path.EndsWith("/")) { path += "/"; } if (alias.Path != "") { path = path.Replace(alias.Path + "/", ""); } // extract admin route elements from path var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); int result; // check if path has moduleid and control specification ie. page/moduleid/control/ if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result)) { action = segments[segments.Length - 1]; moduleid = result; path = path.Replace(moduleid.ToString() + "/" + action + "/", ""); } else { // check if path has only moduleid specification ie. page/moduleid/ if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result)) { moduleid = result; path = path.Replace(moduleid.ToString() + "/", ""); } } // remove trailing slash so it can be used as a key for Pages if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1); if (PageState == null || reload >= Reload.Page) { page = pages.Where(item => item.Path == path).FirstOrDefault(); } else { page = PageState.Page; } // failsafe in case router cannot locate the home page for the site if (page == null && path == "") { page = pages.FirstOrDefault(); path = page.Path; } // check if page has changed if (page != null && page.Path != path) { page = pages.Where(item => item.Path == path).FirstOrDefault(); reload = Reload.Page; if (page != null) { editmode = page.EditMode; } } if (page != null) { if (PageState == null) { editmode = page.EditMode; } // check if user is authorized to view page if (UserSecurity.IsAuthorized(user, PermissionNames.View, page.Permissions)) { page = await ProcessPage(page, site, user); _pagestate = new PageState { Alias = alias, Site = site, Pages = pages, Page = page, User = user, Uri = new Uri(_absoluteUri, UriKind.Absolute), QueryString = querystring, ModuleId = moduleid, Action = action, Runtime = runtime }; if (PageState != null && (PageState.ModuleId != _pagestate.ModuleId || PageState.Action != _pagestate.Action)) { reload = Reload.Page; } if (PageState == null || reload >= Reload.Page) { modules = await ModuleService.GetModulesAsync(site.SiteId); modules = ProcessModules(modules, page.PageId, _pagestate.ModuleId, _pagestate.Action, page.Panes, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); } else { modules = PageState.Modules; } _pagestate.Modules = modules; _pagestate.EditMode = editmode; _pagestate.LastSyncDate = lastsyncdate; OnStateChange?.Invoke(_pagestate); } } else { if (user == null) { await LogService.Log(null, null, null, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path); // redirect to login page NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "login", "returnurl=" + path)); } else { await LogService.Log(null, null, user.UserId, GetType().AssemblyQualifiedName, Utilities.GetTypeNameLastSegment(GetType().AssemblyQualifiedName, 1), LogFunction.Security, LogLevel.Error, null, "Page Does Not Exist Or User Is Not Authorized To View Page {Path}", path); if (path != "") { // redirect to home page NavigationManager.NavigateTo(Utilities.NavigateUrl(alias.Path, "", "")); } } } } else { // site does not exist } } private async void LocationChanged(object sender, LocationChangedEventArgs args) { _absoluteUri = args.Location; await Refresh(); } Task IHandleAfterRender.OnAfterRenderAsync() { if (!_navigationInterceptionEnabled) { _navigationInterceptionEnabled = true; return NavigationInterception.EnableNavigationInterceptionAsync(); } return Task.CompletedTask; } private Dictionary ParseQueryString(string query) { Dictionary querystring = new Dictionary(); if (!string.IsNullOrEmpty(query)) { query = query.Substring(1); // ignore "?" foreach (string kvp in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)) { if (kvp != "") { if (kvp.Contains("=")) { string[] pair = kvp.Split('='); querystring.Add(pair[0], pair[1]); } else { querystring.Add(kvp, "true"); // default parameter when no value is provided } } } } return querystring; } private async Task ProcessPage(Page page, Site site, User user) { try { if (page.IsPersonalizable && user != null) { // load the personalized page page = await PageService.GetPageAsync(page.PageId, user.UserId); } if (string.IsNullOrEmpty(page.ThemeType)) { page.ThemeType = site.DefaultThemeType; page.LayoutType = site.DefaultLayoutType; } page.Resources = new List(); Type themetype = Type.GetType(page.ThemeType); var themeobject = Activator.CreateInstance(themetype); if (themeobject != null) { page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null); var resources = (List)themetype.GetProperty("Resources").GetValue(themeobject, null); if (resources != null) { page.Resources.AddRange(resources); } } if (!string.IsNullOrEmpty(page.LayoutType)) { themetype = Type.GetType(page.LayoutType); themeobject = Activator.CreateInstance(themetype); if (themeobject != null) { page.Panes = (string)themetype.GetProperty("Panes").GetValue(themeobject, null); } } } catch { // error loading theme or layout } return page; } private List ProcessModules(List modules, int pageid, int moduleid, string control, string panes, string defaultcontainertype) { var paneindex = new Dictionary(); foreach (Module module in modules) { if (module.PageId == pageid || module.ModuleId == moduleid) { var typename = string.Empty; if (module.ModuleDefinition != null) { typename = module.ModuleDefinition.ControlTypeTemplate; } else { typename = Constants.ErrorModule; } if (module.ModuleId == moduleid && control != "") { // check if the module defines custom routes if (module.ModuleDefinition.ControlTypeRoutes != "") { foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { if (route.StartsWith(control + "=")) { typename = route.Replace(control + "=", ""); } } } module.ModuleType = typename.Replace(Constants.ActionToken, control); // admin controls need to load additional metadata from the IModuleControl interface if (moduleid == module.ModuleId) { typename = module.ModuleType; // check for core module actions component if (Constants.DefaultModuleActions.Contains(control)) { typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control); } Type moduletype = Type.GetType(typename); if (moduletype != null) { var moduleobject = Activator.CreateInstance(moduletype); module.SecurityAccessLevel = (SecurityAccessLevel)moduletype.GetProperty("SecurityAccessLevel").GetValue(moduleobject, null); module.ControlTitle = (string)moduletype.GetProperty("Title").GetValue(moduleobject); module.Actions = (string)moduletype.GetProperty("Actions").GetValue(moduleobject); module.UseAdminContainer = (bool)moduletype.GetProperty("UseAdminContainer").GetValue(moduleobject); } } } else { module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction); } // ensure module's pane exists in current page and if not, assign it to the Admin pane if (panes == null || !panes.ToLower().Contains(module.Pane.ToLower())) { module.Pane = Constants.AdminPane; } // calculate module position within pane if (paneindex.ContainsKey(module.Pane)) { paneindex[module.Pane] += 1; } else { paneindex.Add(module.Pane, 0); } module.PaneModuleIndex = paneindex[module.Pane]; if (string.IsNullOrEmpty(module.ContainerType)) { module.ContainerType = defaultcontainertype; } } } foreach (Module module in modules.Where(item => item.PageId == pageid)) { module.PaneModuleCount = paneindex[module.Pane] + 1; } return modules; } private Runtime GetRuntime() => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")) ? Runtime.WebAssembly : Runtime.Server; }