using Microsoft.AspNetCore.Components; using Oqtane.Shared; using Oqtane.Models; using System.Threading.Tasks; using Oqtane.Services; using System; using Oqtane.Enums; using Oqtane.UI; using System.Collections.Generic; using Microsoft.JSInterop; using System.Linq; using System.Dynamic; using System.Reflection; namespace Oqtane.Modules { public abstract class ModuleBase : ComponentBase, IModuleControl { private Logger _logger; private string _urlparametersstate; private Dictionary _urlparameters; private bool _scriptsloaded = false; protected Logger logger => _logger ?? (_logger = new Logger(this)); [Inject] protected ILogService LoggingService { get; set; } [Inject] protected IJSRuntime JSRuntime { get; set; } [Inject] protected SiteState SiteState { get; set; } [CascadingParameter] protected PageState PageState { get; set; } [CascadingParameter] protected Models.Module ModuleState { get; set; } [Parameter] public RenderModeBoundary RenderModeBoundary { get; set; } // optional interface properties public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security public virtual string Title { get { return ""; } } public virtual string Actions { get { return ""; } } public virtual bool UseAdminContainer { get { return true; } } public virtual List Resources { get; set; } public virtual string RenderMode { get { return RenderModes.Interactive; } } // interactive by default public virtual bool? Prerender { get { return null; } } // allows the Site Prerender property to be overridden // url parameters public virtual string UrlParametersTemplate { get; set; } public Dictionary UrlParameters { get { if (_urlparametersstate == null || _urlparametersstate != PageState.UrlParameters) { _urlparametersstate = PageState.UrlParameters; _urlparameters = GetUrlParameters(UrlParametersTemplate); } return _urlparameters; } } // base lifecycle method for handling JSInterop script registration protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { List resources = null; var type = GetType(); if (type.BaseType == typeof(ModuleBase)) { if (PageState.Page.Resources != null) { resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList(); } } else // modulecontrolbase { if (Resources != null) { resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList(); } } if (resources != null && resources.Any()) { var interop = new Interop(JSRuntime); var scripts = new List(); var inline = 0; foreach (Resource resource in resources) { if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) { if (!string.IsNullOrEmpty(resource.Url)) { var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; scripts.Add(new { href = url, type = resource.Type ?? "", bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", location = resource.Location.ToString().ToLower(), dataAttributes = resource.DataAttributes }); } else { inline += 1; await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower()); } } } if (scripts.Any()) { await interop.IncludeScripts(scripts.ToArray()); } } _scriptsloaded = true; } } protected override bool ShouldRender() { return PageState?.RenderId == ModuleState?.RenderId; } public bool ScriptsLoaded { get { return _scriptsloaded; } } // path method public string ModulePath() { return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/"; } // fingerprint hash code for static assets public string Fingerprint { get { return ModuleState.ModuleDefinition.Fingerprint; } } // url methods // navigate url public string NavigateUrl() { return NavigateUrl(PageState.Page.Path); } public string NavigateUrl(string path) { return NavigateUrl(path, ""); } public string NavigateUrl(bool refresh) { return NavigateUrl(PageState.Page.Path, refresh); } public string NavigateUrl(string path, string querystring) { return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring); } public string NavigateUrl(string path, Dictionary querystring) { return NavigateUrl(path, Utilities.CreateQueryString(querystring)); } public string NavigateUrl(string path, bool refresh) { return NavigateUrl(path, refresh ? "refresh" : ""); } public string NavigateUrl(int moduleId, string action) { return EditUrl(PageState.Page.Path, moduleId, action, ""); } public string NavigateUrl(int moduleId, string action, string querystring) { return EditUrl(PageState.Page.Path, moduleId, action, querystring); } public string NavigateUrl(int moduleId, string action, Dictionary querystring) { return EditUrl(PageState.Page.Path, moduleId, action, querystring); } public string NavigateUrl(string path, int moduleId, string action) { return EditUrl(path, moduleId, action, ""); } public string NavigateUrl(string path, int moduleId, string action, string querystring) { return EditUrl(path, moduleId, action, querystring); } public string NavigateUrl(string path, int moduleId, string action, Dictionary querystring) { return EditUrl(path, moduleId, action, querystring); } // edit url public string EditUrl(string action) { return EditUrl(ModuleState.ModuleId, action); } public string EditUrl(string action, string querystring) { return EditUrl(ModuleState.ModuleId, action, querystring); } public string EditUrl(string action, Dictionary querystring) { return EditUrl(ModuleState.ModuleId, action, querystring); } public string EditUrl(int moduleId, string action) { return EditUrl(moduleId, action, ""); } public string EditUrl(int moduleId, string action, string querystring) { return EditUrl(PageState.Page.Path, moduleId, action, querystring); } public string EditUrl(int moduleId, string action, Dictionary querystring) { return EditUrl(PageState.Page.Path, moduleId, action, querystring); } public string EditUrl(string path, int moduleid, string action, string querystring) { return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring); } public string EditUrl(string path, int moduleid, string action, Dictionary querystring) { return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring)); } // file url public string FileUrl(string folderpath, string filename) { return FileUrl(folderpath, filename, false); } public string FileUrl(string folderpath, string filename, bool download) { return Utilities.FileUrl(PageState.Alias, folderpath, filename, download); } public string FileUrl(int fileid) { return FileUrl(fileid, false); } public string FileUrl(int fileid, bool download) { return Utilities.FileUrl(PageState.Alias, fileid, download); } // image url public string ImageUrl(int fileid, int width, int height) { return ImageUrl(fileid, width, height, ""); } public string ImageUrl(int fileid, int width, int height, string mode) { return ImageUrl(fileid, width, height, mode, "", "", 0, false); } public string ImageUrl(int fileid, int width, int height, string mode, string position, string background, int rotate, bool recreate) { return Utilities.ImageUrl(PageState.Alias, fileid, width, height, mode, position, background, rotate, recreate); } public string AddUrlParameters(params object[] parameters) { return Utilities.AddUrlParameters(parameters); } // template is in the form of a standard route template ie. "/{id}/{name}" and produces dictionary of key/value pairs // if url parameters belong to a specific module you should embed a unique key into the route (ie. /!/blog/1) and validate the url parameter key in the module public virtual Dictionary GetUrlParameters(string template = "") { var urlParameters = new Dictionary(); var parameters = _urlparametersstate.Split('/', StringSplitOptions.RemoveEmptyEntries); if (string.IsNullOrEmpty(template)) { // no template will populate dictionary with generic "parameter#" keys for (int i = 0; i < parameters.Length; i++) { urlParameters.TryAdd("parameter" + i, parameters[i]); } } else { var segments = template.Split('/', StringSplitOptions.RemoveEmptyEntries); string key; for (int i = 0; i < parameters.Length; i++) { if (i < segments.Length) { key = segments[i]; if (key.StartsWith("{") && key.EndsWith("}")) { // dynamic segment key = key.Substring(1, key.Length - 2); } else { // static segments use generic "parameter#" keys key = "parameter" + i.ToString(); } } else // unspecified segments use generic "parameter#" keys { key = "parameter" + i.ToString(); } urlParameters.TryAdd(key, parameters[i]); } } return urlParameters; } // UI methods public void AddModuleMessage(string message, MessageType type) { AddModuleMessage(message, type, "top"); } public void AddModuleMessage(string message, MessageType type, string position) { RenderModeBoundary.AddModuleMessage(message, type, position); } public void ClearModuleMessage() { RenderModeBoundary.AddModuleMessage("", MessageType.Undefined); } public void ShowProgressIndicator() { RenderModeBoundary.ShowProgressIndicator(); } public void HideProgressIndicator() { RenderModeBoundary.HideProgressIndicator(); } public void SetModuleTitle(string title) { dynamic obj = new ExpandoObject(); obj.PageModuleId = ModuleState.PageModuleId; obj.Title = title; SiteState.Properties.ModuleTitle = obj; } public void SetModuleVisibility(bool visible) { dynamic obj = new ExpandoObject(); obj.PageModuleId = ModuleState.PageModuleId; obj.Visible = visible; SiteState.Properties.ModuleVisibility = obj; } public void SetPageTitle(string title) { SiteState.Properties.PageTitle = title; } // note - only supports links and meta tags - not scripts public void AddHeadContent(string content) { SiteState.AppendHeadContent(content); } public void AddScript(Resource resource) { resource.ResourceType = ResourceType.Script; if (Resources == null) Resources = new List(); if (!Resources.Any(item => (!string.IsNullOrEmpty(resource.Url) && item.Url == resource.Url) || (!string.IsNullOrEmpty(resource.Content) && item.Content == resource.Content))) { Resources.Add(resource); } } public async Task ScrollToPageTop() { var interop = new Interop(JSRuntime); await interop.ScrollTo(0, 0, "smooth"); } public string ReplaceTokens(string content) { return ReplaceTokens(content, null); } public string ReplaceTokens(string content, object obj) { var tokens = new List(); var pos = content.IndexOf("["); if (pos != -1) { if (content.IndexOf("]", pos) != -1) { var token = content.Substring(pos, content.IndexOf("]", pos) - pos + 1); if (token.Contains(":")) { tokens.Add(token.Substring(1, token.Length - 2)); } } pos = content.IndexOf("[", pos + 1); } if (tokens.Count != 0) { foreach (string token in tokens) { var segments = token.Split(":"); if (segments.Length >= 2 && segments.Length <= 3) { var objectName = string.Join(":", segments, 0, segments.Length - 1); var propertyName = segments[segments.Length - 1]; var propertyValue = ""; switch (objectName) { case "ModuleState": propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString(); break; case "PageState": propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null).ToString(); break; case "PageState:Alias": propertyValue = PageState.Alias.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null).ToString(); break; case "PageState:Site": propertyValue = PageState.Site.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null).ToString(); break; case "PageState:Page": propertyValue = PageState.Page.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null).ToString(); break; case "PageState:User": propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null).ToString(); break; case "PageState:Route": propertyValue = PageState.Route.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null).ToString(); break; default: if (obj != null && obj.GetType().Name == objectName) { propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null).ToString(); } break; } if (propertyValue != null) { content = content.Replace("[" + token + "]", propertyValue); } } } } return content; } // logging methods public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) { LogFunction logFunction; if (string.IsNullOrEmpty(function)) { // try to infer from page action function = PageState.Action; } if (!Enum.TryParse(function, out logFunction)) { switch (function.ToLower()) { case "add": logFunction = LogFunction.Create; break; case "edit": logFunction = LogFunction.Update; break; case "delete": logFunction = LogFunction.Delete; break; case "": logFunction = LogFunction.Read; break; default: logFunction = LogFunction.Other; break; } } await Log(alias, level, logFunction, exception, message, args); } public async Task Log(Alias alias, LogLevel level, LogFunction function, Exception exception, string message, params object[] args) { int pageId = ModuleState.PageId; int moduleId = ModuleState.ModuleId; string category = GetType().AssemblyQualifiedName; string feature = Utilities.GetTypeNameLastSegment(category, 1); await LoggingService.Log(alias, pageId, moduleId, PageState.User?.UserId, category, feature, function, level, exception, message, args); } public class Logger { private readonly ModuleBase _moduleBase; public Logger(ModuleBase moduleBase) { _moduleBase = moduleBase; } public async Task LogTrace(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Trace, "", null, message, args); } public async Task LogTrace(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Trace, function, null, message, args); } public async Task LogTrace(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Trace, "", exception, message, args); } public async Task LogDebug(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Debug, "", null, message, args); } public async Task LogDebug(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Debug, function, null, message, args); } public async Task LogDebug(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Debug, "", exception, message, args); } public async Task LogInformation(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Information, "", null, message, args); } public async Task LogInformation(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Information, function, null, message, args); } public async Task LogInformation(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Information, "", exception, message, args); } public async Task LogWarning(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Warning, "", null, message, args); } public async Task LogWarning(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Warning, function, null, message, args); } public async Task LogWarning(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Warning, "", exception, message, args); } public async Task LogError(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Error, "", null, message, args); } public async Task LogError(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Error, function, null, message, args); } public async Task LogError(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Error, "", exception, message, args); } public async Task LogCritical(string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Critical, "", null, message, args); } public async Task LogCritical(LogFunction function, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Critical, function, null, message, args); } public async Task LogCritical(Exception exception, string message, params object[] args) { await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args); } } [Obsolete("ContentUrl(int fileId) is deprecated. Use FileUrl(int fileId) instead.", false)] public string ContentUrl(int fileid) { return ContentUrl(fileid, false); } [Obsolete("ContentUrl(int fileId, bool asAttachment) is deprecated. Use FileUrl(int fileId, bool download) instead.", false)] public string ContentUrl(int fileid, bool asAttachment) { return Utilities.FileUrl(PageState.Alias, fileid, asAttachment); } // Referencing ModuleInstance methods from ModuleBase is deprecated. Use the ModuleBase methods instead public ModuleInstance ModuleInstance { get { return new ModuleInstance(); } } } }