Merge remote-tracking branch 'upstream/dev' into Bootstrap
This commit is contained in:
@ -144,7 +144,7 @@ else
|
|||||||
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -5,24 +5,57 @@
|
|||||||
@inject IStringLocalizer<Export> Localizer
|
@inject IStringLocalizer<Export> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="container">
|
<TabStrip>
|
||||||
<div class="row mb-1 align-items-center">
|
<TabPanel Name="Content" Heading="Content" ResourceKey="Content">
|
||||||
<Label Class="col-sm-3" For="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
<Label Class="col-sm-3" For="content" HelpText="Select the Export option and you will be able to view the module content" ResourceKey="Content">Content: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
</div>
|
<button type="button" class="btn btn-success" @onclick="ExportText">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="File" Heading="File" ResourceKey="File">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="folder" HelpText="Select a folder where you wish to save the exported content" ResourceKey="Folder">Folder: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager ShowFiles="false" ShowUpload="false" @ref="_filemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="filename" HelpText="Specify a name for the file (without an extension)" ResourceKey="Filename">Filename: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="content" type="text" class="form-control" @bind="@_filename" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ExportFile">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _content = string.Empty;
|
private string _content = string.Empty;
|
||||||
|
private FileManager _filemanager;
|
||||||
|
private string _filename = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Export Content";
|
public override string Title => "Export Content";
|
||||||
|
|
||||||
private async Task ExportModule()
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportText()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -35,4 +68,34 @@
|
|||||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExportFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var folderid = _filemanager.GetFolderId();
|
||||||
|
if (folderid != -1 && !string.IsNullOrEmpty(_filename))
|
||||||
|
{
|
||||||
|
var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename);
|
||||||
|
if (fileid != -1)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,27 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
|
@inject IFileService FileService
|
||||||
@inject IStringLocalizer<Import> Localizer
|
@inject IStringLocalizer<Import> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
|
<Label Class="col-sm-3" For="file" HelpText="Optionally upload or select a file to import for this module" ResourceKey="File">File: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager Filter="json" OnSelectFile="OnSelectFile" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="content" HelpText="Provide the module content to import" ResourceKey="Content">Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</form>
|
</form>
|
||||||
@ -28,6 +35,12 @@
|
|||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Import Content";
|
public override string Title => "Import Content";
|
||||||
|
|
||||||
|
private async Task OnSelectFile(int fileId)
|
||||||
|
{
|
||||||
|
var bytes = await FileService.DownloadFileAsync(fileId);
|
||||||
|
_content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportModule()
|
private async Task ImportModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
@ -101,15 +101,15 @@
|
|||||||
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="header" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
<Label Class="col-sm-3" For="moduleheader" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="header" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
<textarea id="moduleheader" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="footer" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
<Label Class="col-sm-3" For="modulefooter" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="footer" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
<textarea id="modulefooter" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,88 +8,92 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
if (!_userCreated)
|
@if (PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
if (PageState.User != null)
|
if (!_userCreated)
|
||||||
{
|
{
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
if (PageState.User != null)
|
||||||
}
|
{
|
||||||
else
|
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||||
{
|
}
|
||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
else
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
{
|
||||||
<div class="container">
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<div class="row mb-1 align-items-center">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<div class="input-group">
|
||||||
<div class="input-group">
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
|
||||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
|
||||||
@foreach (var timezone in _timezones)
|
|
||||||
{
|
|
||||||
<option value="@timezone.Id">@timezone.DisplayName</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
|
||||||
@if (_allowsitelogin)
|
|
||||||
{
|
|
||||||
<br />
|
<br />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
@if (_allowsitelogin)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
{
|
||||||
{
|
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
private List<Models.TimeZone> _timezones;
|
private List<Models.TimeZone> _timezones;
|
||||||
private string _passwordrequirements;
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
@ -113,6 +117,7 @@ else
|
|||||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
_timezoneid = PageState.Site.TimeZoneId;
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="An absolute Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||||
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,13 +8,13 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="A fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
@ -42,13 +42,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="email" class="form-control" @bind="@_email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
|
<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="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</div>
|
</div>
|
||||||
@ -68,7 +77,7 @@
|
|||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted">Deleted?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="isdeleted" class="form-select" @bind="@_isdeleted">
|
<select id="isdeleted" class="form-select" @bind="@_isdeleted">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
@ -78,13 +87,13 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin">Last Login:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
|
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
|
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress">Last IP Address:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
|
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
|
||||||
</div>
|
</div>
|
||||||
@ -167,6 +176,7 @@
|
|||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
|
private string _confirmed = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string _timezoneid = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
private string _isdeleted;
|
private string _isdeleted;
|
||||||
@ -204,6 +214,7 @@
|
|||||||
{
|
{
|
||||||
_username = user.Username;
|
_username = user.Username;
|
||||||
_email = user.Email;
|
_email = user.Email;
|
||||||
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
_displayname = user.DisplayName;
|
_displayname = user.DisplayName;
|
||||||
_timezoneid = PageState.User.TimeZoneId;
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
_isdeleted = user.IsDeleted.ToString();
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
@ -255,6 +266,7 @@
|
|||||||
user.Username = _username;
|
user.Username = _username;
|
||||||
user.Password = _password;
|
user.Password = _password;
|
||||||
user.Email = _email;
|
user.Email = _email;
|
||||||
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.TimeZoneId = _timezoneid;
|
user.TimeZoneId = _timezoneid;
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Oqtane.Shared;
|
|
||||||
using Oqtane.Models;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Oqtane.Services;
|
|
||||||
using System;
|
using System;
|
||||||
using Oqtane.Enums;
|
|
||||||
using Oqtane.UI;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
using System.Reflection;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.UI;
|
||||||
|
|
||||||
namespace Oqtane.Modules
|
namespace Oqtane.Modules
|
||||||
{
|
{
|
||||||
@ -79,18 +79,21 @@ namespace Oqtane.Modules
|
|||||||
{
|
{
|
||||||
List<Resource> resources = null;
|
List<Resource> resources = null;
|
||||||
var type = GetType();
|
var type = GetType();
|
||||||
if (type.BaseType == typeof(ModuleBase))
|
if (type.IsSubclassOf(typeof(ModuleBase)))
|
||||||
{
|
{
|
||||||
if (PageState.Page.Resources != null)
|
if (type.IsSubclassOf(typeof(ModuleControlBase)))
|
||||||
{
|
{
|
||||||
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList();
|
if (Resources != null)
|
||||||
|
{
|
||||||
|
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else // ModuleBase
|
||||||
else // modulecontrolbase
|
|
||||||
{
|
|
||||||
if (Resources != null)
|
|
||||||
{
|
{
|
||||||
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
if (PageState.Page.Resources != null)
|
||||||
|
{
|
||||||
|
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resources != null && resources.Any())
|
if (resources != null && resources.Any())
|
||||||
@ -421,70 +424,80 @@ namespace Oqtane.Modules
|
|||||||
|
|
||||||
public string ReplaceTokens(string content, object obj)
|
public string ReplaceTokens(string content, object obj)
|
||||||
{
|
{
|
||||||
var tokens = new List<string>();
|
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||||
var pos = content.IndexOf("[");
|
// that occur with string.Replace or string concatenation inside loops.
|
||||||
if (pos != -1)
|
var sb = new StringBuilder();
|
||||||
{
|
var cache = new Dictionary<string, string>(); // Cache to store resolved tokens
|
||||||
if (content.IndexOf("]", pos) != -1)
|
int index = 0;
|
||||||
{
|
|
||||||
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)
|
// Loop through content to find and replace all tokens
|
||||||
|
while (index < content.Length)
|
||||||
|
{
|
||||||
|
int start = content.IndexOf('[', index); // Find start of token
|
||||||
|
if (start == -1)
|
||||||
|
{
|
||||||
|
sb.Append(content, index, content.Length - index); // Append remaining content
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = content.IndexOf(']', start); // Find end of token
|
||||||
|
if (end == -1)
|
||||||
|
{
|
||||||
|
sb.Append(content, index, content.Length - index); // Append unmatched content
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(content, index, start - index); // Append content before token
|
||||||
|
|
||||||
|
string token = content.Substring(start + 1, end - start - 1); // Extract token without brackets
|
||||||
|
string[] parts = token.Split('|', 2); // Separate default fallback if present
|
||||||
|
string key = parts[0];
|
||||||
|
string fallback = parts.Length == 2 ? parts[1] : null;
|
||||||
|
|
||||||
|
if (!cache.TryGetValue(token, out string replacement)) // Check cache first
|
||||||
|
{
|
||||||
|
replacement = "[" + token + "]"; // Default replacement is original token
|
||||||
|
string[] segments = key.Split(':');
|
||||||
|
|
||||||
|
if (segments.Length >= 2)
|
||||||
|
{
|
||||||
|
object current = GetTarget(segments[0], obj); // Start from root object
|
||||||
|
for (int i = 1; i < segments.Length && current != null; i++)
|
||||||
{
|
{
|
||||||
case "ModuleState":
|
var type = current.GetType();
|
||||||
propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString();
|
var prop = type.GetProperty(segments[i]);
|
||||||
break;
|
current = prop?.GetValue(current);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
replacement = current.ToString();
|
||||||
|
}
|
||||||
|
else if (fallback != null)
|
||||||
|
{
|
||||||
|
replacement = fallback; // Use fallback if available
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
cache[token] = replacement; // Store in cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.Append(replacement); // Append replacement value
|
||||||
|
index = end + 1; // Move index past token
|
||||||
}
|
}
|
||||||
return content;
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the object instance for a given object name
|
||||||
|
// Easy to extend with additional object types
|
||||||
|
private object GetTarget(string name, object obj)
|
||||||
|
{
|
||||||
|
return name switch
|
||||||
|
{
|
||||||
|
"ModuleState" => ModuleState,
|
||||||
|
"PageState" => PageState,
|
||||||
|
_ => (obj != null && obj.GetType().Name == name) ? obj : null // Fallback to obj
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// date methods
|
// date methods
|
||||||
|
@ -121,10 +121,10 @@
|
|||||||
<value>Forgot Password</value>
|
<value>Forgot Password</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Success.Account.Verified" xml:space="preserve">
|
<data name="Success.Account.Verified" xml:space="preserve">
|
||||||
<value>User Account Verified Successfully. You Can Now Login With Your Username And Password Below.</value>
|
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Account.NotVerified" xml:space="preserve">
|
<data name="Message.Account.NotVerified" xml:space="preserve">
|
||||||
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
<value>User Account Email Address Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Success.Account.Linked" xml:space="preserve">
|
<data name="Success.Account.Linked" xml:space="preserve">
|
||||||
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
|
<value>User Account Linked Successfully. You Can Now Login With Your External Login Below.</value>
|
||||||
@ -133,7 +133,7 @@
|
|||||||
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
<value>External Login Could Not Be Linked. Please Contact Your Administrator For Further Instructions.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Login.Fail" xml:space="preserve">
|
<data name="Error.Login.Fail" xml:space="preserve">
|
||||||
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email If You Are A New User.</value>
|
<value>Login Failed. Please Remember That Passwords Are Case Sensitive. If You Have Attempted To Sign In Multiple Times Unsuccessfully, Your Account Will Be Locked Out For A Period Of Time. Note That User Accounts Often Require Email Address Verification So You May Wish To Check Your Email For A Notification.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.Required.UserInfo" xml:space="preserve">
|
<data name="Message.Required.UserInfo" xml:space="preserve">
|
||||||
<value>Please Provide All Required Fields</value>
|
<value>Please Provide All Required Fields</value>
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
<value>Export</value>
|
<value>Export</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Content.HelpText" xml:space="preserve">
|
<data name="Content.HelpText" xml:space="preserve">
|
||||||
<value>The Exported Module Content</value>
|
<value>Select the Export option and you will be able to view the module content</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Content.Text" xml:space="preserve">
|
<data name="Content.Text" xml:space="preserve">
|
||||||
<value>Content: </value>
|
<value>Content: </value>
|
||||||
@ -135,4 +135,25 @@
|
|||||||
<data name="Export Content" xml:space="preserve">
|
<data name="Export Content" xml:space="preserve">
|
||||||
<value>Export Content</value>
|
<value>Export Content</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Content.Heading" xml:space="preserve">
|
||||||
|
<value>Content</value>
|
||||||
|
</data>
|
||||||
|
<data name="File.Heading" xml:space="preserve">
|
||||||
|
<value>File</value>
|
||||||
|
</data>
|
||||||
|
<data name="Folder.Text" xml:space="preserve">
|
||||||
|
<value>Folder:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Folder.HelpText" xml:space="preserve">
|
||||||
|
<value>Select a folder where you wish to save the exported content</value>
|
||||||
|
</data>
|
||||||
|
<data name="Message.Content.Export" xml:space="preserve">
|
||||||
|
<value>Please Select A Folder And Provide A Filename Before Choosing Export</value>
|
||||||
|
</data>
|
||||||
|
<data name="Filename.Text" xml:space="preserve">
|
||||||
|
<value>Filename:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Filename.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify a name for the file (without an extension)</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -121,10 +121,10 @@
|
|||||||
<value>Redirect To:</value>
|
<value>Redirect To:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MappedUrl.HelpText" xml:space="preserve">
|
<data name="MappedUrl.HelpText" xml:space="preserve">
|
||||||
<value>A relative or absolute Url where the user will be redirected. Use "/" for site root path.</value>
|
<value>A Url where the user will be redirected (absolute or relative). Use '/' for site root path.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Url.HelpText" xml:space="preserve">
|
<data name="Url.HelpText" xml:space="preserve">
|
||||||
<value>An absolute Url for this site</value>
|
<value>A Url identifying a path to a specific page in the site (absolute or relative)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Url.Text" xml:space="preserve">
|
<data name="Url.Text" xml:space="preserve">
|
||||||
<value>Url:</value>
|
<value>Url:</value>
|
||||||
|
@ -121,10 +121,10 @@
|
|||||||
<value>Redirect To:</value>
|
<value>Redirect To:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MappedUrl.HelpText" xml:space="preserve">
|
<data name="MappedUrl.HelpText" xml:space="preserve">
|
||||||
<value>A relative or absolute Url where the user will be redirected. Use "/" for site root path.</value>
|
<value>A Url where the user will be redirected (absolute or relative). Use '/' for site root path.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Url.HelpText" xml:space="preserve">
|
<data name="Url.HelpText" xml:space="preserve">
|
||||||
<value>A relative Url identifying a path to a specific page in the site</value>
|
<value>A Url identifying a path to a specific page in the site (absolute or relative)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Url.Text" xml:space="preserve">
|
<data name="Url.Text" xml:space="preserve">
|
||||||
<value>Url:</value>
|
<value>Url:</value>
|
||||||
|
@ -216,4 +216,10 @@
|
|||||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||||
<value>The user's time zone</value>
|
<value>The user's time zone</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Confirmed.Text" xml:space="preserve">
|
||||||
|
<value>Confirmed?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||||
|
<value>Indicates if the user's email is verified</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -56,7 +56,18 @@ namespace Oqtane.Services
|
|||||||
/// Exports a given module
|
/// Exports a given module
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="moduleId"></param>
|
/// <param name="moduleId"></param>
|
||||||
/// <returns>module in JSON</returns>
|
/// <param name="pageId"></param>
|
||||||
|
/// <returns>module content in JSON format</returns>
|
||||||
Task<string> ExportModuleAsync(int moduleId, int pageId);
|
Task<string> ExportModuleAsync(int moduleId, int pageId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports a given module
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moduleId"></param>
|
||||||
|
/// <param name="pageId"></param>
|
||||||
|
/// <param name="folderId"></param>
|
||||||
|
/// <param name="filename"></param>
|
||||||
|
/// <returns>file id</returns>
|
||||||
|
Task<int> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,13 @@ namespace Oqtane.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ExportModuleAsync(int moduleId, int pageId)
|
public async Task<string> ExportModuleAsync(int moduleId, int pageId)
|
||||||
{
|
{
|
||||||
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}");
|
return await GetStringAsync($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> ExportModuleAsync(int moduleId, int pageId, int folderId, string filename)
|
||||||
|
{
|
||||||
|
return await PostJsonAsync<string,int>($"{Apiurl}/export?moduleid={moduleId}&pageid={pageId}&folderid={folderId}&filename={filename}", null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,18 +43,21 @@ namespace Oqtane.Themes
|
|||||||
{
|
{
|
||||||
List<Resource> resources = null;
|
List<Resource> resources = null;
|
||||||
var type = GetType();
|
var type = GetType();
|
||||||
if (type.BaseType == typeof(ThemeBase))
|
if (type.IsSubclassOf(typeof(ThemeBase)))
|
||||||
{
|
{
|
||||||
if (PageState.Page.Resources != null)
|
if (type.IsSubclassOf(typeof(ThemeControlBase)) || type.IsSubclassOf(typeof(ContainerBase)))
|
||||||
{
|
{
|
||||||
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Page && item.Namespace == type.Namespace).ToList();
|
if (Resources != null)
|
||||||
|
{
|
||||||
|
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else // ThemeBase
|
||||||
else // themecontrolbase, containerbase
|
|
||||||
{
|
|
||||||
if (Resources != null)
|
|
||||||
{
|
{
|
||||||
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
if (PageState.Page.Resources != null)
|
||||||
|
{
|
||||||
|
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Page && item.Namespace == type.Namespace).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resources != null && resources.Any())
|
if (resources != null && resources.Any())
|
||||||
|
@ -9,6 +9,8 @@ nuget.exe pack Oqtane.Framework.nuspec
|
|||||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL
|
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL
|
||||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish"
|
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish"
|
||||||
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
|
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
|
||||||
|
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" > NUL
|
||||||
|
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content"
|
||||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL
|
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL
|
||||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
|
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
|
||||||
setlocal ENABLEDELAYEDEXPANSION
|
setlocal ENABLEDELAYEDEXPANSION
|
||||||
|
@ -22,7 +22,6 @@ using Microsoft.AspNetCore.Cors;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||||
|
@ -9,6 +9,7 @@ using Oqtane.Infrastructure;
|
|||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
@ -20,18 +21,22 @@ namespace Oqtane.Controllers
|
|||||||
private readonly IPageRepository _pages;
|
private readonly IPageRepository _pages;
|
||||||
private readonly IModuleDefinitionRepository _moduleDefinitions;
|
private readonly IModuleDefinitionRepository _moduleDefinitions;
|
||||||
private readonly ISettingRepository _settings;
|
private readonly ISettingRepository _settings;
|
||||||
|
private readonly IFolderRepository _folders;
|
||||||
|
private readonly IFileRepository _files;
|
||||||
private readonly IUserPermissions _userPermissions;
|
private readonly IUserPermissions _userPermissions;
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
private readonly Alias _alias;
|
private readonly Alias _alias;
|
||||||
|
|
||||||
public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
|
public ModuleController(IModuleRepository modules, IPageModuleRepository pageModules, IPageRepository pages, IModuleDefinitionRepository moduleDefinitions, ISettingRepository settings, IFolderRepository folders, IFileRepository files, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger)
|
||||||
{
|
{
|
||||||
_modules = modules;
|
_modules = modules;
|
||||||
_pageModules = pageModules;
|
_pageModules = pageModules;
|
||||||
_pages = pages;
|
_pages = pages;
|
||||||
_moduleDefinitions = moduleDefinitions;
|
_moduleDefinitions = moduleDefinitions;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
_folders = folders;
|
||||||
|
_files = files;
|
||||||
_userPermissions = userPermissions;
|
_userPermissions = userPermissions;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -248,6 +253,61 @@ namespace Oqtane.Controllers
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST api/<controller>/export?moduleid=x&pageid=y&folderid=z&filename=a
|
||||||
|
[HttpPost("export")]
|
||||||
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
|
public int Export(int moduleid, int pageid, int folderid, string filename)
|
||||||
|
{
|
||||||
|
var fileid = -1;
|
||||||
|
var module = _modules.GetModule(moduleid);
|
||||||
|
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit) &&
|
||||||
|
_userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Folder, folderid, PermissionNames.Edit) && !string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
// get content
|
||||||
|
var content = _modules.ExportModule(moduleid);
|
||||||
|
|
||||||
|
// get folder
|
||||||
|
var folder = _folders.GetFolder(folderid, false);
|
||||||
|
string folderPath = _folders.GetFolderPath(folder);
|
||||||
|
if (!Directory.Exists(folderPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create json file
|
||||||
|
filename = Utilities.GetFriendlyUrl(Path.GetFileNameWithoutExtension(filename)) + ".json";
|
||||||
|
string filepath = Path.Combine(folderPath, filename);
|
||||||
|
if (System.IO.File.Exists(filepath))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(filepath);
|
||||||
|
}
|
||||||
|
System.IO.File.WriteAllText(filepath, content);
|
||||||
|
|
||||||
|
// register file
|
||||||
|
var file = _files.GetFile(folderid, filename);
|
||||||
|
if (file == null)
|
||||||
|
{
|
||||||
|
file = new Models.File { FolderId = folderid, Name = filename, Extension = "json", Size = (int)new FileInfo(filepath).Length, ImageWidth = 0, ImageHeight = 0 };
|
||||||
|
_files.AddFile(file);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file.Size = (int)new FileInfo(filepath).Length;
|
||||||
|
_files.UpdateFile(file);
|
||||||
|
}
|
||||||
|
fileid = file.FileId;
|
||||||
|
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Read, "Content Exported For Module {ModuleId} To Folder {FolderId}", moduleid, folderid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Export Attempt For Module {Module} To Folder {FolderId}", moduleid, folderid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileid;
|
||||||
|
}
|
||||||
|
|
||||||
// POST api/<controller>/import?moduleid=x&pageid=y
|
// POST api/<controller>/import?moduleid=x&pageid=y
|
||||||
[HttpPost("import")]
|
[HttpPost("import")]
|
||||||
[Authorize(Roles = RoleNames.Registered)]
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
|
@ -24,26 +24,50 @@ namespace Oqtane.Controllers
|
|||||||
private readonly IPageModuleRepository _pageModules;
|
private readonly IPageModuleRepository _pageModules;
|
||||||
private readonly IUserPermissions _userPermissions;
|
private readonly IUserPermissions _userPermissions;
|
||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly IAliasAccessor _aliasAccessor;
|
|
||||||
private readonly IOptionsMonitorCache<CookieAuthenticationOptions> _cookieCache;
|
private readonly IOptions<CookieAuthenticationOptions> _cookieOptions;
|
||||||
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _oidcCache;
|
private readonly IOptionsSnapshot<CookieAuthenticationOptions> _cookieOptionsSnapshot;
|
||||||
private readonly IOptionsMonitorCache<OAuthOptions> _oauthCache;
|
private readonly IOptionsMonitorCache<CookieAuthenticationOptions> _cookieOptionsMonitorCache;
|
||||||
private readonly IOptionsMonitorCache<IdentityOptions> _identityCache;
|
|
||||||
|
private readonly IOptions<OpenIdConnectOptions> _oidcOptions;
|
||||||
|
private readonly IOptionsSnapshot<OpenIdConnectOptions> _oidcOptionsSnapshot;
|
||||||
|
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _oidcOptionsMonitorCache;
|
||||||
|
|
||||||
|
private readonly IOptions<OAuthOptions> _oauthOptions;
|
||||||
|
private readonly IOptionsSnapshot<OAuthOptions> _oauthOptionsSnapshot;
|
||||||
|
private readonly IOptionsMonitorCache<OAuthOptions> _oauthOptionsMonitorCache;
|
||||||
|
|
||||||
|
private readonly IOptions<IdentityOptions> _identityOptions;
|
||||||
|
private readonly IOptionsSnapshot<IdentityOptions> _identityOptionsSnapshot;
|
||||||
|
private readonly IOptionsMonitorCache<IdentityOptions> _identityOptionsMonitorCache;
|
||||||
|
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
private readonly Alias _alias;
|
private readonly Alias _alias;
|
||||||
private readonly string _visitorCookie;
|
private readonly string _visitorCookie;
|
||||||
|
|
||||||
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, IOptionsMonitorCache<CookieAuthenticationOptions> cookieCache, IOptionsMonitorCache<OpenIdConnectOptions> oidcCache, IOptionsMonitorCache<OAuthOptions> oauthCache, IOptionsMonitorCache<IdentityOptions> identityCache, ILogManager logger)
|
public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager,
|
||||||
|
IOptions<CookieAuthenticationOptions> cookieOptions, IOptionsSnapshot<CookieAuthenticationOptions> cookieOptionsSnapshot, IOptionsMonitorCache<CookieAuthenticationOptions> cookieOptionsMonitorCache,
|
||||||
|
IOptions<OpenIdConnectOptions> oidcOptions, IOptionsSnapshot<OpenIdConnectOptions> oidcOptionsSnapshot, IOptionsMonitorCache<OpenIdConnectOptions> oidcOptionsMonitorCache,
|
||||||
|
IOptions<OAuthOptions> oauthOptions, IOptionsSnapshot<OAuthOptions> oauthOptionsSnapshot, IOptionsMonitorCache<OAuthOptions> oauthOptionsMonitorCache,
|
||||||
|
IOptions<IdentityOptions> identityOptions, IOptionsSnapshot<IdentityOptions> identityOptionsSnapshot, IOptionsMonitorCache<IdentityOptions> identityOptionsMonitorCache,
|
||||||
|
ILogManager logger)
|
||||||
{
|
{
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_pageModules = pageModules;
|
_pageModules = pageModules;
|
||||||
_userPermissions = userPermissions;
|
_userPermissions = userPermissions;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_aliasAccessor = aliasAccessor;
|
_cookieOptions = cookieOptions;
|
||||||
_cookieCache = cookieCache;
|
_cookieOptionsSnapshot = cookieOptionsSnapshot;
|
||||||
_oidcCache = oidcCache;
|
_cookieOptionsMonitorCache = cookieOptionsMonitorCache;
|
||||||
_oauthCache = oauthCache;
|
_oidcOptions = oidcOptions;
|
||||||
_identityCache = identityCache;
|
_oidcOptionsSnapshot = oidcOptionsSnapshot;
|
||||||
|
_oidcOptionsMonitorCache = oidcOptionsMonitorCache;
|
||||||
|
_oauthOptions = oauthOptions;
|
||||||
|
_oauthOptionsSnapshot = oauthOptionsSnapshot;
|
||||||
|
_oauthOptionsMonitorCache = oauthOptionsMonitorCache;
|
||||||
|
_identityOptions = identityOptions;
|
||||||
|
_identityOptionsSnapshot = identityOptionsSnapshot;
|
||||||
|
_identityOptionsMonitorCache = identityOptionsMonitorCache;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_alias = tenantManager.GetAlias();
|
_alias = tenantManager.GetAlias();
|
||||||
_visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
_visitorCookie = Constants.VisitorCookiePrefix + _alias.SiteId.ToString();
|
||||||
@ -210,21 +234,21 @@ namespace Oqtane.Controllers
|
|||||||
[Authorize(Roles = RoleNames.Admin)]
|
[Authorize(Roles = RoleNames.Admin)]
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
// clear SiteOptionsCache for each option type
|
(_cookieOptions as SiteOptionsManager<CookieAuthenticationOptions>).Reset();
|
||||||
var cookieCache = new SiteOptionsCache<CookieAuthenticationOptions>(_aliasAccessor);
|
(_cookieOptionsSnapshot as SiteOptionsManager<CookieAuthenticationOptions>).Reset();
|
||||||
cookieCache.Clear();
|
_cookieOptionsMonitorCache.Clear();
|
||||||
var oidcCache = new SiteOptionsCache<OpenIdConnectOptions>(_aliasAccessor);
|
|
||||||
oidcCache.Clear();
|
|
||||||
var oauthCache = new SiteOptionsCache<OAuthOptions>(_aliasAccessor);
|
|
||||||
oauthCache.Clear();
|
|
||||||
var identityCache = new SiteOptionsCache<IdentityOptions>(_aliasAccessor);
|
|
||||||
identityCache.Clear();
|
|
||||||
|
|
||||||
// clear IOptionsMonitorCache for each option type
|
(_oidcOptions as SiteOptionsManager<OpenIdConnectOptions>).Reset();
|
||||||
_cookieCache.Clear();
|
(_oidcOptionsSnapshot as SiteOptionsManager<OpenIdConnectOptions>).Reset();
|
||||||
_oidcCache.Clear();
|
_oidcOptionsMonitorCache.Clear();
|
||||||
_oauthCache.Clear();
|
|
||||||
_identityCache.Clear();
|
(_oauthOptions as SiteOptionsManager<OAuthOptions>).Reset();
|
||||||
|
(_oauthOptionsSnapshot as SiteOptionsManager<OAuthOptions>).Reset();
|
||||||
|
_oauthOptionsMonitorCache.Clear();
|
||||||
|
|
||||||
|
(_identityOptions as SiteOptionsManager<IdentityOptions>).Reset();
|
||||||
|
(_identityOptionsSnapshot as SiteOptionsManager<IdentityOptions>).Reset();
|
||||||
|
_identityOptionsMonitorCache.Clear();
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
|
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ namespace Oqtane.Controllers
|
|||||||
filtered.TwoFactorCode = "";
|
filtered.TwoFactorCode = "";
|
||||||
filtered.SecurityStamp = "";
|
filtered.SecurityStamp = "";
|
||||||
|
|
||||||
// include private properties if authenticated user is accessing their own user account os is an administrator
|
// include private properties if authenticated user is accessing their own user account or is an administrator
|
||||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId)
|
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||||
{
|
{
|
||||||
filtered.Email = user.Email;
|
filtered.Email = user.Email;
|
||||||
@ -140,6 +140,7 @@ namespace Oqtane.Controllers
|
|||||||
filtered.LastLoginOn = user.LastLoginOn;
|
filtered.LastLoginOn = user.LastLoginOn;
|
||||||
filtered.LastIPAddress = user.LastIPAddress;
|
filtered.LastIPAddress = user.LastIPAddress;
|
||||||
filtered.TwoFactorRequired = user.TwoFactorRequired;
|
filtered.TwoFactorRequired = user.TwoFactorRequired;
|
||||||
|
filtered.EmailConfirmed = user.EmailConfirmed;
|
||||||
filtered.Roles = user.Roles;
|
filtered.Roles = user.Roles;
|
||||||
filtered.CreatedBy = user.CreatedBy;
|
filtered.CreatedBy = user.CreatedBy;
|
||||||
filtered.CreatedOn = user.CreatedOn;
|
filtered.CreatedOn = user.CreatedOn;
|
||||||
@ -200,10 +201,15 @@ namespace Oqtane.Controllers
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<User> Put(int id, [FromBody] User user)
|
public async Task<User> Put(int id, [FromBody] User user)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && _users.GetUser(user.UserId, false) != null
|
var existing = _userManager.GetUser(user.UserId, user.SiteId);
|
||||||
|
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && user.UserId == id && existing != null
|
||||||
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
|
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
|
||||||
{
|
{
|
||||||
user.EmailConfirmed = User.IsInRole(RoleNames.Admin);
|
// only authorized users can update the email confirmation
|
||||||
|
if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
user.EmailConfirmed = existing.EmailConfirmed;
|
||||||
|
}
|
||||||
user = await _userManager.UpdateUser(user);
|
user = await _userManager.UpdateUser(user);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Oqtane.Models;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ namespace Oqtane.Infrastructure
|
|||||||
{
|
{
|
||||||
var cache = map.GetOrAdd(GetKey(), new OptionsCache<TOptions>());
|
var cache = map.GetOrAdd(GetKey(), new OptionsCache<TOptions>());
|
||||||
cache.Clear();
|
cache.Clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TOptions GetOrAdd(string name, Func<TOptions> createOptions)
|
public TOptions GetOrAdd(string name, Func<TOptions> createOptions)
|
||||||
|
@ -65,7 +65,12 @@ namespace Oqtane.Managers
|
|||||||
{
|
{
|
||||||
user.SiteId = siteid;
|
user.SiteId = siteid;
|
||||||
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
user.Roles = GetUserRoles(user.UserId, user.SiteId);
|
||||||
user.SecurityStamp = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult()?.SecurityStamp;
|
var identityuser = _identityUserManager.FindByNameAsync(user.Username).GetAwaiter().GetResult();
|
||||||
|
if (identityuser != null)
|
||||||
|
{
|
||||||
|
user.SecurityStamp = identityuser.SecurityStamp;
|
||||||
|
user.EmailConfirmed = identityuser.EmailConfirmed;
|
||||||
|
}
|
||||||
user.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
|
user.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
|
||||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||||
}
|
}
|
||||||
@ -245,22 +250,30 @@ namespace Oqtane.Managers
|
|||||||
{
|
{
|
||||||
identityuser.Email = user.Email;
|
identityuser.Email = user.Email;
|
||||||
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||||
|
|
||||||
// if email address changed and it is not confirmed, verification is required for new email address
|
|
||||||
if (!user.EmailConfirmed)
|
|
||||||
{
|
|
||||||
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
|
||||||
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
|
||||||
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
|
||||||
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
|
||||||
_notifications.AddNotification(notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.EmailConfirmed)
|
if (user.EmailConfirmed)
|
||||||
{
|
{
|
||||||
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
if (!identityuser.EmailConfirmed)
|
||||||
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
{
|
||||||
|
var emailConfirmationToken = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
|
await _identityUserManager.ConfirmEmailAsync(identityuser, emailConfirmationToken);
|
||||||
|
|
||||||
|
string body = "Dear " + user.DisplayName + ",\n\nThe Email Address For Your User Account Has Been Verified. You Can Now Login With Your Username And Password.";
|
||||||
|
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||||
|
_notifications.AddNotification(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
identityuser.EmailConfirmed = false;
|
||||||
|
await _identityUserManager.UpdateAsync(identityuser); // security stamp not updated
|
||||||
|
|
||||||
|
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
|
||||||
|
string url = alias.Protocol + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
|
||||||
|
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Verify The Email Address Associated To Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
|
||||||
|
var notification = new Notification(user.SiteId, user, "User Account Verification", body);
|
||||||
|
_notifications.AddNotification(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
user = _users.UpdateUser(user);
|
user = _users.UpdateUser(user);
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.11" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.8" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.8" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
<ProjectReference Include="..\Oqtane.Client\Oqtane.Client.csproj" />
|
||||||
|
@ -43,7 +43,7 @@ namespace Oqtane.Models
|
|||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sorting order of the folder
|
/// Sorting order of the folder ** not used as folders are sorted in alphabetical order **
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ namespace Oqtane.Models
|
|||||||
|
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public Result() {}
|
||||||
|
|
||||||
public Result(bool success)
|
public Result(bool success)
|
||||||
{
|
{
|
||||||
Success = success;
|
Success = success;
|
||||||
|
@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline
|
|||||||
|
|
||||||
# Latest Release
|
# Latest Release
|
||||||
|
|
||||||
[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) was released on April 10, 2025 and is a maintenance release including 41 pull requests by 3 different contributors, pushing the total number of project commits all-time to over 6500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
[6.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3) was released on May 29, 2025 and is a maintenance release including 59 pull requests by 5 different contributors, pushing the total number of project commits all-time to over 6600. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
|
||||||
|
|
||||||
# Try It Now!
|
# Try It Now!
|
||||||
|
|
||||||
@ -22,11 +22,11 @@ Microsoft's Public Cloud (requires an Azure account)
|
|||||||
A free ASP.NET hosting account. No hidden fees. No credit card required.
|
A free ASP.NET hosting account. No hidden fees. No credit card required.
|
||||||
[](https://www.monsterasp.net/)
|
[](https://www.monsterasp.net/)
|
||||||
|
|
||||||
# Getting Started (Version 6.1.2)
|
# Getting Started (Version 6)
|
||||||
|
|
||||||
**Installing using source code from the Dev/Master branch:**
|
**Installing using source code from the Dev/Master branch:**
|
||||||
|
|
||||||
- Install **[.NET 9.0.4 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**.
|
- Install **[.NET 9.0.5 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**.
|
||||||
|
|
||||||
- Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
|
- Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
|
||||||
|
|
||||||
@ -92,6 +92,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan
|
|||||||
# Roadmap
|
# Roadmap
|
||||||
This project is open source, and therefore is a work in progress...
|
This project is open source, and therefore is a work in progress...
|
||||||
|
|
||||||
|
[6.1.3](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.3) (May 29, 2025)
|
||||||
|
- [x] Stabilization improvements
|
||||||
|
|
||||||
[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) (Apr 10, 2025)
|
[6.1.2](https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.2) (Apr 10, 2025)
|
||||||
- [x] Stabilization improvements
|
- [x] Stabilization improvements
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@
|
|||||||
"apiVersion": "2024-04-01",
|
"apiVersion": "2024-04-01",
|
||||||
"name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]",
|
"name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]",
|
||||||
"properties": {
|
"properties": {
|
||||||
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.2/Oqtane.Framework.6.1.2.Install.zip"
|
"packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.1.3/Oqtane.Framework.6.1.3.Install.zip"
|
||||||
},
|
},
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
|
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
|
||||||
|
Reference in New Issue
Block a user