Merge pull request #1 from oqtane/dev

sync
This commit is contained in:
gjwalk
2021-02-15 13:49:36 -05:00
committed by GitHub
83 changed files with 1622 additions and 954 deletions

View File

@ -1,141 +0,0 @@
@namespace Oqtane.Modules.Admin.Jobs
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IJobService JobService
@inject IStringLocalizer<Add> Localizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="type" HelpText="Enter the job type" ResourceKey="Type">Type: </Label>
</td>
<td>
<input id="type" class="form-control" @bind="@_jobType" />
</td>
</tr>
<tr>
<td>
<Label For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
</td>
<td>
<select id="enabled" class="form-control" @bind="@_isEnabled">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
</td>
<td>
<input id="runs-every" class="form-control" @bind="@_interval" />
<select id="runs-every" class="form-control" @bind="@_frequency">
<option value="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option>
<option value="d">@Localizer["Day(s)"]</option>
<option value="M">@Localizer["Month(s)"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="starting" HelpText="What time do you want the job to start" ResourceKey="Starting">Starting: </Label>
</td>
<td>
<input id="starting" class="form-control" @bind="@_startDate" />
</td>
</tr>
<tr>
<td>
<Label For="ending" HelpText="When do you want the job to end" ResourceKey="Ending">Ending: </Label>
</td>
<td>
<input id="ending" class="form-control" @bind="@_endDate" />
</td>
</tr>
<tr>
<td>
<Label For="retention-log" HelpText="What items do you want in the retention log" ResourceKey="RetentionLog">Retention Log (Items): </Label>
</td>
<td>
<input id="retention-log" class="form-control" @bind="@_retentionHistory" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveJob">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
@code {
private string _name = string.Empty;
private string _jobType = string.Empty;
private string _isEnabled = "True";
private string _interval = string.Empty;
private string _frequency = string.Empty;
private string _startDate = string.Empty;
private string _endDate = string.Empty;
private string _retentionHistory = "10";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private async Task SaveJob()
{
if (_name != string.Empty && !string.IsNullOrEmpty(_jobType) && _frequency != string.Empty && _interval != string.Empty && _retentionHistory != string.Empty)
{
var job = new Job();
job.Name = _name;
job.JobType = _jobType;
job.IsEnabled = Boolean.Parse(_isEnabled);
job.Frequency = _frequency;
job.Interval = int.Parse(_interval);
if (_startDate == string.Empty)
{
job.StartDate = null;
}
else
{
job.StartDate = DateTime.Parse(_startDate);
}
if (_endDate == string.Empty)
{
job.EndDate = null;
}
else
{
job.EndDate = DateTime.Parse(_endDate);
}
job.RetentionHistory = int.Parse(_retentionHistory);
job.IsStarted = false;
job.IsExecuting = false;
job.NextExecution = null;
try
{
job = await JobService.AddJobAsync(job);
await logger.LogInformation("Job Added {Job}", job);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Job {Job} {Error}", job, ex.Message);
AddModuleMessage(Localizer["Error Adding Job"], MessageType.Error);
}
}
else
{
AddModuleMessage(Localizer["You Must Provide The Job Name, Type, Frequency, and Retention"], MessageType.Warning);
}
}
}

View File

@ -15,10 +15,10 @@
</tr>
<tr>
<td>
<Label For="type" HelpText="Enter the job type" ResourceKey="Type">Type: </Label>
<Label For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
</td>
<td>
<input id="type" class="form-control" @bind="@_jobType" />
<input id="type" class="form-control" @bind="@_jobType" readonly />
</td>
</tr>
<tr>

View File

@ -9,7 +9,6 @@
}
else
{
<ActionLink Action="Add" Text="Add Job" ResourceKey="AddJob" />
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">Refresh</button>
<br />

View File

@ -0,0 +1,107 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@inject NavigationManager NavigationManager
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
@inject IStringLocalizer<Add> Localizer
@if (_supportedCultures == null)
{
<p><em>@Localizer["Loading..."]</em></p>
}
else
{
@if (_supportedCultures?.Count() > 1)
{
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="Name Of The Langauage" ResourceKey="Name">Name:</Label>
</td>
<td>
<select id="_code" class="form-control" @bind="@_code">
@foreach (var culture in _supportedCultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
</td>
<td>
<select id="default" class="form-control" @bind="@_isDefault">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@Localizer["Save"]</button>
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
}
@code {
private string _code = string.Empty;
private string _isDefault = "False";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private IEnumerable<Culture> _supportedCultures;
protected override async Task OnParametersSetAsync()
{
_supportedCultures = await LocalizationService.GetCulturesAsync();
if (_supportedCultures.Count() <= 1)
{
AddModuleMessage(Localizer["The Only Supported Culture That Has Been Defined Is English"], MessageType.Warning);
}
}
private async Task SaveLanguage()
{
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
};
try
{
language = await LanguageService.AddLanguageAsync(language);
if (language.IsDefault)
{
await SetCultureAsync(language.Code);
}
await logger.LogInformation("Language Added {Language}", language);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error Adding Language"], MessageType.Error);
}
}
private async Task SetCultureAsync(string culture)
{
if (culture != CultureInfo.CurrentUICulture.Name)
{
var interop = new Interop(JSRuntime);
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
}
}

View File

@ -0,0 +1,56 @@
@namespace Oqtane.Modules.Admin.Languages
@inherits ModuleBase
@inject ILanguageService LanguageService
@inject IStringLocalizer<Index> Localizer
@if (_languages == null)
{
<p><em>@Localizer["Loading..."]</em></p>
}
else
{
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
<Pager Items="@_languages">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
<th>@Localizer["Code"]</th>
<th>@Localizer["Default?"]</th>
</Header>
<Row>
<td><ActionDialog Header="Delete Langauge" Message="@Localizer["Are You Sure You Wish To Delete The {0} Language?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@(context.IsDefault)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td>
<td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
</Row>
</Pager>
}
@code {
private List<Language> _languages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync()
{
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
}
private async Task DeleteLanguage(Language language)
{
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error Deleting Language"], MessageType.Error);
}
}
}

View File

@ -7,6 +7,7 @@
@inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer
@using System.Text.RegularExpressions
@using System.IO;
@if (string.IsNullOrEmpty(_moduledefinitionname))
{
@ -113,7 +114,7 @@ else
{
try
{
if (IsValid(_owner) && IsValid(_module) && _template != "-")
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
{
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
@ -126,7 +127,7 @@ else
}
else
{
AddModuleMessage(Localizer["You Must Provide A Valid Owner Name, Module Name, And Template"], MessageType.Warning);
AddModuleMessage(Localizer["You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template"], MessageType.Warning);
}
}
catch (Exception ex)
@ -171,14 +172,18 @@ else
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null)
{
string[] path = systeminfo["serverpath"].Split('\\');
string[] path = systeminfo["serverpath"].Split(Path.DirectorySeparatorChar);
if (_template == "internal")
{
_location = string.Join("\\", path, 0, path.Length - 1) + "\\Oqtane.Client\\Modules\\" + _owner + "." + _module;
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 1) +
Path.DirectorySeparatorChar + "Oqtane.Client" +
Path.DirectorySeparatorChar + "Modules" +
Path.DirectorySeparatorChar + _owner + "." + _module;
}
else
{
_location = string.Join("\\", path, 0, path.Length - 2) + "\\" + _owner + "." + _module;
_location = string.Join(Path.DirectorySeparatorChar, path, 0, path.Length - 2) +
Path.DirectorySeparatorChar + _owner + "." + _module;
}
}
}

View File

@ -60,7 +60,7 @@
</tr>
<tr>
<td>
<Label For="url" HelpText="The reference url of the module" resource="ReferenceUrl">Reference Url: </Label>
<Label For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
</td>
<td>
<input id="url" class="form-control" @bind="@_url" disabled />

View File

@ -47,7 +47,7 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
try
{
@ -100,7 +100,8 @@ else
try
{
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Module Deleted Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(Localizer["Module Deleted Successfully"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Admin.Modules
@using Oqtane.Interfaces
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IThemeService ThemeService
@ -162,15 +163,16 @@
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_settingsModuleType != null)
{
var moduleType = Type.GetType(ModuleState.ModuleType);
if (moduleType != null)
{
moduleType.GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component
}
}
if (_settings is ISettingsControl control)
{
await control.UpdateSettings();
}
else
{
// Compatibility 2.0 fallback
_settings?.GetType().GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component
}
NavigationManager.NavigateTo(NavigateUrl());
}

View File

@ -189,7 +189,7 @@
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
@code {
private List<Theme> _themeList;
@ -386,8 +386,15 @@
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
}
else
{
AddModuleMessage(Localizer["You Must Provide Page Name And Theme/Layout"], MessageType.Warning);
@ -401,6 +408,18 @@
}
}
private void Cancel()
{
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
}
}
private static bool PagePathIsUnique(string pagePath, int siteId, List<Page> existingPages)
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);

View File

@ -205,7 +205,7 @@
</TabPanel>
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button>
@code {
private List<Theme> _themeList;
@ -493,8 +493,15 @@
}
await logger.LogInformation("Page Saved {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
}
else
{
AddModuleMessage(Localizer["You Must Provide Page Name"], MessageType.Warning);
@ -507,6 +514,18 @@
}
}
private void Cancel()
{
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl());
}
}
private static bool PagePathIsUnique(string pagePath, int siteId, int pageId, List<Page> existingPages)
{
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.PageId != pageId);

View File

@ -38,7 +38,7 @@ else
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
</td>
<td>
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
</td>
</tr>
<tr>
@ -46,7 +46,7 @@ else
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
</td>
<td>
<input id="expiryDate" class="form-control" @bind="@expirydate" />
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
</td>
</tr>
</table>
@ -75,8 +75,8 @@ else
private string name = string.Empty;
private List<UserRole> users;
private int userid = -1;
private string effectivedate = string.Empty;
private string expirydate = string.Empty;
private DateTime? effectivedate = null;
private DateTime? expirydate = null;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -89,7 +89,10 @@ else
Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name;
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
users = users.Where(item => item.Role.Name == RoleNames.Registered).ToList();
users = users
.Where(u => u.Role.Name == RoleNames.Registered)
.OrderBy(u => u.User.DisplayName)
.ToList();
await GetUserRoles();
}
catch (Exception ex)
@ -122,23 +125,8 @@ else
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
@ -146,24 +134,8 @@ else
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
if (string.IsNullOrEmpty(effectivedate))
{
userrole.EffectiveDate = null;
}
else
{
userrole.EffectiveDate = DateTime.Parse(effectivedate);
}
if (string.IsNullOrEmpty(expirydate))
{
userrole.ExpiryDate = null;
}
else
{
userrole.ExpiryDate = DateTime.Parse(expirydate);
}
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole);
}

