Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@ -411,6 +411,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_rendermode == RenderModes.Static)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enhancednavigation" HelpText="Indicates if enhanced navigation should be used with static rendering" ResourceKey="EnhancedNavigation">Enhanced Navigation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enhancednavigation" class="form-select" @bind="@_enhancednavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -537,6 +549,7 @@
|
||||
private string _defaultalias;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _enhancednavigation = "True";
|
||||
private string _runtime = Runtimes.Server;
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
@ -660,6 +673,7 @@
|
||||
|
||||
// hosting model
|
||||
_rendermode = site.RenderMode;
|
||||
_enhancednavigation = site.EnhancedNavigation.ToString();
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender.ToString();
|
||||
_hybrid = site.Hybrid.ToString();
|
||||
@ -807,13 +821,11 @@
|
||||
// hosting model
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
|
||||
{
|
||||
site.RenderMode = _rendermode;
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
site.RenderMode = _rendermode;
|
||||
site.EnhancedNavigation = bool.Parse(_enhancednavigation);
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
|
||||
site = await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||
|
||||
<div class="row" style="margin-bottom: 50px;">
|
||||
<div class="row" style="@_style">
|
||||
<div class="col">
|
||||
@_textEditorComponent
|
||||
</div>
|
||||
@ -18,6 +18,8 @@
|
||||
private RenderFragment _textEditorComponent;
|
||||
private ITextEditor _textEditor;
|
||||
|
||||
private string _style = "margin-bottom: 50px;";
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
|
||||
@ -30,6 +32,9 @@
|
||||
[Parameter]
|
||||
public string Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Style { get; set; } // optional
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
@ -40,6 +45,12 @@
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(Style))
|
||||
{
|
||||
_style = Style;
|
||||
}
|
||||
|
||||
_textEditorComponent = (builder) =>
|
||||
{
|
||||
CreateTextEditor(builder);
|
||||
|
||||
@ -460,6 +460,11 @@ namespace Oqtane.Modules
|
||||
|
||||
public string ReplaceTokens(string content, object obj)
|
||||
{
|
||||
// check for null or empty content
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return content;
|
||||
}
|
||||
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||
// that occur with string.Replace or string concatenation inside loops.
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@ -204,7 +204,7 @@
|
||||
<data name="Success.Page.Delete" xml:space="preserve">
|
||||
<value>Page Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Pages.Deleted" xml:space="preserve">
|
||||
<data name="Success.Pages.Delete" xml:space="preserve">
|
||||
<value>All Pages Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Module.Restore" xml:space="preserve">
|
||||
|
||||
@ -124,6 +124,9 @@
|
||||
<value>Module Type Is Invalid For {0}</value>
|
||||
</data>
|
||||
<data name="Error.Module.Exception" xml:space="preserve">
|
||||
<value>An Unexpected Error Has Occurred</value>
|
||||
<value>An Unexpected Error Has Occurred</value>
|
||||
</data>
|
||||
<data name="Error.Module.InvalidInjectedServices" xml:space="preserve">
|
||||
<value>Missing service(s): {0}. Please make sure they have been registered correctly.</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -1,7 +1,11 @@
|
||||
@namespace Oqtane.UI
|
||||
@using System.Reflection
|
||||
@using Module = Oqtane.Models.Module
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject SiteState ComponentSiteState
|
||||
@inject IStringLocalizer<ModuleInstance> Localizer
|
||||
@inject ILogService LoggingService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inherits ErrorBoundary
|
||||
|
||||
<CascadingValue Value="@PageState" IsFixed="true">
|
||||
@ -67,37 +71,50 @@
|
||||
{
|
||||
if (ShouldRender())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
|
||||
{
|
||||
ModuleType = Type.GetType(ModuleState.ModuleType);
|
||||
if (ModuleType != null)
|
||||
{
|
||||
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
|
||||
ComponentSiteState.Hydrate(SiteState);
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, ModuleType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", this);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// module does not exist with typename specified
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (string.IsNullOrEmpty(ModuleState.ModuleType))
|
||||
{
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleType = Type.GetType(ModuleState.ModuleType);
|
||||
var moduleName = Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0);
|
||||
if (ModuleType == null)
|
||||
{
|
||||
// module does not exist with typename specified
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidName"], moduleName);
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//only validate the services injection in development environment
|
||||
if (NavigationManager.BaseUri.Contains("localhost:") && !ValidateModuleTypeInjectedServices(ModuleType, out IList<string> missingServices))
|
||||
{
|
||||
// module type is not valid for instantiation
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidInjectedServices"], string.Join(",", missingServices));
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
|
||||
ComponentSiteState.Hydrate(SiteState);
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, ModuleType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", this);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,4 +182,26 @@
|
||||
_error = "";
|
||||
base.Recover();
|
||||
}
|
||||
|
||||
private bool ValidateModuleTypeInjectedServices(Type moduleType, out IList<string> missingServices)
|
||||
{
|
||||
missingServices = new List<string>();
|
||||
|
||||
var properties = Utilities.GetPropertiesIncludingInherited(moduleType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
foreach(var property in properties)
|
||||
{
|
||||
var injectAttribute = property.GetCustomAttribute(typeof(InjectAttribute));
|
||||
if (injectAttribute != null)
|
||||
{
|
||||
var serviceType = property.PropertyType;
|
||||
var service = ServiceProvider.GetService(serviceType);
|
||||
if (serviceType != null && service == null)
|
||||
{
|
||||
missingServices.Add(Utilities.GetTypeNameLastSegment(serviceType.FullName, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !missingServices.Any();
|
||||
}
|
||||
}
|
||||
@ -31,7 +31,7 @@
|
||||
<dependency id="MySql.Data" version="9.5.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Pomelo.EntityFrameworkCore.MySql" version="9.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="EFCore.NamingConventions" version="10.0.0-rc.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Npgsql.EntityFrameworkCore.PostgreSQL" version="10.0.0-rc.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Npgsql.EntityFrameworkCore.PostgreSQL" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Data.Sqlite.Core" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.EntityFrameworkCore.SqlServer" version="10.0.0" exclude="Build,Analyzers" />
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
}
|
||||
@((MarkupString)_headResources)
|
||||
</head>
|
||||
<body>
|
||||
<body data-enhance-nav="@_enhancedNavigation.ToString().ToLower()">
|
||||
@if (string.IsNullOrEmpty(_message))
|
||||
{
|
||||
@if (_renderMode == RenderModes.Static)
|
||||
@ -97,6 +97,7 @@
|
||||
private string _renderMode = RenderModes.Interactive;
|
||||
private string _runtime = Runtimes.Server;
|
||||
private bool _prerender = true;
|
||||
private bool _enhancedNavigation = true;
|
||||
private string _fingerprint = "";
|
||||
private int _visitorId = -1;
|
||||
private string _antiForgeryToken = "";
|
||||
@ -141,6 +142,7 @@
|
||||
_renderMode = site.RenderMode;
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender;
|
||||
_enhancedNavigation = site.EnhancedNavigation;
|
||||
_fingerprint = site.Fingerprint;
|
||||
|
||||
var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty);
|
||||
|
||||
@ -265,7 +265,19 @@ namespace Oqtane.Controllers
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||
|
||||
// set user personalized page path
|
||||
_settings.AddSetting(new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false });
|
||||
var settingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}";
|
||||
var pathSetting = _settings.GetSetting(EntityNames.User, page.UserId.Value, settingName);
|
||||
if(pathSetting == null)
|
||||
{
|
||||
pathSetting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = settingName, SettingValue = path, IsPrivate = false };
|
||||
_settings.AddSetting(pathSetting);
|
||||
}
|
||||
else
|
||||
{
|
||||
pathSetting.SettingValue = path;
|
||||
_settings.UpdateSetting(pathSetting);
|
||||
}
|
||||
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ namespace Oqtane.Infrastructure
|
||||
var mailboxAddressValidationError = "";
|
||||
|
||||
// sender
|
||||
if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True")
|
||||
if ((settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True") && string.IsNullOrEmpty(fromEmail))
|
||||
{
|
||||
fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", "");
|
||||
fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName;
|
||||
|
||||
@ -279,7 +279,7 @@ namespace Oqtane.Managers
|
||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||
}
|
||||
|
||||
if (bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")))
|
||||
if (bool.Parse(_settings.GetSettingValue(EntityNames.Site, alias.SiteId, "LoginOptions:RequireConfirmedEmail", "true")) && !user.IsDeleted)
|
||||
{
|
||||
if (user.EmailConfirmed)
|
||||
{
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
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.10.00.01.01")]
|
||||
public class AddSiteEnhancedNavigation : MultiDatabaseMigration
|
||||
{
|
||||
public AddSiteEnhancedNavigation(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteEntityBuilder.AddBooleanColumn("EnhancedNavigation", true);
|
||||
siteEntityBuilder.UpdateData("EnhancedNavigation", true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,7 @@
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
<!-- PostgreSQL Database Provider Dependencies -->
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="10.0.0-rc.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0-rc.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
<!-- SQLite Database Provider Dependencies -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.0" />
|
||||
|
||||
@ -115,6 +115,11 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public bool Hybrid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if enhanced navigation should be used with static rendering
|
||||
/// </summary>
|
||||
public bool EnhancedNavigation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of site configuration changes and is used by the ISiteMigration interface
|
||||
/// </summary>
|
||||
@ -222,6 +227,7 @@ namespace Oqtane.Models
|
||||
Runtime = Runtime,
|
||||
Prerender = Prerender,
|
||||
Hybrid = Hybrid,
|
||||
EnhancedNavigation = EnhancedNavigation,
|
||||
Version = Version,
|
||||
HomePageId = HomePageId,
|
||||
HeadContent = HeadContent,
|
||||
|
||||
@ -4,6 +4,8 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@ -750,6 +752,75 @@ namespace Oqtane.Shared
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<PropertyInfo> GetPropertiesIncludingInherited(Type type, BindingFlags bindingFlags)
|
||||
{
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
var currentType = type;
|
||||
while (currentType != null)
|
||||
{
|
||||
var properties = currentType.GetProperties(bindingFlags | BindingFlags.DeclaredOnly);
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (!dictionary.TryGetValue(property.Name, out var others))
|
||||
{
|
||||
dictionary.Add(property.Name, property);
|
||||
}
|
||||
else if (!IsInheritedProperty(property, others))
|
||||
{
|
||||
List<PropertyInfo> many;
|
||||
if (others is PropertyInfo single)
|
||||
{
|
||||
many = new List<PropertyInfo> { single };
|
||||
dictionary[property.Name] = many;
|
||||
}
|
||||
else
|
||||
{
|
||||
many = (List<PropertyInfo>)others;
|
||||
}
|
||||
many.Add(property);
|
||||
}
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
foreach (var item in dictionary)
|
||||
{
|
||||
if (item.Value is PropertyInfo property)
|
||||
{
|
||||
yield return property;
|
||||
continue;
|
||||
}
|
||||
|
||||
var list = (List<PropertyInfo>)item.Value;
|
||||
var count = list.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
yield return list[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsInheritedProperty(PropertyInfo property, object others)
|
||||
{
|
||||
if (others is PropertyInfo single)
|
||||
{
|
||||
return single.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition();
|
||||
}
|
||||
|
||||
var many = (List<PropertyInfo>)others;
|
||||
foreach (var other in CollectionsMarshal.AsSpan(many))
|
||||
{
|
||||
if (other.GetMethod?.GetBaseDefinition() == property.GetMethod?.GetBaseDefinition())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[Obsolete("ContentUrl(Alias alias, int fileId) is deprecated. Use FileUrl(Alias alias, int fileId) instead.", false)]
|
||||
public static string ContentUrl(Alias alias, int fileId)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user