diff --git a/Oqtane.Client/UI/PageState.cs b/Oqtane.Client/UI/PageState.cs index a2678802..14b15bbc 100644 --- a/Oqtane.Client/UI/PageState.cs +++ b/Oqtane.Client/UI/PageState.cs @@ -14,10 +14,11 @@ namespace Oqtane.UI public List Modules { get; set; } public Uri Uri { get; set; } public Dictionary QueryString { get; set; } + public Dictionary PageVariables { get; set; } public int ModuleId { get; set; } public string Action { get; set; } public bool EditMode { get; set; } public DateTime LastSyncDate { get; set; } public Runtime Runtime { get; set; } } -} +} \ No newline at end of file diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index c3142689..3503cb77 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -75,6 +75,7 @@ Page page; User user = null; List modules; + Dictionary pageVariables = new Dictionary(); var moduleid = -1; var action = ""; var editmode = false; @@ -179,23 +180,33 @@ // extract admin route elements from path var segments = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); int result; - // check if path has moduleid and action specification ie. pagename/moduleid/action/ - if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result)) + + int modIdPos = 0; + + for (int i = 0; i < segments.Length; i++) { - action = segments[segments.Length - 1]; - moduleid = result; - path = path.Replace(moduleid.ToString() + "/" + action + "/", ""); - } - else - { - // check if path has moduleid specification ie. pagename/moduleid/ - if (segments.Length >= 1 && int.TryParse(segments[segments.Length - 1], out result)) + if (segments[i] == Constants.ModuleSegment) { - moduleid = result; - path = path.Replace(moduleid.ToString() + "/", ""); + modIdPos = i + 1; + } + + if (i > modIdPos && modIdPos != 0) + { + action += segments[i] + "/"; } } + // check if path has moduleid and action specification ie. pagename/moduleid/action/ + if (modIdPos > 0) + { + int.TryParse(segments[modIdPos], out result); + moduleid = result; + path = path.Replace(segments[modIdPos - 1] + "/" + segments[modIdPos] + "/" + action, ""); + + } + + if (action.EndsWith("/")) action = action.Substring(0, action.Length - 1); + // remove trailing slash so it can be used as a key for Pages if (path.EndsWith("/")) path = path.Substring(0, path.Length - 1); @@ -246,7 +257,7 @@ if (PageState == null || reload >= Reload.Page) { modules = await ModuleService.GetModulesAsync(site.SiteId); - (page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); + (page, modules, pageVariables) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType); } else { @@ -263,6 +274,7 @@ Modules = modules, Uri = new Uri(_absoluteUri, UriKind.Absolute), QueryString = querystring, + PageVariables = pageVariables, ModuleId = moduleid, Action = action, EditMode = editmode, @@ -314,9 +326,12 @@ return Task.CompletedTask; } - private Dictionary ParseQueryString(string query) + private Dictionary + ParseQueryString(string query) { - Dictionary querystring = new Dictionary(); + Dictionary + querystring = new Dictionary + (); if (!string.IsNullOrEmpty(query)) { query = query.Substring(1); // ignore "?" @@ -339,7 +354,8 @@ return querystring; } - private async Task ProcessPage(Page page, Site site, User user) + private async Task + ProcessPage(Page page, Site site, User user) { try { @@ -355,8 +371,10 @@ page.LayoutType = site.DefaultLayoutType; } - page.Panes = new List(); - page.Resources = new List(); + page.Panes = new List + (); + page.Resources = new List + (); string panes = ""; Type themetype = Type.GetType(page.ThemeType); @@ -393,9 +411,12 @@ return page; } - private (Page Page, List Modules) ProcessModules(Page page, List modules, int moduleid, string action, string defaultcontainertype) + private (Page Page, List + Modules, Dictionary PageVariables) ProcessModules(Page page, List + modules, int moduleid, string action, string defaultcontainertype) { var paneindex = new Dictionary(); + var pageVariables = new Dictionary(); foreach (Module module in modules) { if (module.PageId == page.PageId || module.ModuleId == moduleid) @@ -410,17 +431,52 @@ typename = Constants.ErrorModule; } + var pages = action.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (module.ModuleId == moduleid && action != "") { // check if the module defines custom routes if (module.ModuleDefinition.ControlTypeRoutes != "") { - foreach (string route in module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + var controlTypeRoutes = module.ModuleDefinition.ControlTypeRoutes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string route in controlTypeRoutes) { - if (route.StartsWith(action + "=")) + var pageAction = route.Split('=')[0]; + + var routes = pageAction.Split('/', StringSplitOptions.RemoveEmptyEntries); + var newRoute = ""; + if (pages.Length == routes.Length) { - typename = route.Replace(action + "=", ""); + for (int i = 0; i < pages.Length; i++) + { + if (pages.Length > i) + { + if (routes[i] == pages[i]) + { + newRoute += pages[i] + "/"; + } + else if (routes[i].StartsWith("[") && routes[i].EndsWith("]")) + { + newRoute += pages[i] + "/"; + var key = routes[i].Replace("[", ""); + key = key.Replace("]", ""); + pageVariables.TryAdd(key, pages[i]); + } + else + { + i = pages.Length; + newRoute = ""; + } + + } + } + + if (newRoute != "") + { + typename = route.Replace(pageAction + "=", ""); + } } + } } module.ModuleType = typename.Replace(Constants.ActionToken, action); @@ -489,10 +545,13 @@ module.PaneModuleCount = paneindex[module.Pane.ToLower()] + 1; } - return (page, modules); + return (page, modules, pageVariables); } - private List ManagePageResources(List pageresources, List resources) + private List + ManagePageResources(List + pageresources, List + resources) { if (resources != null) { @@ -509,7 +568,7 @@ } private Runtime GetRuntime() -=> RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")) - ? Runtime.WebAssembly - : Runtime.Server; -} + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")) + ? Runtime.WebAssembly + : Runtime.Server; +} \ No newline at end of file diff --git a/Oqtane.Server/appsettings.json b/Oqtane.Server/appsettings.json index 0ab499f0..4b88e95c 100644 --- a/Oqtane.Server/appsettings.json +++ b/Oqtane.Server/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "" + "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\\Oqtane.mdf;Initial Catalog=Oqtane;Integrated Security=SSPI;" }, "Runtime": "Server", "Installation": { diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 0ce9743b..3e02a938 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -19,6 +19,7 @@ namespace Oqtane.Shared public const string ActionToken = "{Action}"; public const string DefaultAction = "Index"; public const string AdminPane = "Admin"; + public const string ModuleSegment = "module"; // Default Module Actions are reserved and should not be used by modules public static readonly string[] DefaultModuleActions = new[] { "Settings", "Import", "Export" }; @@ -46,6 +47,7 @@ namespace Oqtane.Shared public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png"; public const string UploadableFiles = "jpg,jpeg,jpe,gif,bmp,png,mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg"; public const string ReservedDevices = "CON,NUL,PRN,COM0,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT0,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,CONIN$,CONOUT$"; + public static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char) 1, (Char) 2, (Char) 3, (Char) 4, (Char) 5, (Char) 6, (Char) 7, (Char) 8, @@ -55,4 +57,4 @@ namespace Oqtane.Shared }; public static readonly string[] InvalidFileNameEndingChars = { ".", " " }; } -} +} \ No newline at end of file diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 04f3ea1e..5efe5c38 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -39,7 +39,7 @@ namespace Oqtane.Shared { if (moduleid != -1) { - path += $"/{moduleid}"; + path += $"/{Constants.ModuleSegment}/{moduleid}"; if (!string.IsNullOrEmpty(action)) { path += $"/{action}"; @@ -136,6 +136,7 @@ namespace Oqtane.Shared stringBuilder.Append(RemapInternationalCharToAscii(c)); prevdash = false; break; + case UnicodeCategory.SpaceSeparator: case UnicodeCategory.ConnectorPunctuation: case UnicodeCategory.DashPunctuation: @@ -250,7 +251,7 @@ namespace Oqtane.Shared public static string PathCombine(params string[] segments) { - var separators = new char[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + var separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; for (int i = 1; i < segments.Length; i++) { @@ -284,7 +285,6 @@ namespace Oqtane.Shared !Constants.ReservedDevices.Split(',').Contains(name.ToUpper().Split('.')[0])); } - public static bool TryGetQueryValue( this Uri uri, string key, @@ -304,7 +304,7 @@ namespace Oqtane.Shared { value = defaultValue; string s; - return uri.TryGetQueryValue(key, out s, (string) null) && int.TryParse(s, out value); + return uri.TryGetQueryValue(key, out s, (string)null) && int.TryParse(s, out value); } public static Dictionary ParseQueryString(string query) @@ -314,7 +314,7 @@ namespace Oqtane.Shared { query = query.Substring(1); string str = query; - char[] separator = new char[1] {'&'}; + char[] separator = new char[1] { '&' }; foreach (string key in str.Split(separator, StringSplitOptions.RemoveEmptyEntries)) { if (key != "") @@ -333,4 +333,4 @@ namespace Oqtane.Shared return dictionary; } } -} +} \ No newline at end of file