View File

@ -103,6 +103,21 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
</td>
<td>
<select id="defaultAdminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
@ -127,11 +142,16 @@
</tr>
</table>
<Section Name="SMTP" ResourceKey="SMTPSettings">
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<table class="table table-borderless">
<tr>
<td colspan="2">
@Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In the Scheduled Jobs"]
</td>
</tr>
<tr>
<td>
<Label For="host" HelpText="Enter the host name of the server" ResourceKey="Host">Host: </Label>
<Label For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
</td>
<td>
<input id="host" class="form-control" @bind="@_smtphost" />
@ -139,7 +159,7 @@
</tr>
<tr>
<td>
<Label For="port" HelpText="Enter the port number for the server" ResourceKey="Port">Port: </Label>
<Label For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
</td>
<td>
<input id="port" class="form-control" @bind="@_smtpport" />
@ -147,15 +167,18 @@
</tr>
<tr>
<td>
<Label For="enabledSSl" HelpText="Specifiy if SSL is enabled for your server" ResourceKey="UseSsl">SSL Enabled: </Label>
<Label For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
</td>
<td>
<input id="enabledSSl" class="form-control" @bind="@_smtpssl" />
<select id="enabledSSl" class="form-control" @bind="@_smtpssl">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="username" HelpText="Enter the username for the server" ResourceKey="SmptUsername">Username: </Label>
<Label For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
</td>
<td>
<input id="username" class="form-control" @bind="@_smtpusername" />
@ -163,12 +186,20 @@
</tr>
<tr>
<td>
<Label For="password" HelpText="Enter the password for the server" ResourceKey="SmtpPassword">Password: </Label>
<Label For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</td>
</tr>
<tr>
<td>
<Label For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label>
</td>
<td>
<input id="sender" class="form-control" @bind="@_smtpsender" />
</td>
</tr>
</table>
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
@ -205,7 +236,6 @@
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
@ -229,12 +259,14 @@
private string _themetype = "-";
private string _layouttype = "-";
private string _containertype = "-";
private string _admincontainertype = "-";
private string _allowregistration;
private string _smtphost = string.Empty;
private string _smtpport = string.Empty;
private string _smtpssl = string.Empty;
private string _smtpssl = "False";
private string _smtpusername = string.Empty;
private string _smtppassword = string.Empty;
private string _smtpsender = string.Empty;
private string _pwaisenabled;
private int _pwaappiconfileid = -1;
private FileManager _pwaappiconfilemanager;
@ -282,14 +314,16 @@
_layouttype = site.DefaultLayoutType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = site.DefaultContainerType;
_admincontainertype = site.AdminContainerType;
_allowregistration = site.AllowRegistration.ToString();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
_smtpport = SettingService.GetSetting(settings, "SMTPPort", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", string.Empty);
_smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "False");
_smtpusername = SettingService.GetSetting(settings, "SMTPUsername", string.Empty);
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_pwaisenabled = site.PwaIsEnabled.ToString();
@ -348,6 +382,7 @@
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
@ -388,6 +423,7 @@
site.DefaultThemeType = _themetype;
site.DefaultLayoutType = (_layouttype == "-" ? string.Empty : _layouttype);
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
@ -435,11 +471,12 @@
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Saved {Site}", site);
await logger.LogInformation("Site Settings Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl());
AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success);
}
}
else

View File

@ -78,6 +78,21 @@ else
</select>
</td>
</tr>
<tr>
<td>
<Label For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
</td>
<td>
<select id="adminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
@ -225,6 +240,7 @@ else
private string _themetype = "-";
private string _layouttype = "-";
private string _containertype = "-";
private string _admincontainertype = "";
private string _sitetemplatetype = "-";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -278,6 +294,7 @@ else
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
@ -378,6 +395,7 @@ else
config.DefaultTheme = _themetype;
config.DefaultLayout = _layouttype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.SiteTemplate = _sitetemplatetype;
ShowProgressIndicator();

View File

@ -18,14 +18,6 @@
<input id="name" class="form-control" @bind="@_name" />
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="Enter the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</td>
</tr>
<tr>
<td>
<Label For="alias" HelpText="Enter the alias for the server" ResourceKey="Aliases">Aliases: </Label>
@ -86,6 +78,21 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
</td>
<td>
<select id="defaultAdminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Has this site been deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
@ -97,6 +104,23 @@
</select>
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="The tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</td>
</tr>
<tr>
<td>
<Label For="connectionstring" HelpText="The database connection string" ResourceKey="ConnectionString">Connection String: </Label>
</td>
<td>
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="3" readonly></textarea>
</td>
</tr>
</table>
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button>
@ -114,13 +138,12 @@
private List<ThemeControl> _containers = new List<ThemeControl>();
private Alias _alias;
private string _name = string.Empty;
private List<Tenant> _tenantList;
private string _tenant = string.Empty;
private List<Alias> _aliasList;
private string _urls = string.Empty;
private string _themetype;
private string _layouttype;
private string _containertype;
private string _containertype = "-";
private string _admincontainertype = "-";
private string _createdby;
private DateTime _createdon;
private string _modifiedby;
@ -128,6 +151,8 @@
private string _deletedby;
private DateTime? _deletedon;
private string _isdeleted;
private string _tenant = string.Empty;
private string _connectionstring = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -144,8 +169,6 @@
if (site != null)
{
_name = site.Name;
_tenantList = await TenantService.GetTenantsAsync();
_tenant = _tenantList.Find(item => item.TenantId == site.TenantId).Name;
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{
@ -158,6 +181,7 @@
_layouttype = site.DefaultLayoutType;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = site.DefaultContainerType;
_admincontainertype = site.AdminContainerType;
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy;
@ -166,6 +190,14 @@
_deletedon = site.DeletedOn;
_isdeleted = site.IsDeleted.ToString();
List<Tenant> tenants = await TenantService.GetTenantsAsync();
Tenant tenant = tenants.Find(item => item.TenantId == site.TenantId);
if (tenant != null)
{
_tenant = tenant.Name;
_connectionstring = tenant.DBConnectionString;
}
_initialized = true;
}
}
@ -193,6 +225,7 @@
}
_layouttype = "-";
_containertype = "-";
_admincontainertype = "";
StateHasChanged();
}
catch (Exception ex)
@ -228,6 +261,7 @@
site.DefaultThemeType = _themetype;
site.DefaultLayoutType = _layouttype ?? string.Empty;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.IsDeleted = (_isdeleted == null || Boolean.Parse(_isdeleted));
site = await SiteService.UpdateSiteAsync(site);

View File

@ -1,86 +0,0 @@
@namespace Oqtane.Modules.Admin.Tenants
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject ITenantService TenantService
@inject IStringLocalizer<Edit> Localizer
<table class="table table-borderless">
<tr>
<td>
<Label For="name" HelpText="The name of the tenant" ResourceKey="Name">Name: </Label>
</td>
<td>
@if (name == TenantNames.Master)
{
<input id="name" class="form-control" @bind="@name" readonly />
}
else
{
<input id="name" class="form-control" @bind="@name" />
}
</td>
</tr>
<tr>
<td>
<Label For="connectionstring" HelpText="The database connection string" ResourceKey="ConnectionString">Connection String: </Label>
</td>
<td>
<textarea id="connectionstring" class="form-control" @bind="@connectionstring" rows="3" readonly></textarea>
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveTenant">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
@code {
private int tenantid;
private string name = string.Empty;
private string connectionstring = string.Empty;
private string schema = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
{
try
{
tenantid = Int32.Parse(PageState.QueryString["id"]);
var tenant = await TenantService.GetTenantAsync(tenantid);
if (tenant != null)
{
name = tenant.Name;
connectionstring = tenant.DBConnectionString;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", tenantid, ex.Message);
AddModuleMessage(Localizer["Error Loading Tenant"], MessageType.Error);
}
}
private async Task SaveTenant()
{
try
{
connectionstring = connectionstring.Replace("\\\\", "\\");
var tenant = await TenantService.GetTenantAsync(tenantid);
if (tenant != null)
{
tenant.Name = name;
tenant.DBConnectionString = connectionstring;
await TenantService.UpdateTenantAsync(tenant);
await logger.LogInformation("Tenant Saved {TenantId}", tenantid);
NavigationManager.NavigateTo(NavigateUrl());
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Tenant {TenantId} {Error}", tenantid, ex.Message);
AddModuleMessage(Localizer["Error Saving Tenant"], MessageType.Error);
}
}
}

View File

@ -1,68 +0,0 @@
@namespace Oqtane.Modules.Admin.Tenants
@inherits ModuleBase
@inject ITenantService TenantService
@inject IAliasService AliasService
@inject IStringLocalizer<Index> Localizer
@if (tenants == null)
{
<p><em>@Localizer["Loading..."]</em></p>
}
else
{
<Pager Items="@tenants">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th>
</Header>
<Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.TenantId.ToString())" ResourceKey="EditTenant" /></td>
<td><ActionDialog Header="Delete Tenant" Message="@Localizer["Are You Sure You Wish To Delete The {0} Tenant?", context.Name]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTenant(context))" Disabled="@(context.Name == TenantNames.Master)" ResourceKey="DeleteTenant" /></td>
<td>@context.Name</td>
</Row>
</Pager>
}
@code {
private List<Tenant> tenants;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnParametersSetAsync()
{
tenants = await TenantService.GetTenantsAsync();
}
private async Task DeleteTenant(Tenant Tenant)
{
try
{
string message = string.Empty;
var aliases = await AliasService.GetAliasesAsync();
foreach (var alias in aliases)
{
if (alias.TenantId == Tenant.TenantId)
{
message += ", " + alias.Name;
}
}
if (string.IsNullOrEmpty(message))
{
await TenantService.DeleteTenantAsync(Tenant.TenantId);
await logger.LogInformation("Tenant Deleted {Tenant}", Tenant);
StateHasChanged();
}
else
{
AddModuleMessage(Localizer["Tenant Cannot Be Deleted Until The Following Sites Are Deleted: {0}", message.Substring(2)], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Tenant {Tenant} {Error}", Tenant, ex.Message);
AddModuleMessage(Localizer["Error Deleting Tenant"], MessageType.Error);
}
}
}

View File

@ -49,7 +49,7 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
try
{
@ -101,7 +101,8 @@ else
try
{
await ThemeService.DeleteThemeAsync(Theme.ThemeName);
AddModuleMessage(Localizer["Theme Deleted Successfully. You Must <a href=\"{0}\">Restart</a> Your Application To Apply These Changes.", NavigateUrl("admin/system")], MessageType.Success);
AddModuleMessage(Localizer["Theme Deleted Successfully"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{

View File

@ -48,26 +48,12 @@
private async Task Send()
{
var notification = new Notification();
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
{
notification.SiteId = PageState.Site.SiteId;
notification.FromUserId = PageState.User.UserId;
notification.FromDisplayName = PageState.User.DisplayName;
notification.FromEmail = PageState.User.Email;
notification.ToUserId = user.UserId;
notification.ToDisplayName = user.DisplayName;
notification.ToEmail = user.Email;
notification.Subject = subject;
notification.Body = body;
notification.ParentId = null;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
notification.SendOn = DateTime.UtcNow;
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
@ -79,7 +65,7 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message);
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error);
}
}

View File

@ -176,26 +176,12 @@
private async Task Send()
{
var notification = new Notification();
try
{
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
if (user != null)
{
notification.SiteId = PageState.Site.SiteId;
notification.FromUserId = PageState.User.UserId;
notification.FromDisplayName = PageState.User.DisplayName;
notification.FromEmail = PageState.User.Email;
notification.ToUserId = user.UserId;
notification.ToDisplayName = user.DisplayName;
notification.ToEmail = user.Email;
notification.Subject = subject;
notification.Body = body;
notification.ParentId = notificationid;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
notification.SendOn = DateTime.UtcNow;
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
@ -207,7 +193,7 @@
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message);
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error);
}
}

View File

@ -3,6 +3,43 @@
@typeparam TableItem
<p>
@if (Toolbar == "Top")
{
<div class="mx-auto text-center">
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
}
@if (_page > _maxPages)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))>
@pager
</button>
}
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
}
@if (_endPage < _pages)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<span class="btn btn-link disabled">Page @_page of @_pages</span>
}
</div>
}
@if (Format == "Table")
{
<table class="@Class">
@ -35,14 +72,20 @@
}
</div>
}
@if (Toolbar == "Bottom")
{
<div class="mx-auto text-center">
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
}
@if (_page > _maxPages)
{
<button class="btn btn-secondary" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
{
var pager = i;
@ -50,17 +93,22 @@
@pager
</button>
}
<button class="btn btn-secondary" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
}
@if (_endPage < _pages)
{
<button class="btn btn-secondary" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
}
@if (_endPage > 1)
{
<span class="btn btn-link disabled">Page @_page of @_pages</span>
}
</div>
}
</p>
@code {
@ -74,6 +122,9 @@
[Parameter]
public string Format { get; set; }
[Parameter]
public string Toolbar { get; set; }
[Parameter]
public RenderFragment Header { get; set; }
@ -104,6 +155,11 @@
Format = "Table";
}
if (string.IsNullOrEmpty(Toolbar))
{
Toolbar = "Top";
}
if (string.IsNullOrEmpty(Class))
{
if (Format == "Table")

View File

@ -16,13 +16,14 @@
<div class="d-flex">
<hr class="app-rule" />
</div>
<div class="collapse" id="@Name">
<div class="collapse @_show" id="@Name">
@ChildContent
</div>
@code {
private string _heading = string.Empty;
private string _expanded = string.Empty;
private string _show = string.Empty;
[Parameter]
public RenderFragment ChildContent { get; set; }
@ -40,6 +41,7 @@
{
_heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name;
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded : "false";
if (_expanded == "true") { _show = "show"; }
}
protected override void OnParametersSet()

View File

@ -1,7 +1,7 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleControlBase
<CascadingValue Value="this">
<CascadingValue Value="this" IsFixed="true">
<div class="container-fluid">
<div class="form-group">
<ul class="nav nav-tabs" role="tablist">

View File

@ -67,6 +67,7 @@ namespace Oqtane.Client
builder.Services.AddScoped<ISqlService, SqlService>();
builder.Services.AddScoped<ISystemService, SystemService>();
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
builder.Services.AddScoped<ILanguageService, LanguageService>();
await LoadClientAssemblies(httpClient);

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
@ -37,7 +37,7 @@ namespace Oqtane.Services
{
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{
folderPath = Utilities.PathCombine(folderPath,"\\");
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
}
var path = WebUtility.UrlEncode(folderPath);

View File

@ -1,4 +1,4 @@
using Oqtane.Models;
using Oqtane.Models;
using System.Threading.Tasks;
using System.Linq;
using System.Net.Http;
@ -37,7 +37,7 @@ namespace Oqtane.Services
{
if (!(folderPath.EndsWith(System.IO.Path.DirectorySeparatorChar) || folderPath.EndsWith(System.IO.Path.AltDirectorySeparatorChar)))
{
folderPath = Utilities.PathCombine(folderPath, "\\");
folderPath = Utilities.PathCombine(folderPath, System.IO.Path.DirectorySeparatorChar.ToString());
}
var path = WebUtility.UrlEncode(folderPath);

View File

@ -0,0 +1,17 @@
using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Oqtane.Services
{
public interface ILanguageService
{
Task<List<Language>> GetLanguagesAsync(int siteId);
Task<Language> GetLanguageAsync(int languageId);
Task<Language> AddLanguageAsync(Language language);
Task DeleteLanguageAsync(int languageId);
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Services
{
public class LanguageService : ServiceBase, ILanguageService
{
private readonly SiteState _siteState;
public LanguageService(HttpClient http, SiteState siteState) : base(http)
{
_siteState = siteState;
}
private string Apiurl => CreateApiUrl(_siteState.Alias, "Language");
public async Task<List<Language>> GetLanguagesAsync(int siteId)
{
var languages = await GetJsonAsync<List<Language>>($"{Apiurl}?siteid={siteId}");
return languages?.OrderBy(l => l.Name).ToList() ?? Enumerable.Empty<Language>().ToList();
}
public async Task<Language> GetLanguageAsync(int languageId)
=> await GetJsonAsync<Language>($"{Apiurl}/{languageId}");
public async Task<Language> AddLanguageAsync(Language language)
=> await PostJsonAsync<Language>(Apiurl, language);
public async Task DeleteLanguageAsync(int languageId)
=> await DeleteAsync($"{Apiurl}/{languageId}");
}
}

View File

@ -296,7 +296,7 @@
public bool ShowLanguageSwitcher { get; set; } = true;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{
@ -485,10 +485,10 @@
switch (location)
{
case "Add":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "");
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "cp=" + PageState.Page.PageId);
break;
case "Edit":
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString());
url = EditUrl(PageState.Page.Path, module.ModuleId, location, "id=" + PageState.Page.PageId.ToString() + "&cp=" + PageState.Page.PageId);
break;
}
}

View File

@ -0,0 +1,12 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@if (!string.IsNullOrWhiteSpace(Value))
{
<span class="@Value" aria-hidden="true"></span>
}
@code {
[Parameter()]
public string Value { get; set; }
}

View File

@ -1,9 +1,9 @@
@namespace Oqtane.Themes.Controls
@inherits ThemeControlBase
@using System.Globalization
@using Microsoft.AspNetCore.Localization;
@using Microsoft.AspNetCore.Localization
@using Oqtane.Models
@inject ILocalizationService LocalizationService
@inject ILanguageService LanguageService
@inject NavigationManager NavigationManager
@if (_supportedCultures?.Count() > 1)
@ -26,7 +26,8 @@
protected override async Task OnParametersSetAsync()
{
_supportedCultures = await LocalizationService.GetCulturesAsync();
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
}
private async Task SetCultureAsync(string culture)

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Themes.Controls
@inherits MenuBase
@if (MenuPages.Any())
@ -10,35 +11,7 @@
</span>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="navbar-nav mr-auto">
@foreach (var p in MenuPages)
{
if (p.PageId == PageState.Page.PageId)
{
<li class="nav-item active">
<a class="nav-link" href="@GetUrl(p)" target="@GetTarget(p)" >
@if (p.Icon != string.Empty)
{
<span class="@p.Icon" aria-hidden="true"></span>
}
@p.Name<span class="sr-only">(current)</span>
</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link" href="@GetUrl(p)" target="@GetTarget(p)" >
@if (p.Icon != string.Empty)
{
<span class="@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
}
</ul>
<MenuItemsHorizontal ParentPage="null" Pages="MenuPages" />
</div>
</div>
}

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Components;
using Oqtane.Models;
using Oqtane.UI;
namespace Oqtane.Themes.Controls
{
public abstract class MenuItemsBase : MenuBase
{
[Parameter()]
public Page ParentPage { get; set; }
[Parameter()]
public IEnumerable<Page> Pages { get; set; }
protected IEnumerable<Page> GetChildPages()
{
return Pages
.Where(e => e.ParentId == ParentPage?.PageId)
.OrderBy(e => e.Order)
.AsEnumerable();
}
}
}

View File

@ -0,0 +1,77 @@
@namespace Oqtane.Themes.Controls
@inherits MenuItemsBase
@if (ParentPage != null)
{
<div class="dropdown-menu" aria-labelledby="@($"navbarDropdown{ParentPage.PageId}")">
@foreach (var childPage in GetChildPages())
{
if (childPage.PageId == PageState.Page.PageId)
{
<a class="dropdown-item active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
}
else
{
<a class="dropdown-item" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name
</a>
}
}
</div>
}
else
{
<ul class="navbar-nav mr-auto">
@foreach (var childPage in GetChildPages())
{
if (!Pages.Any(e => e.ParentId == childPage.PageId))
{
if (childPage.PageId == PageState.Page.PageId)
{
<li class="nav-item active">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name
</a>
</li>
}
}
else
{
if (childPage.PageId == PageState.Page.PageId)
{
<li class="nav-item dropdown active">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />
</li>
}
else
{
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" />
@childPage.Name
</a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />
</li>
}
}
}
</ul>
}

View File

@ -0,0 +1,61 @@
@namespace Oqtane.Themes.Controls
@inherits MenuItemsBase
@if (ParentPage != null)
{
foreach (var childPage in GetChildPages())
{
if (childPage.PageId == PageState.Page.PageId)
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
}
else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name
</a>
</li>
}
if (Pages.Any(e => e.ParentId == childPage.PageId))
{
<MenuItemsVertical ParentPage="childPage" Pages="Pages" />
}
}
}
else
{
<ul class="nav flex-column">
@foreach (var childPage in GetChildPages())
{
if (childPage.PageId == PageState.Page.PageId)
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
}
else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name
</a>
</li>
}
if (Pages.Any(e => e.ParentId == childPage.PageId))
{
<MenuItemsVertical ParentPage="childPage" Pages="Pages" />
}
}
</ul>
}

View File

@ -10,25 +10,7 @@
</span>
<div class="app-menu">
<div class="collapse navbar-collapse" id="Menu">
<ul class="nav flex-column">
@foreach (var p in MenuPages)
{
<li class="nav-item px-3">
<a href="@GetUrl(p)" class="nav-link" style="padding-left:@((p.Level + 1) * 15)px !important;" target="@GetTarget(p)">
@if (p.HasChildren)
{
<i class="oi oi-chevron-right"></i>
}
@if (p.Icon != string.Empty)
{
<span class="@p.Icon" aria-hidden="true"></span>
}
@p.Name
</a>
</li>
}
</ul>
<MenuItemsVertical ParentPage="null" Pages="MenuPages" />
</div>
</div>
}

View File

@ -88,5 +88,10 @@ namespace Oqtane.Themes
{
return Utilities.ContentUrl(PageState.Alias, fileid);
}
public string ContentUrl(int fileid, bool asAttachment)
{
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment);
}
}
}

View File

@ -1,6 +1,6 @@
@namespace Oqtane.UI
@namespace Oqtane.UI
<CascadingValue Value="@_moduleState">
<CascadingValue Value="@_moduleState" IsFixed="true">
@DynamicComponent
</CascadingValue>
@ -21,7 +21,7 @@
string container = _moduleState.ContainerType;
if (PageState.ModuleId != -1 && _moduleState.UseAdminContainer)
{
container = Constants.DefaultAdminContainer;
container = (!string.IsNullOrEmpty(PageState.Site.AdminContainerType)) ? PageState.Site.AdminContainerType : Constants.DefaultAdminContainer;
}
DynamicComponent = builder =>

View File

@ -2,7 +2,7 @@
@inject IStringLocalizer<ModuleInstance> Localizer
<ModuleMessage Message="@_message" Type="@_messagetype" />
<CascadingValue Value="this">
<CascadingValue Value="this" IsFixed="true">
@DynamicComponent
</CascadingValue>
@if (_progressindicator)

View File

@ -64,21 +64,24 @@
private async Task InitializePwa(Interop interop)
{
string url = NavigationManager.BaseUri;
url = url.Substring(0, url.Length - 1);
// dynamically create manifest.json and add to page
string manifest = "setTimeout(() => { " +
"var manifest = { " +
"\"name\": \"" + PageState.Site.Name + "\", " +
"\"short_name\": \"" + PageState.Site.Name + "\", " +
"\"start_url\": \"/\", " +
"\"start_url\": \"" + url + "/\", " +
"\"display\": \"standalone\", " +
"\"background_color\": \"#fff\", " +
"\"description\": \"" + PageState.Site.Name + "\", " +
"\"icons\": [{ " +
"\"src\": \"" + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaAppIconFileId.Value) + "\", " +
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaAppIconFileId.Value) + "\", " +
"\"sizes\": \"192x192\", " +
"\"type\": \"image/png\" " +
"}, { " +
"\"src\": \"" + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaSplashIconFileId.Value) + "\", " +
"\"src\": \"" + url + Utilities.ContentUrl(PageState.Alias, PageState.Site.PwaSplashIconFileId.Value) + "\", " +
"\"sizes\": \"512x512\", " +
"\"type\": \"image/png\" " +
"}] " +

View File

@ -50,29 +50,13 @@ namespace Oqtane.Controllers
[HttpGet("name/{**name}")]
public Alias Get(string name, string sync)
{
List<Alias> aliases = _aliases.GetAliases().ToList(); // cached
Alias alias = null;
if (_accessor.HttpContext != null)
{
name = (name == "~") ? "" : name;
name = _accessor.HttpContext.Request.Host.Value + "/" + WebUtility.UrlDecode(name);
var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// iterate segments in reverse order
for (int i = segments.Length; i > 0; i--)
{
name = string.Join("/", segments, 0, i);
alias = aliases.Find(item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
if (alias != null)
{
break; // found a matching alias
}
}
}
if (alias == null && aliases.Any())
{
// use first alias if name does not exist
alias = aliases.FirstOrDefault();
alias = _aliases.GetAlias(name);
}
// get sync events
@ -81,6 +65,7 @@ namespace Oqtane.Controllers
alias.SyncDate = DateTime.UtcNow;
alias.SyncEvents = _syncManager.GetSyncEvents(alias.TenantId, DateTime.ParseExact(sync, "yyyyMMddHHmmssfff", CultureInfo.InvariantCulture));
}
return alias;
}

View File

@ -142,13 +142,13 @@ namespace Oqtane.Controllers
Models.File _file = _files.GetFile(id, false);
if (_file.Name != file.Name || _file.FolderId != file.FolderId)
{
string folderpath = GetFolderPath(file.Folder);
string folderpath = _folders.GetFolderPath(file.Folder);
if (!Directory.Exists(folderpath))
{
Directory.CreateDirectory(folderpath);
}
System.IO.File.Move(Path.Combine(GetFolderPath(_file.Folder), _file.Name), Path.Combine(folderpath, file.Name));
System.IO.File.Move(_files.GetFilePath(_file), Path.Combine(folderpath, file.Name));
}
file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", "");
@ -177,7 +177,7 @@ namespace Oqtane.Controllers
{
_files.DeleteFile(id);
string filepath = Path.Combine(GetFolderPath(file.Folder), file.Name);
string filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
System.IO.File.Delete(filepath);
@ -213,7 +213,7 @@ namespace Oqtane.Controllers
return file;
}
string folderPath = GetFolderPath(folder);
string folderPath = _folders.GetFolderPath(folder);
CreateDirectory(folderPath);
string filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
@ -280,7 +280,7 @@ namespace Oqtane.Controllers
if (virtualFolder != null &&
_userPermissions.IsAuthorized(User, PermissionNames.Edit, virtualFolder.Permissions))
{
folderPath = GetFolderPath(virtualFolder);
folderPath = _folders.GetFolderPath(virtualFolder);
}
}
else
@ -291,7 +291,7 @@ namespace Oqtane.Controllers
}
}
if (folderPath != "")
if (!String.IsNullOrEmpty(folderPath))
{
CreateDirectory(folderPath);
using (var stream = new FileStream(Path.Combine(folderPath, file.FileName), FileMode.Create))
@ -472,7 +472,7 @@ namespace Oqtane.Controllers
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions))
{
var filepath = Path.Combine(GetFolderPath(file.Folder), file.Name);
var filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath))
{
var result = asAttachment
@ -500,11 +500,6 @@ namespace Oqtane.Controllers
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
}
private string GetFolderPath(Folder folder)
{
return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _tenants.GetTenant().TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path);
}
private string GetFolderPath(string folder)
{
return Utilities.PathCombine(_environment.WebRootPath, folder);

View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Controllers
{
[Route(ControllerRoutes.Default)]
public class LanguageController : Controller
{
private readonly ILanguageRepository _languages;
private readonly ILogManager _logger;
public LanguageController(ILanguageRepository language, ILogManager logger)
{
_languages = language;
_logger = logger;
}
[HttpGet]
public IEnumerable<Language> Get(string siteid) => _languages.GetLanguages(int.Parse(siteid));
[HttpGet("{id}")]
public Language Get(int id) => _languages.GetLanguage(id);
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public Language Post([FromBody] Language language)
{
if (ModelState.IsValid)
{
language = _languages.AddLanguage(language);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Language Added {Language}", language);
}
return language;
}
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public void Delete(int id)
{
_languages.DeleteLanguage(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Language Deleted {LanguageId}", id);
}
}
}

View File

@ -101,13 +101,12 @@ namespace Oqtane.Controllers
public void Delete(int id, int siteid)
{
ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, siteid);
if (moduledefinition != null )
if (moduledefinition != null && Utilities.GetAssemblyName(moduledefinition.ServerManagerType) != "Oqtane.Server")
{
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType) && Utilities.GetAssemblyName(moduledefinition.ServerManagerType) != "Oqtane.Server")
// execute uninstall logic or scripts
if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType))
{
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
// execute uninstall logic
foreach (Tenant tenant in _tenants.GetTenants())
{
try
@ -128,36 +127,47 @@ namespace Oqtane.Controllers
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} For Tenant {Tenant} {Error}", moduledefinition.ModuleDefinitionName, tenant.Name, ex.Message);
}
}
}
// use assets.json to clean up file resources
string assetfilepath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName), "assets.json");
if (System.IO.File.Exists(assetfilepath))
// remove module assets
string assetpath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName));
if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json")))
{
List<string> assets = JsonSerializer.Deserialize<List<string>>(System.IO.File.ReadAllText(assetfilepath));
// use assets.json to clean up file resources
List<string> assets = JsonSerializer.Deserialize<List<string>>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json")));
foreach(string asset in assets)
{
if (System.IO.File.Exists(asset))
// legacy support for assets that were stored as absolute paths
string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset;
if (System.IO.File.Exists(filepath))
{
System.IO.File.Delete(asset);
System.IO.File.Delete(filepath);
}
}
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
}
else
{
// attempt to delete assemblies based on naming convention
foreach(string asset in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + "*.*"))
{
System.IO.File.Delete(asset);
}
_logger.Log(LogLevel.Warning, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}. Please Note That Some Assets May Have Been Missed Due To A Missing Asset Manifest. An Asset Manifest Is Only Created If A Module Is Installed From A Nuget Package.", moduledefinition.Name);
}
// clean up module static resource folder
string folder = Path.Combine(_environment.WebRootPath, Path.Combine("Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)));
if (Directory.Exists(folder))
if (Directory.Exists(assetpath))
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
Directory.Delete(assetpath, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Static Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
}
// remove module definition
_moduleDefinitions.DeleteModuleDefinition(id, siteid);
_moduleDefinitions.DeleteModuleDefinition(id);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
}
}
}
// POST api/<controller>?moduleid=x
[HttpPost]

View File

@ -57,28 +57,44 @@ namespace Oqtane.Controllers
Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault();
if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != "Oqtane.Client")
{
// use assets.json to clean up file resources
string assetfilepath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName), "assets.json");
if (System.IO.File.Exists(assetfilepath))
// remove theme assets
string assetpath = Path.Combine(_environment.WebRootPath, "Themes", Utilities.GetTypeName(theme.ThemeName));
if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json")))
{
List<string> assets = JsonSerializer.Deserialize<List<string>>(System.IO.File.ReadAllText(assetfilepath));
// use assets.json to clean up file resources
List<string> assets = JsonSerializer.Deserialize<List<string>>(System.IO.File.ReadAllText(Path.Combine(assetpath, "assets.json")));
foreach (string asset in assets)
{
if (System.IO.File.Exists(asset))
// legacy support for assets that were stored as absolute paths
string filepath = (asset.StartsWith("\\")) ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset;
if (System.IO.File.Exists(filepath))
{
System.IO.File.Delete(asset);
System.IO.File.Delete(filepath);
}
}
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Assets Removed For {ThemeName}", theme.ThemeName);
}
else
{
// attempt to delete assemblies based on naming convention
foreach (string asset in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(theme.ThemeName) + "*.*"))
{
System.IO.File.Delete(asset);
}
_logger.Log(LogLevel.Warning, this, LogFunction.Delete, "Theme Assets Removed For {ThemeName}. Please Note That Some Assets May Have Been Missed Due To A Missing Asset Manifest. An Asset Manifest Is Only Created If A Theme Is Installed From A Nuget Package.", theme.ThemeName);
}
// clean up theme static resource folder
string folder = Path.Combine(_environment.WebRootPath, "Themes" , Utilities.GetTypeName(theme.ThemeName));
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Resource Folder Removed For {ThemeName}", theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Static Resource Folder Removed For {ThemeName}", theme.ThemeName);
}
// remove theme
_themes.DeleteTheme(theme.ThemeName);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Theme Removed For {ThemeName}", theme.ThemeName);
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
@ -146,20 +146,10 @@ namespace Oqtane.Controllers
newUser = _users.AddUser(user);
if (!verified)
{
Notification notification = new Notification();
notification.SiteId = user.SiteId;
notification.FromUserId = null;
notification.ToUserId = newUser.UserId;
notification.ToEmail = newUser.Email;
notification.Subject = "User Account Verification";
string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
notification.Body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
notification.SendOn = DateTime.UtcNow;
string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, null, newUser, "User Account Verification", body, null);
_notifications.AddNotification(notification);
}
@ -379,20 +369,10 @@ namespace Oqtane.Controllers
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null)
{
Notification notification = new Notification();
notification.SiteId = user.SiteId;
notification.FromUserId = null;
notification.ToUserId = user.UserId;
notification.ToEmail = "";
notification.Subject = "User Password Reset";
string token = await _identityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + _tenants.GetAlias().Name + "/reset?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token);
notification.Body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.UtcNow;
notification.IsDelivered = false;
notification.DeliveredOn = null;
notification.SendOn = DateTime.UtcNow;
string body = "Dear " + user.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!";
var notification = new Notification(user.SiteId, null, user, "User Password Reset", body, null);
_notifications.AddNotification(notification);
_logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", user.Username);
}

View File

@ -178,7 +178,7 @@ namespace Microsoft.Extensions.DependencyInjection
private static Assembly ResolveDependencies(AssemblyLoadContext context, AssemblyName name)
{
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + "\\" + name.Name + ".dll";
var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) + Path.DirectorySeparatorChar + name.Name + ".dll";
if (File.Exists(assemblyPath))
{
return context.LoadFromStream(new MemoryStream(File.ReadAllBytes(assemblyPath)));

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
@ -244,14 +244,6 @@ namespace Oqtane.Infrastructure
db.Tenant.Add(tenant);
db.SaveChanges();
_cache.Remove("tenants");
if (install.TenantName == TenantNames.Master)
{
var job = new Job { Name = "Notification Job", JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server", Frequency = "m", Interval = 1, StartDate = null, EndDate = null, IsEnabled = false, IsStarted = false, IsExecuting = false, NextExecution = null, RetentionHistory = 10, CreatedBy = "", CreatedOn = DateTime.UtcNow, ModifiedBy = "", ModifiedOn = DateTime.UtcNow };
db.Job.Add(job);
db.SaveChanges();
_cache.Remove("jobs");
}
}
else
{

View File

@ -28,13 +28,13 @@ namespace Oqtane.Infrastructure
public void InstallPackages(string folders)
{
if (!InstallPackages(folders, _environment.WebRootPath))
if (!InstallPackages(folders, _environment.WebRootPath, _environment.ContentRootPath))
{
// error installing packages
}
}
public static bool InstallPackages(string folders, string webRootPath)
public static bool InstallPackages(string folders, string webRootPath, string contentRootPath)
{
bool install = false;
string binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
@ -79,6 +79,7 @@ namespace Oqtane.Infrastructure
if (frameworkversion == "" || Version.Parse(Constants.Version).CompareTo(Version.Parse(frameworkversion)) >= 0)
{
List<string> assets = new List<string>();
bool manifest = false;
// module and theme packages must be in form of name.1.0.0.nupkg
string name = Path.GetFileNameWithoutExtension(packagename);
@ -91,36 +92,41 @@ namespace Oqtane.Infrastructure
string foldername = Path.GetDirectoryName(entry.FullName).Split(Path.DirectorySeparatorChar)[0];
string filename = Path.GetFileName(entry.FullName);
if (!manifest && filename == "assets.json")
{
manifest = true;
}
switch (foldername)
{
case "lib":
filename = Path.Combine(binFolder, filename);
ExtractFile(entry, filename);
assets.Add(filename);
assets.Add(filename.Replace(contentRootPath, ""));
break;
case "wwwroot":
filename = Path.Combine(webRootPath, Utilities.PathCombine(entry.FullName.Replace("wwwroot/", "").Split('/')));
ExtractFile(entry, filename);
assets.Add(filename);
assets.Add(filename.Replace(contentRootPath, ""));
break;
case "runtimes":
var destSubFolder = Path.GetDirectoryName(entry.FullName);
filename = Path.Combine(binFolder, destSubFolder, filename);
ExtractFile(entry, filename);
assets.Add(filename);
assets.Add(filename.Replace(contentRootPath, ""));
break;
}
}
// save list of assets
if (assets.Count != 0)
// save dynamic list of assets
if (!manifest && assets.Count != 0)
{
string assetfilepath = Path.Combine(webRootPath, folder, name, "assets.json");
if (File.Exists(assetfilepath))
string manifestpath = Path.Combine(webRootPath, folder, name, "assets.json");
if (File.Exists(manifestpath))
{
File.Delete(assetfilepath);
File.Delete(manifestpath);
}
File.WriteAllText(assetfilepath, JsonSerializer.Serialize(assets));
File.WriteAllText(manifestpath, JsonSerializer.Serialize(assets));
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -25,6 +25,15 @@ namespace Oqtane.Infrastructure
// abstract method must be overridden
public abstract string ExecuteJob(IServiceProvider provider);
// public properties which can be overridden and are used during auto registration of job
public string Name { get; set; } = "";
public string Frequency { get; set; } = "d"; // day
public int Interval { get; set; } = 1;
public DateTime? StartDate { get; set; } = null;
public DateTime? EndDate { get; set; } = null;
public int RetentionHistory { get; set; } = 10;
public bool IsEnabled { get; set; } = false;
protected async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield(); // required so that this method does not block startup
@ -82,7 +91,15 @@ namespace Oqtane.Infrastructure
// execute the job
try
{
log.Notes = ExecuteJob(scope.ServiceProvider);
var notes = "";
var tenants = scope.ServiceProvider.GetRequiredService<ITenantRepository>();
var siteState = scope.ServiceProvider.GetRequiredService<SiteState>();
foreach (var tenant in tenants.GetTenants())
{
siteState.Alias = new Alias { TenantId = tenant.TenantId };
notes += ExecuteJob(scope.ServiceProvider);
}
log.Notes = notes;
log.Succeeded = true;
}
catch (Exception ex)
@ -153,20 +170,40 @@ namespace Oqtane.Infrastructure
// set IsExecuting to false in case this job was forcefully terminated previously
using (var scope = _serviceScopeFactory.CreateScope())
{
string jobType = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
Job job = jobs.GetJobs().Where(item => item.JobType == jobType).FirstOrDefault();
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
if (job != null)
{
job.IsStarted = true;
job.IsExecuting = false;
jobs.UpdateJob(job);
}
}
}
catch
else
{
// can occur during the initial installation as there is no DBContext
// auto registration - does not run on initial installation but will run after restart
job = new Job { JobType = jobTypeName };
// optional properties
var jobType = Type.GetType(jobTypeName);
var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase;
if (jobObject.Name != "")
{
job.Name = jobObject.Name;
}
else
{
job.Name = Utilities.GetTypeName(job.JobType);
}
job.Frequency = jobObject.Frequency;
job.Interval = jobObject.Interval;
job.StartDate = jobObject.StartDate;
job.EndDate = jobObject.EndDate;
job.RetentionHistory = jobObject.RetentionHistory;
job.IsEnabled = jobObject.IsEnabled;
job.IsStarted = true;
job.IsExecuting = false;
jobs.AddJob(job);
}
}
_executingTask = ExecuteAsync(_cancellationTokenSource.Token);
@ -175,6 +212,11 @@ namespace Oqtane.Infrastructure
{
return _executingTask;
}
}
catch
{
// can occur during the initial installation because this method is called during startup and the database has not yet been created
}
return Task.CompletedTask;
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -14,31 +14,26 @@ namespace Oqtane.Infrastructure
{
// JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server"
public NotificationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) {}
public NotificationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Notification Job";
Frequency = "m"; // minute
Interval = 1;
IsEnabled = false;
}
// job is executed for each tenant in installation
public override string ExecuteJob(IServiceProvider provider)
{
string log = "";
// iterate through tenants in this installation
List<int> tenants = new List<int>();
var aliasRepository = provider.GetRequiredService<IAliasRepository>();
List<Alias> aliases = aliasRepository.GetAliases().ToList();
foreach (Alias alias in aliases)
{
if (tenants.Contains(alias.TenantId)) continue;
tenants.Add(alias.TenantId);
// use the SiteState to set the Alias explicitly so the tenant can be resolved
var siteState = provider.GetRequiredService<SiteState>();
siteState.Alias = alias;
// get services which require tenant resolution
// get services
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var userRepository = provider.GetRequiredService<IUserRepository>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
// iterate through sites for this tenant
// iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList();
foreach (Site site in sites)
{
@ -47,7 +42,10 @@ namespace Oqtane.Infrastructure
// get site settings
List<Setting> sitesettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToList();
Dictionary<string, string> settings = GetSettings(sitesettings);
if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "")
if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "" &&
settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" &&
settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" &&
settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "")
{
// construct SMTP Client
var client = new SmtpClient()
@ -63,15 +61,44 @@ namespace Oqtane.Infrastructure
client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
}
// iterate through notifications
// iterate through undelivered notifications
int sent = 0;
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications)
{
// get sender and receiver information if not provided
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
{
var user = userRepository.GetUser(notification.FromUserId.Value);
if (user != null)
{
notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail;
notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName;
}
}
if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null)
{
var user = userRepository.GetUser(notification.ToUserId.Value);
if (user != null)
{
notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail;
notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName;
}
}
// validate recipient
if (string.IsNullOrEmpty(notification.ToEmail))
{
log += "Recipient Missing For NotificationId: " + notification.NotificationId + "<br />";
notification.IsDeleted = true;
notificationRepository.UpdateNotification(notification);
}
else
{
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(settings["SMTPUsername"], site.Name);
mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name);
mailMessage.Subject = notification.Subject;
if (notification.FromUserId != null)
if (!string.IsNullOrEmpty(notification.FromEmail) && !string.IsNullOrEmpty(notification.FromDisplayName))
{
mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n";
}
@ -80,7 +107,7 @@ namespace Oqtane.Infrastructure
mailMessage.Body = "From: " + site.Name + "\n";
}
mailMessage.Body += "Sent: " + notification.CreatedOn + "\n";
if (notification.ToUserId != null)
if (!string.IsNullOrEmpty(notification.ToEmail) && !string.IsNullOrEmpty(notification.ToDisplayName))
{
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n";
@ -108,12 +135,12 @@ namespace Oqtane.Infrastructure
log += ex.Message + "<br />";
}
}
}
log += "Notifications Delivered: " + sent + "<br />";
}
else
{
log += "SMTP Not Configured" + "<br />";
}
log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />";
}
}

View File

@ -2,7 +2,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>7.3</LangVersion>
<Configurations>Debug;Release</Configurations>
<Version>2.0.0</Version>
<Product>Oqtane</Product>
@ -35,6 +34,8 @@
<EmbeddedResource Include="Scripts\Tenant.01.00.02.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.00.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.01.01.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.01.02.sql" />
<EmbeddedResource Include="Scripts\Tenant.02.00.01.03.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.1.0.0.sql" />
<EmbeddedResource Include="Modules\HtmlText\Scripts\HtmlText.Uninstall.sql" />
</ItemGroup>

View File

@ -5,13 +5,38 @@ using Oqtane.Modules;
using Oqtane.Models;
using Oqtane.Themes;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http.Extensions;
using Oqtane.Repository;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Configuration;
namespace Oqtane.Pages
{
public class HostModel : PageModel
{
private IConfiguration _configuration;
private readonly SiteState _state;
private readonly IAliasRepository _aliases;
private readonly ILocalizationManager _localizationManager;
private readonly ILanguageRepository _languages;
public HostModel(
IConfiguration configuration,
SiteState state,
IAliasRepository aliases,
ILocalizationManager localizationManager,
ILanguageRepository languages)
{
_configuration = configuration;
_state = state;
_aliases = aliases;
_localizationManager = localizationManager;
_languages = languages;
}
public string HeadResources = "";
public string BodyResources = "";
@ -24,6 +49,27 @@ namespace Oqtane.Pages
ProcessModuleControls(assembly);
ProcessThemeControls(assembly);
}
// if culture not specified and framework is installed
if (HttpContext.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName] == null && !string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
var uri = new Uri(Request.GetDisplayUrl());
var alias = _aliases.GetAlias(uri.Authority + "/" + uri.LocalPath.Substring(1));
_state.Alias = alias;
// set default language for site if the culture is not supported
var languages = _languages.GetLanguages(alias.SiteId);
if (languages.Any() && languages.All(l => l.Code != CultureInfo.CurrentUICulture.Name))
{
var defaultLanguage = languages.Where(l => l.IsDefault).SingleOrDefault() ?? languages.First();
SetLocalizationCookie(defaultLanguage.Code);
}
else
{
SetLocalizationCookie(_localizationManager.GetDefaultCulture());
}
}
}
private void ProcessHostResources(Assembly assembly)
@ -134,5 +180,12 @@ namespace Oqtane.Pages
return "";
}
}
private void SetLocalizationCookie(string culture)
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
@ -48,6 +48,27 @@ namespace Oqtane.Repository
return _db.Alias.Find(aliasId);
}
public Alias GetAlias(string name)
{
Alias alias = null;
List<Alias> aliases = GetAliases().ToList();
var segments = name.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// iterate segments in reverse order
for (int i = segments.Length; i > 0; i--)
{
name = string.Join("/", segments, 0, i);
alias = aliases.Find(item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
if (alias != null)
{
break; // found a matching alias
}
}
return alias;
}
public void DeleteAlias(int aliasId)
{
Alias alias = _db.Alias.Find(aliasId);

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
@ -10,20 +10,24 @@ namespace Oqtane.Repository
{
public class DBContextBase : IdentityUserContext<IdentityUser>
{
private Tenant _tenant;
private ITenantResolver _tenantResolver;
private IHttpContextAccessor _accessor;
public DBContextBase(ITenantResolver tenantResolver, IHttpContextAccessor accessor)
{
_tenant = tenantResolver.GetTenant();
_tenantResolver = tenantResolver;
_accessor = accessor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_tenant.DBConnectionString
var tenant = _tenantResolver.GetTenant();
if (tenant != null)
{
optionsBuilder.UseSqlServer(tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
);
}
base.OnConfiguring(optionsBuilder);
}

View File

@ -1,18 +1,32 @@
using System;
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
using Microsoft.Extensions.Configuration;
namespace Oqtane.Repository
{
public class MasterDBContext : DbContext
{
private IHttpContextAccessor _accessor;
private IConfiguration _configuration;
public MasterDBContext(DbContextOptions<MasterDBContext> options, IHttpContextAccessor accessor) : base(options)
public MasterDBContext(DbContextOptions<MasterDBContext> options, IHttpContextAccessor accessor, IConfiguration configuration) : base(options)
{
_accessor = accessor;
_configuration = configuration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
);
}
base.OnConfiguring(optionsBuilder);
}
public virtual DbSet<Alias> Alias { get; set; }

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
@ -21,6 +21,8 @@ namespace Oqtane.Repository
public virtual DbSet<Folder> Folder { get; set; }
public virtual DbSet<File> File { get; set; }
public virtual DbSet<Language> Language { get; set; }
public TenantDBContext(ITenantResolver tenantResolver, IHttpContextAccessor accessor) : base(tenantResolver, accessor)
{
// DBContextBase handles multi-tenant database connections

View File

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
using Oqtane.Shared;
using File = Oqtane.Models.File;
namespace Oqtane.Repository
{
@ -11,11 +13,13 @@ namespace Oqtane.Repository
{
private TenantDBContext _db;
private readonly IPermissionRepository _permissions;
private readonly IFolderRepository _folderRepository;
public FileRepository(TenantDBContext context, IPermissionRepository permissions)
public FileRepository(TenantDBContext context, IPermissionRepository permissions, IFolderRepository folderRepository)
{
_db = context;
_permissions = permissions;
_folderRepository = folderRepository;
}
public IEnumerable<File> GetFiles(int folderId)
@ -74,5 +78,19 @@ namespace Oqtane.Repository
_db.File.Remove(file);
_db.SaveChanges();
}
public string GetFilePath(int fileId)
{
var file = _db.File.Find(fileId);
return GetFilePath(file);
}
public string GetFilePath(File file)
{
if (file == null) return null;
var folder = file.Folder ?? _db.Folder.Find(file.FolderId);
var filepath = Path.Combine(_folderRepository.GetFolderPath(folder), file.Name);
return filepath;
}
}
}

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
@ -11,11 +12,15 @@ namespace Oqtane.Repository
{
private TenantDBContext _db;
private readonly IPermissionRepository _permissions;
private readonly IWebHostEnvironment _environment;
private readonly ITenantResolver _tenants;
public FolderRepository(TenantDBContext context, IPermissionRepository permissions)
public FolderRepository(TenantDBContext context, IPermissionRepository permissions,IWebHostEnvironment environment, ITenantResolver tenants)
{
_db = context;
_permissions = permissions;
_environment = environment;
_tenants = tenants;
}
public IEnumerable<Folder> GetFolders(int siteId)
@ -85,5 +90,17 @@ namespace Oqtane.Repository
_db.Folder.Remove(folder);
_db.SaveChanges();
}
public string GetFolderPath(int folderId)
{
Folder folder = _db.Folder.Find(folderId);
return GetFolderPath(folder);
}
public string GetFolderPath(Folder folder)
{
return Utilities.PathCombine(_environment.ContentRootPath, "Content", "Tenants", _tenants.GetTenant().TenantId.ToString(), "Sites", folder.SiteId.ToString(), folder.Path);
}
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
@ -9,6 +9,7 @@ namespace Oqtane.Repository
Alias AddAlias(Alias alias);
Alias UpdateAlias(Alias alias);
Alias GetAlias(int aliasId);
Alias GetAlias(string name);
void DeleteAlias(int aliasId);
}
}

View File

@ -11,5 +11,7 @@ namespace Oqtane.Repository
File GetFile(int fileId);
File GetFile(int fileId, bool tracking);
void DeleteFile(int fileId);
string GetFilePath(int fileId);
string GetFilePath(File file);
}
}

View File

@ -12,5 +12,7 @@ namespace Oqtane.Repository
Folder GetFolder(int folderId, bool tracking);
Folder GetFolder(int siteId, string path);
void DeleteFolder(int folderId);
string GetFolderPath(int folderId);
string GetFolderPath(Folder folder);
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface ILanguageRepository
{
IEnumerable<Language> GetLanguages(int siteId);
Language AddLanguage(Language language);
Language GetLanguage(int languageId);
void DeleteLanguage(int languageId);
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
@ -9,6 +9,6 @@ namespace Oqtane.Repository
IEnumerable<ModuleDefinition> GetModuleDefinitions(int sideId);
ModuleDefinition GetModuleDefinition(int moduleDefinitionId, int sideId);
void UpdateModuleDefinition(ModuleDefinition moduleDefinition);
void DeleteModuleDefinition(int moduleDefinitionId, int siteId);
void DeleteModuleDefinition(int moduleDefinitionId);
}
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
@ -6,5 +6,6 @@ namespace Oqtane.Repository
public interface IThemeRepository
{
IEnumerable<Theme> GetThemes();
void DeleteTheme(string ThemeName);
}
}

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class LanguageRepository : ILanguageRepository
{
private TenantDBContext _db;
public LanguageRepository(TenantDBContext context)
{
_db = context;
}
public IEnumerable<Language> GetLanguages(int siteId) => _db.Language.Where(l => l.SiteId == siteId);
public Language AddLanguage(Language language)
{
if (language.IsDefault)
{
// Ensure all other languages are not set to current
_db.Language
.Where(l => l.SiteId == language.SiteId)
.ToList()
.ForEach(l => l.IsDefault = false);
}
_db.Language.Add(language);
_db.SaveChanges();
return language;
}
public Language GetLanguage(int languageId) => _db.Language.Find(languageId);
public void DeleteLanguage(int languageId)
{
var language = _db.Language.Find(languageId);
_db.Language.Remove(language);
_db.SaveChanges();
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
@ -16,7 +17,6 @@ namespace Oqtane.Repository
private MasterDBContext _db;
private readonly IMemoryCache _cache;
private readonly IPermissionRepository _permissions;
private List<ModuleDefinition> _moduleDefinitions; // lazy load
public ModuleDefinitionRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions)
{
@ -46,44 +46,71 @@ namespace Oqtane.Repository
_db.Entry(moduleDefinition).State = EntityState.Modified;
_db.SaveChanges();
_permissions.UpdatePermissions(moduleDefinition.SiteId, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, moduleDefinition.Permissions);
_cache.Remove("moduledefinitions:" + moduleDefinition.SiteId.ToString());
}
public void DeleteModuleDefinition(int moduleDefinitionId, int siteId)
public void DeleteModuleDefinition(int moduleDefinitionId)
{
ModuleDefinition moduleDefinition = _db.ModuleDefinition.Find(moduleDefinitionId);
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduleDefinitionId);
_db.ModuleDefinition.Remove(moduleDefinition);
_db.SaveChanges();
_cache.Remove("moduledefinitions");
}
public List<ModuleDefinition> LoadModuleDefinitions(int siteId)
{
// get module definitions for site
List<ModuleDefinition> moduleDefinitions = _cache.GetOrCreate("moduledefinitions:" + siteId.ToString(), entry =>
// get module definitions
List<ModuleDefinition> moduleDefinitions;
if (siteId != -1)
{
moduleDefinitions = _cache.GetOrCreate("moduledefinitions", entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return LoadSiteModuleDefinitions(siteId);
return LoadModuleDefinitions();
});
// get all module definition permissions for site
List<Permission> permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
// populate module definition permissions
foreach (ModuleDefinition moduledefinition in moduleDefinitions)
{
moduledefinition.SiteId = siteId;
if (permissions.Count == 0)
{
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.Permissions);
}
else
{
if (permissions.Where(item => item.EntityId == moduledefinition.ModuleDefinitionId).Any())
{
moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledefinition.ModuleDefinitionId).EncodePermissions();
}
else
{
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId, moduledefinition.Permissions);
}
}
}
// clean up any orphaned permissions
var ids = new HashSet<int>(moduleDefinitions.Select(item => item.ModuleDefinitionId));
foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId)))
{
_permissions.DeletePermission(permission.PermissionId);
}
}
else
{
moduleDefinitions = LoadModuleDefinitions();
}
return moduleDefinitions;
}
private List<ModuleDefinition> LoadSiteModuleDefinitions(int siteId)
{
if (_moduleDefinitions == null)
private List<ModuleDefinition> LoadModuleDefinitions()
{
// get module assemblies
_moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
}
List<ModuleDefinition> moduleDefinitions = _moduleDefinitions;
List<Permission> permissions = new List<Permission>();
if (siteId != -1)
{
// get module definition permissions for site
permissions = _permissions.GetPermissions(siteId, EntityNames.ModuleDefinition).ToList();
}
List<ModuleDefinition> moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
// get module definitions in database
List<ModuleDefinition> moduledefs = _db.ModuleDefinition.ToList();
@ -98,10 +125,6 @@ namespace Oqtane.Repository
moduledef = new ModuleDefinition { ModuleDefinitionName = moduledefinition.ModuleDefinitionName };
_db.ModuleDefinition.Add(moduledef);
_db.SaveChanges();
if (siteId != -1)
{
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
}
}
else
{
@ -126,31 +149,11 @@ namespace Oqtane.Repository
moduledefinition.Version = moduledef.Version;
}
if (siteId != -1)
{
if (permissions.Count == 0)
{
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
}
else
{
if (permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).Any())
{
moduledefinition.Permissions = permissions.Where(item => item.EntityId == moduledef.ModuleDefinitionId).EncodePermissions();
}
else
{
_permissions.UpdatePermissions(siteId, EntityNames.ModuleDefinition, moduledef.ModuleDefinitionId, moduledefinition.Permissions);
}
}
}
// remove module definition from list as it is already synced
moduledefs.Remove(moduledef);
}
moduledefinition.ModuleDefinitionId = moduledef.ModuleDefinitionId;
moduledefinition.SiteId = siteId;
moduledefinition.CreatedBy = moduledef.CreatedBy;
moduledefinition.CreatedOn = moduledef.CreatedOn;
moduledefinition.ModifiedBy = moduledef.ModifiedBy;
@ -160,11 +163,6 @@ namespace Oqtane.Repository
// any remaining module definitions are orphans
foreach (ModuleDefinition moduledefinition in moduledefs)
{
if (siteId != -1)
{
_permissions.DeletePermissions(siteId, EntityNames.ModuleDefinition, moduledefinition.ModuleDefinitionId);
}
_db.ModuleDefinition.Remove(moduledefinition); // delete
_db.SaveChanges();
}
@ -175,12 +173,16 @@ namespace Oqtane.Repository
private List<ModuleDefinition> LoadModuleDefinitionsFromAssemblies()
{
List<ModuleDefinition> moduleDefinitions = new List<ModuleDefinition>();
// iterate through Oqtane module assemblies
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll")))
{
moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
}
}
return moduleDefinitions;
}

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
@ -20,7 +20,7 @@ namespace Oqtane.Repository
{
return _db.Notification
.Where(item => item.SiteId == siteId)
.Where(item => item.IsDelivered == false)
.Where(item => item.IsDelivered == false && item.IsDeleted == false)
.Where(item => item.SendOn == null || item.SendOn < System.DateTime.UtcNow)
.ToList();
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -408,32 +408,6 @@ namespace Oqtane.Repository
Content = ""
}
}
}); pageTemplates.Add(new PageTemplate
{
Name = "Tenant Management",
Parent = "Admin",
Path = "admin/tenants",
Icon = Icons.List,
IsNavigation = false,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Tenants.Index).ToModuleDefinitionName(), Title = "Tenant Management", Pane = "Content",
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true)
}.EncodePermissions(),
Content = ""
}
}
});
pageTemplates.Add(new PageTemplate
{
@ -502,6 +476,37 @@ namespace Oqtane.Repository
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Language Management",
Parent = "Admin",
Path = "admin/languages",
Icon = Icons.Text,
IsNavigation = false,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = "Content",
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Scheduled Jobs", Parent = "Admin", Path = "admin/jobs", Icon = Icons.Timer, IsNavigation = false, IsPersonalizable = false,
PagePermissions = new List<Permission>
@ -525,12 +530,7 @@ namespace Oqtane.Repository
});
pageTemplates.Add(new PageTemplate
{
Name = "Sql Management",
Parent = "Admin",
Path = "admin/sql",
Icon = "spreadsheet",
IsNavigation = false,
IsPersonalizable = false,
Name = "Sql Management", Parent = "Admin", Path = "admin/sql", Icon = Icons.Spreadsheet, IsNavigation = false, IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
@ -552,12 +552,7 @@ namespace Oqtane.Repository
});
pageTemplates.Add(new PageTemplate
{
Name = "System Info",
Parent = "Admin",
Path = "admin/system",
Icon = "medical-cross",
IsNavigation = false,
IsPersonalizable = false,
Name = "System Info", Parent = "Admin", Path = "admin/system", Icon = Icons.MedicalCross, IsNavigation = false, IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
@ -9,24 +9,49 @@ namespace Oqtane.Repository
{
public class TenantResolver : ITenantResolver
{
private readonly Alias _alias;
private readonly Tenant _tenant;
private readonly IHttpContextAccessor _accessor;
private readonly IAliasRepository _aliasRepository;
private readonly ITenantRepository _tenantRepository;
private readonly SiteState _siteState;
private Alias _alias;
private Tenant _tenant;
public TenantResolver(IHttpContextAccessor accessor, IAliasRepository aliasRepository, ITenantRepository tenantRepository, SiteState siteState)
{
int aliasId = -1;
_accessor = accessor;
_aliasRepository = aliasRepository;
_tenantRepository = tenantRepository;
_siteState = siteState;
}
if (siteState != null && siteState.Alias != null)
public Alias GetAlias()
{
if (_alias == null) ResolveTenant();
return _alias;
}
public Tenant GetTenant()
{
if (_tenant == null) ResolveTenant();
return _tenant;
}
private void ResolveTenant()
{
if (_siteState != null && _siteState.Alias != null)
{
// background processes can pass in an alias using the SiteState service
_alias = siteState.Alias;
_alias = _siteState.Alias;
}
else
{
int aliasId = -1;
// get aliasid identifier based on request
if (accessor.HttpContext != null)
if (_accessor.HttpContext != null)
{
string[] segments = accessor.HttpContext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string[] segments = _accessor.HttpContext.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (segments.Length > 1 && (segments[1] == "api" || segments[1] == "pages") && segments[0] != "~")
{
aliasId = int.Parse(segments[0]);
@ -34,7 +59,7 @@ namespace Oqtane.Repository
}
// get the alias
IEnumerable<Alias> aliases = aliasRepository.GetAliases().ToList(); // cached
IEnumerable<Alias> aliases = _aliasRepository.GetAliases().ToList(); // cached
if (aliasId != -1)
{
_alias = aliases.FirstOrDefault(item => item.AliasId == aliasId);
@ -44,19 +69,10 @@ namespace Oqtane.Repository
if (_alias != null)
{
// get the tenant
IEnumerable<Tenant> tenants = tenantRepository.GetTenants(); // cached
IEnumerable<Tenant> tenants = _tenantRepository.GetTenants(); // cached
_tenant = tenants.FirstOrDefault(item => item.TenantId == _alias.TenantId);
}
}
public Alias GetAlias()
{
return _alias;
}
public Tenant GetTenant()
{
return _tenant;
}
}
}

View File

@ -1,7 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Themes;
@ -10,7 +12,12 @@ namespace Oqtane.Repository
{
public class ThemeRepository : IThemeRepository
{
private List<Theme> _themes; // lazy load
private readonly IMemoryCache _cache;
public ThemeRepository(IMemoryCache cache)
{
_cache = cache;
}
public IEnumerable<Theme> GetThemes()
{
@ -19,12 +26,14 @@ namespace Oqtane.Repository
private List<Theme> LoadThemes()
{
if (_themes == null)
// get module definitions
List<Theme> themes = _cache.GetOrCreate("themes", entry =>
{
// get themes
_themes = LoadThemesFromAssemblies();
}
return _themes;
entry.SlidingExpiration = TimeSpan.FromMinutes(30);
return LoadThemesFromAssemblies();
});
return themes;
}
private List<Theme> LoadThemesFromAssemblies()
@ -34,9 +43,12 @@ namespace Oqtane.Repository
// iterate through Oqtane theme assemblies
var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
foreach (Assembly assembly in assemblies)
{
if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll")))
{
themes = LoadThemesFromAssembly(themes, assembly);
}
}
return themes;
}
@ -143,5 +155,10 @@ namespace Oqtane.Repository
}
return themes;
}
public void DeleteTheme(string ThemeName)
{
_cache.Remove("themes");
}
}
}

View File

@ -0,0 +1,27 @@
/*
Version 2.0.1 Tenant migration script
*/
CREATE TABLE [dbo].[Language](
[LanguageId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](100) NOT NULL,
[Code] [nvarchar](10) NOT NULL,
[IsDefault] [bit] NOT NULL,
[SiteId] [int] NOT NULL,
[CreatedBy] [nvarchar](256) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[ModifiedBy] [nvarchar](256) NOT NULL,
[ModifiedOn] [datetime] NOT NULL,
CONSTRAINT [PK_Language] PRIMARY KEY CLUSTERED
(
[LanguageId] ASC
)
)
GO
ALTER TABLE [dbo].[Language] WITH CHECK ADD CONSTRAINT [FK_Language_Site] FOREIGN KEY([SiteId])
REFERENCES [dbo].[Site] ([SiteId])
ON DELETE CASCADE
GO

View File

@ -0,0 +1,17 @@
/*
Version 2.0.1 Tenant migration script
*/
DELETE FROM [dbo].[Page]
WHERE Path = 'admin/tenants';
GO
ALTER TABLE [dbo].[Site] ADD
[AdminContainerType] [nvarchar](200) NULL
GO
UPDATE [dbo].[Site] SET AdminContainerType = ''
GO

View File

@ -26,7 +26,6 @@ namespace Oqtane
{
public class Startup
{
private string _webRoot;
private Runtime _runtime;
private bool _useSwagger;
private IWebHostEnvironment _env;
@ -48,7 +47,6 @@ namespace Oqtane
//add possibility to switch off swagger on production.
_useSwagger = Configuration.GetSection("UseSwagger").Value != "false";
_webRoot = env.WebRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data"));
_env = env;
@ -128,13 +126,11 @@ namespace Oqtane
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<MasterDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppContext.GetData("DataDirectory")?.ToString())
));
services.AddDbContext<MasterDBContext>(options => { });
services.AddDbContext<TenantDBContext>(options => { });
services.AddIdentityCore<IdentityUser>(options => { })
@ -183,7 +179,7 @@ namespace Oqtane
services.AddSingleton<IDatabaseManager, DatabaseManager>();
// install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain )
InstallationManager.InstallPackages("Modules,Themes", _webRoot);
InstallationManager.InstallPackages("Modules,Themes", _env.WebRootPath, _env.ContentRootPath);
// register transient scoped core services
services.AddTransient<IModuleDefinitionRepository, ModuleDefinitionRepository>();
@ -213,6 +209,7 @@ namespace Oqtane
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// load the external assemblies into the app domain, install services
services.AddOqtane(_runtime, _supportedCultures);

View File

@ -4,4 +4,4 @@ XCOPY "..\Server\bin\Debug\net5.0\[Owner].[Module].Server.Oqtane.dll" "..\..\[Ro
XCOPY "..\Server\bin\Debug\net5.0\[Owner].[Module].Server.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y
XCOPY "..\Shared\bin\Debug\net5.0\[Owner].[Module].Shared.Oqtane.dll" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y
XCOPY "..\Shared\bin\Debug\net5.0\[Owner].[Module].Shared.Oqtane.pdb" "..\..\[RootFolder]\Oqtane.Server\bin\Debug\net5.0\" /Y
XCOPY "..\Server\wwwroot\Modules\[Owner].[Module]\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\Modules\[Owner].[Module]\" /Y /S /I
XCOPY "..\Server\wwwroot\*" "..\..\[RootFolder]\Oqtane.Server\wwwroot\" /Y /S /I

View File

@ -19,6 +19,11 @@
<EmbeddedResource Include="Scripts\[Owner].[Module].Uninstall.sql" />
</ItemGroup>
<ItemGroup>
<Content Remove="wwwroot\_content\**\*.*" />
<None Include="wwwroot\_content\**\*.*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.0" />

View File

@ -0,0 +1,11 @@
The _content folder should only contain static resources from shared razor component libraries (RCLs). Static resources can be extracted from shared RCL Nuget packages by executing a Publish task on the module's Server project to a local folder and copying the files from the _content folder which is created. Each shared RCL would have its own appropriately named subfolder within the module's _content folder.
ie.
/_content
/Radzen.Blazor
/css
/fonts
/syncfusion.blazor
/scripts
/styles

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Oqtane.Interfaces
{
public interface ISettingsControl
{
Task UpdateSettings();
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace Oqtane.Models
{
public class Language : IAuditable
{
public int LanguageId { get; set; }
public int? SiteId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public bool IsDefault { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedOn { get; set; }
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
@ -23,6 +23,50 @@ namespace Oqtane.Models
public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; }
public DateTime? SendOn { get; set; }
public Notification() {}
public Notification(int siteId, User from, User to, string subject, string body, int? parentId)
{
SiteId = siteId;
if (from != null)
{
FromUserId = from.UserId;
FromDisplayName = from.DisplayName;
FromEmail = from.Email;
}
if (to != null)
{
ToUserId = to.UserId;
ToDisplayName = to.DisplayName;
ToEmail = to.Email;
}
Subject = subject;
Body = body;
ParentId = parentId;
CreatedOn = DateTime.UtcNow;
IsDelivered = false;
DeliveredOn = null;
SendOn = DateTime.UtcNow;
}
public Notification(int siteId, string fromDisplayName, string fromEmail, string toDisplayName, string toEmail, string subject, string body)
{
SiteId = siteId;
FromUserId = null;
FromDisplayName = fromDisplayName;
FromEmail = fromEmail;
ToUserId = null;
ToDisplayName = toDisplayName;
ToEmail = toEmail;
Subject = subject;
Body = body;
ParentId = null;
CreatedOn = DateTime.UtcNow;
IsDelivered = false;
DeliveredOn = null;
SendOn = DateTime.UtcNow;
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
@ -13,6 +13,7 @@ namespace Oqtane.Models
public string DefaultThemeType { get; set; }
public string DefaultLayoutType { get; set; }
public string DefaultContainerType { get; set; }
public string AdminContainerType { get; set; }
public bool PwaIsEnabled { get; set; }
public int? PwaAppIconFileId { get; set; }
public int? PwaSplashIconFileId { get; set; }

View File

@ -44,7 +44,7 @@ namespace Oqtane.Shared {
public const string MasterTenant = TenantNames.Master;
public const string DefaultSite = "Default Site";
const string RoleObsoleteMessage = "Use the corresponding memeber from Oqtane.Shared.RoleNames";
const string RoleObsoleteMessage = "Use the corresponding member from Oqtane.Shared.RoleNames";
[Obsolete(RoleObsoleteMessage)]
public const string AllUsersRole = RoleNames.Everyone;

View File

@ -1,4 +1,4 @@
namespace Oqtane.Shared
namespace Oqtane.Shared
{
public class InstallConfig
{
@ -14,5 +14,6 @@
public string DefaultTheme { get; set; }
public string DefaultLayout { get; set; }
public string DefaultContainer { get; set; }
public string DefaultAdminContainer { get; set; }
}
}