@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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 />
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										107
									
								
								Oqtane.Client/Modules/Admin/Languages/Add.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Oqtane.Client/Modules/Admin/Languages/Add.razor
									
									
									
									
									
										Normal 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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								Oqtane.Client/Modules/Admin/Languages/Index.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Oqtane.Client/Modules/Admin/Languages/Index.razor
									
									
									
									
									
										Normal 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;"> </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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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 />
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
@namespace Oqtane.Modules.Admin.Modules
 | 
			
		||||
@using Oqtane.Interfaces
 | 
			
		||||
@inherits ModuleBase
 | 
			
		||||
@inject NavigationManager NavigationManager
 | 
			
		||||
@inject IThemeService ThemeService
 | 
			
		||||
@ -137,7 +138,7 @@
 | 
			
		||||
                builder.AddComponentReferenceCapture(1, inst => { _settings = Convert.ChangeType(inst, _settingsModuleType); });
 | 
			
		||||
                builder.CloseComponent();
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task SaveModule()
 | 
			
		||||
@ -162,15 +163,16 @@
 | 
			
		||||
        module.Permissions = _permissionGrid.GetPermissions();
 | 
			
		||||
        await ModuleService.UpdateModuleAsync(module);
 | 
			
		||||
 | 
			
		||||
        if (_settingsModuleType != null)
 | 
			
		||||
        
 | 
			
		||||
        if (_settings is ISettingsControl control)
 | 
			
		||||
        {
 | 
			
		||||
            var moduleType = Type.GetType(ModuleState.ModuleType);
 | 
			
		||||
            if (moduleType != null)
 | 
			
		||||
            {
 | 
			
		||||
                moduleType.GetMethod("UpdateSettings")?.Invoke(_settings, null); // method must be public in settings component
 | 
			
		||||
            }
 | 
			
		||||
             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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,7 +386,14 @@
 | 
			
		||||
                await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
 | 
			
		||||
 | 
			
		||||
                await logger.LogInformation("Page Added {Page}", page);
 | 
			
		||||
                NavigationManager.NavigateTo(NavigateUrl(page.Path));
 | 
			
		||||
                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
 | 
			
		||||
            {
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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,7 +493,14 @@
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await logger.LogInformation("Page Saved {Page}", page);
 | 
			
		||||
                NavigationManager.NavigateTo(NavigateUrl(page.Path));
 | 
			
		||||
                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
 | 
			
		||||
            {
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -10,128 +10,148 @@
 | 
			
		||||
 | 
			
		||||
@if (_initialized)
 | 
			
		||||
{
 | 
			
		||||
    <table class="table table-borderless">
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <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>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="logo" HelpText="Upload a logo for the site" ResourceKey="Logo">Logo: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="favicon" HelpText="Select Your default icon" ResourceKey="FavoriteIcon">Favicon: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))">
 | 
			
		||||
                    <option value="-"><@Localizer["Select Theme"]></option>
 | 
			
		||||
                    @foreach (var theme in _themes)
 | 
			
		||||
<table class="table table-borderless">
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <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>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="logo" HelpText="Upload a logo for the site" ResourceKey="Logo">Logo: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="favicon" HelpText="Select Your default icon" ResourceKey="FavoriteIcon">Favicon: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="defaultTheme" class="form-control" @onchange="(e => ThemeChanged(e))">
 | 
			
		||||
                <option value="-"><@Localizer["Select Theme"]></option>
 | 
			
		||||
                @foreach (var theme in _themes)
 | 
			
		||||
                {
 | 
			
		||||
                    if (theme.TypeName == _themetype)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (theme.TypeName == _themetype)
 | 
			
		||||
                        {
 | 
			
		||||
                            <option value="@theme.TypeName" selected>@theme.Name</option>
 | 
			
		||||
                        }
 | 
			
		||||
                        else
 | 
			
		||||
                        {
 | 
			
		||||
                            <option value="@theme.TypeName">@theme.Name</option>
 | 
			
		||||
                        }
 | 
			
		||||
                        <option value="@theme.TypeName" selected>@theme.Name</option>
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        <option value="@theme.TypeName">@theme.Name</option>
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    @if (_layouts.Count > 0)
 | 
			
		||||
    {
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="defaultLayout" HelpText="Select the sites default layout" ResourceKey="DefaultLayout">Default Layout: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="defaultLayout" class="form-control" @bind="@_layouttype">
 | 
			
		||||
                    <option value="-"><@Localizer["Select Layout"]></option>
 | 
			
		||||
                    @foreach (var layout in _layouts)
 | 
			
		||||
                    {
 | 
			
		||||
                        <option value="@(layout.TypeName)">@(layout.Name)</option>
 | 
			
		||||
                    }
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        @if (_layouts.Count > 0)
 | 
			
		||||
        {
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <Label For="defaultLayout" HelpText="Select the sites default layout" ResourceKey="DefaultLayout">Default Layout: </Label>
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <select id="defaultLayout" class="form-control" @bind="@_layouttype">
 | 
			
		||||
                        <option value="-"><@Localizer["Select Layout"]></option>
 | 
			
		||||
                        @foreach (var layout in _layouts)
 | 
			
		||||
                        {
 | 
			
		||||
                            <option value="@(layout.TypeName)">@(layout.Name)</option>
 | 
			
		||||
                        }
 | 
			
		||||
                    </select>
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        }
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="defaultContainer" class="form-control" @bind="@_containertype">
 | 
			
		||||
                    <option value="-"><@Localizer["Select Container"]></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>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="allowRegister" class="form-control" @bind="@_allowregistration">
 | 
			
		||||
                    <option value="True">@Localizer["Yes"]</option>
 | 
			
		||||
                    <option value="False">@Localizer["No"]</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td>
 | 
			
		||||
                <Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td>
 | 
			
		||||
                <select id="isDeleted" class="form-control" @bind="@_isdeleted">
 | 
			
		||||
                    <option value="True">@Localizer["Yes"]</option>
 | 
			
		||||
                    <option value="False">@Localizer["No"]</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    </table>
 | 
			
		||||
    }
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="defaultContainer" class="form-control" @bind="@_containertype">
 | 
			
		||||
                <option value="-"><@Localizer["Select Container"]></option>
 | 
			
		||||
                @foreach (var container in _containers)
 | 
			
		||||
                {
 | 
			
		||||
                    <option value="@container.TypeName">@container.Name</option>
 | 
			
		||||
                }
 | 
			
		||||
            </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="-"><@Localizer["Select Container"]></option>
 | 
			
		||||
                <option value=""><@Localizer["Default Admin Container"]></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>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="allowRegister" class="form-control" @bind="@_allowregistration">
 | 
			
		||||
                <option value="True">@Localizer["Yes"]</option>
 | 
			
		||||
                <option value="False">@Localizer["No"]</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>
 | 
			
		||||
            <select id="isDeleted" class="form-control" @bind="@_isdeleted">
 | 
			
		||||
                <option value="True">@Localizer["Yes"]</option>
 | 
			
		||||
                <option value="False">@Localizer["No"]</option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </td>
 | 
			
		||||
    </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
 | 
			
		||||
 | 
			
		||||
@ -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="-"><@Localizer["Select Container"]></option>
 | 
			
		||||
                <option value=""><@Localizer["Default Admin Container"]></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();
 | 
			
		||||
 | 
			
		||||
@ -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="-"><@Localizer["Select Container"]></option>
 | 
			
		||||
                    <option value=""><@Localizer["Default Admin Container"]></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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;"> </th>
 | 
			
		||||
            <th style="width: 1px;"> </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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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,32 +72,43 @@
 | 
			
		||||
            }
 | 
			
		||||
        </div>
 | 
			
		||||
    }
 | 
			
		||||
    <div class="mx-auto text-center">
 | 
			
		||||
        @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>
 | 
			
		||||
        }
 | 
			
		||||
        @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>
 | 
			
		||||
            @for (int i = _startPage; i <= _endPage; i++)
 | 
			
		||||
    @if (Toolbar == "Bottom")
 | 
			
		||||
    {
 | 
			
		||||
        <div class="mx-auto text-center">
 | 
			
		||||
            @if (_endPage > 1)
 | 
			
		||||
            {
 | 
			
		||||
                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 () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
 | 
			
		||||
            }
 | 
			
		||||
            <button class="btn btn-secondary" @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>
 | 
			
		||||
        }
 | 
			
		||||
        @if (_endPage > 1)
 | 
			
		||||
        {
 | 
			
		||||
            <span class="btn btn-link disabled">Page @_page of @_pages</span>
 | 
			
		||||
        }
 | 
			
		||||
    </div>
 | 
			
		||||
            @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>
 | 
			
		||||
    }
 | 
			
		||||
</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")
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
@ -37,11 +37,11 @@ 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);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            return await GetJsonAsync<List<File>>($"{Apiurl}/{siteId}/{path}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
using Oqtane.Models;
 | 
			
		||||
using Oqtane.Models;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
@ -37,11 +37,11 @@ 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);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            return await GetJsonAsync<Folder>($"{ApiUrl}/{siteId}/{path}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								Oqtane.Client/Services/Interfaces/ILanguageService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Oqtane.Client/Services/Interfaces/ILanguageService.cs
									
									
									
									
									
										Normal 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								Oqtane.Client/Services/LanguageService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Oqtane.Client/Services/LanguageService.cs
									
									
									
									
									
										Normal 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}");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								Oqtane.Client/Themes/Controls/FontIcon.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Oqtane.Client/Themes/Controls/FontIcon.razor
									
									
									
									
									
										Normal 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; }
 | 
			
		||||
}
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								Oqtane.Client/Themes/Controls/MenuItemsBase.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Oqtane.Client/Themes/Controls/MenuItemsBase.cs
									
									
									
									
									
										Normal 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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Oqtane.Client/Themes/Controls/MenuItemsHorizontal.razor
									
									
									
									
									
										Normal 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>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								Oqtane.Client/Themes/Controls/MenuItemsVertical.razor
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								Oqtane.Client/Themes/Controls/MenuItemsVertical.razor
									
									
									
									
									
										Normal 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>
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 =>
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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\" " +
 | 
			
		||||
            "}] " +
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								Oqtane.Server/Controllers/LanguageController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Oqtane.Server/Controllers/LanguageController.cs
									
									
									
									
									
										Normal 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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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,34 +127,45 @@ 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))
 | 
			
		||||
                    {
 | 
			
		||||
                        List<string> assets = JsonSerializer.Deserialize<List<string>>(System.IO.File.ReadAllText(assetfilepath));
 | 
			
		||||
                        foreach(string asset in assets)
 | 
			
		||||
                        {
 | 
			
		||||
                            if (System.IO.File.Exists(asset))
 | 
			
		||||
                            {
 | 
			
		||||
                                System.IO.File.Delete(asset);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Assets Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // clean up module static resource folder
 | 
			
		||||
                    string folder = Path.Combine(_environment.WebRootPath, Path.Combine("Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName)));
 | 
			
		||||
                    if (Directory.Exists(folder))
 | 
			
		||||
                    {
 | 
			
		||||
                        Directory.Delete(folder, true);
 | 
			
		||||
                        _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Resources Folder Removed For {ModuleDefinitionName}", moduledefinition.ModuleDefinitionName);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // remove module definition
 | 
			
		||||
                    _moduleDefinitions.DeleteModuleDefinition(id, siteid);
 | 
			
		||||
                    _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // remove module assets
 | 
			
		||||
                string assetpath = Path.Combine(_environment.WebRootPath, "Modules", Utilities.GetTypeName(moduledefinition.ModuleDefinitionName));
 | 
			
		||||
                if (System.IO.File.Exists(Path.Combine(assetpath, "assets.json")))
 | 
			
		||||
                {
 | 
			
		||||
                    // 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)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 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(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
 | 
			
		||||
                if (Directory.Exists(assetpath))
 | 
			
		||||
                {
 | 
			
		||||
                    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);
 | 
			
		||||
                _logger.Log(LogLevel.Information, this, LogFunction.Delete, "Module Definition {ModuleDefinitionName} Deleted", moduledefinition.Name);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -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)));
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
                    {
 | 
			
		||||
 | 
			
		||||
@ -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));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@ -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,27 +170,52 @@ 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);
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // 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);
 | 
			
		||||
 | 
			
		||||
                if (_executingTask.IsCompleted)
 | 
			
		||||
                {
 | 
			
		||||
                    return _executingTask;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                // can occur during the initial installation as there is no DBContext
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _executingTask = ExecuteAsync(_cancellationTokenSource.Token);
 | 
			
		||||
 | 
			
		||||
            if (_executingTask.IsCompleted)
 | 
			
		||||
            {
 | 
			
		||||
                return _executingTask;
 | 
			
		||||
                // can occur during the initial installation because this method is called during startup and the database has not yet been created
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net;
 | 
			
		||||
@ -14,64 +14,91 @@ 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)
 | 
			
		||||
            // 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 current tenant
 | 
			
		||||
            List<Site> sites = siteRepository.GetSites().ToList();
 | 
			
		||||
            foreach (Site site in sites)
 | 
			
		||||
            {
 | 
			
		||||
                if (tenants.Contains(alias.TenantId)) continue;
 | 
			
		||||
                tenants.Add(alias.TenantId);
 | 
			
		||||
                log += "Processing Notifications For Site: " + site.Name + "<br />";
 | 
			
		||||
 | 
			
		||||
                // 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
 | 
			
		||||
                var siteRepository = provider.GetRequiredService<ISiteRepository>();
 | 
			
		||||
                var settingRepository = provider.GetRequiredService<ISettingRepository>();
 | 
			
		||||
                var notificationRepository = provider.GetRequiredService<INotificationRepository>();
 | 
			
		||||
 | 
			
		||||
                // iterate through sites for this tenant
 | 
			
		||||
                List<Site> sites = siteRepository.GetSites().ToList();
 | 
			
		||||
                foreach (Site site in sites)
 | 
			
		||||
                // 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"] != "" &&
 | 
			
		||||
                    settings.ContainsKey("SMTPPort") && settings["SMTPPort"] != "" &&
 | 
			
		||||
                    settings.ContainsKey("SMTPSSL") && settings["SMTPSSL"] != "" &&
 | 
			
		||||
                    settings.ContainsKey("SMTPSender") && settings["SMTPSender"] != "")
 | 
			
		||||
                {
 | 
			
		||||
                    log += "Processing Notifications For Site: " + site.Name + "<br />";
 | 
			
		||||
 | 
			
		||||
                    // 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"] != "")
 | 
			
		||||
                    // construct SMTP Client 
 | 
			
		||||
                    var client = new SmtpClient()
 | 
			
		||||
                    {
 | 
			
		||||
                        // construct SMTP Client 
 | 
			
		||||
                        var client = new SmtpClient()
 | 
			
		||||
                        DeliveryMethod = SmtpDeliveryMethod.Network,
 | 
			
		||||
                        UseDefaultCredentials = false,
 | 
			
		||||
                        Host = settings["SMTPHost"],
 | 
			
		||||
                        Port = int.Parse(settings["SMTPPort"]),
 | 
			
		||||
                        EnableSsl = bool.Parse(settings["SMTPSSL"])
 | 
			
		||||
                    };
 | 
			
		||||
                    if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
 | 
			
		||||
                    {
 | 
			
		||||
                        client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // 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)
 | 
			
		||||
                        {
 | 
			
		||||
                            DeliveryMethod = SmtpDeliveryMethod.Network,
 | 
			
		||||
                            UseDefaultCredentials = false,
 | 
			
		||||
                            Host = settings["SMTPHost"],
 | 
			
		||||
                            Port = int.Parse(settings["SMTPPort"]),
 | 
			
		||||
                            EnableSsl = bool.Parse(settings["SMTPSSL"])
 | 
			
		||||
                        };
 | 
			
		||||
                        if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
 | 
			
		||||
                            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)
 | 
			
		||||
                        {
 | 
			
		||||
                            client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
 | 
			
		||||
                            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;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // iterate through notifications
 | 
			
		||||
                        int sent = 0;
 | 
			
		||||
                        List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
 | 
			
		||||
                        foreach (Notification notification in notifications)
 | 
			
		||||
                        // 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";
 | 
			
		||||
@ -92,7 +119,7 @@ namespace Oqtane.Infrastructure
 | 
			
		||||
                            }
 | 
			
		||||
                            mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
 | 
			
		||||
                            mailMessage.Body += notification.Body;
 | 
			
		||||
                            
 | 
			
		||||
 | 
			
		||||
                            // send mail
 | 
			
		||||
                            try
 | 
			
		||||
                            {
 | 
			
		||||
@ -108,12 +135,12 @@ namespace Oqtane.Infrastructure
 | 
			
		||||
                                log += ex.Message + "<br />";
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        log += "Notifications Delivered: " + sent + "<br />";
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        log += "SMTP Not Configured" + "<br />";
 | 
			
		||||
                    }
 | 
			
		||||
                    log += "Notifications Delivered: " + sent + "<br />";
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    log += "SMTP Not Configured Properly In Site Settings - Host, Port, SSL, And Sender Are All Required" + "<br />";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
                    .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
 | 
			
		||||
            );
 | 
			
		||||
            var tenant = _tenantResolver.GetTenant();
 | 
			
		||||
            if (tenant != null)
 | 
			
		||||
            {
 | 
			
		||||
                optionsBuilder.UseSqlServer(tenant.DBConnectionString
 | 
			
		||||
                        .Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            base.OnConfiguring(optionsBuilder);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Oqtane.Server/Repository/Interfaces/ILanguageRepository.cs
									
									
									
									
									
										Normal 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										44
									
								
								Oqtane.Server/Repository/LanguageRepository.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Oqtane.Server/Repository/LanguageRepository.cs
									
									
									
									
									
										Normal 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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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)
 | 
			
		||||
            {
 | 
			
		||||
                entry.SlidingExpiration = TimeSpan.FromMinutes(30);
 | 
			
		||||
                return LoadSiteModuleDefinitions(siteId);
 | 
			
		||||
            });
 | 
			
		||||
                moduleDefinitions = _cache.GetOrCreate("moduledefinitions", entry =>
 | 
			
		||||
                {
 | 
			
		||||
                    entry.SlidingExpiration = TimeSpan.FromMinutes(30);
 | 
			
		||||
                    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)
 | 
			
		||||
        private List<ModuleDefinition> LoadModuleDefinitions()
 | 
			
		||||
        {
 | 
			
		||||
            if (_moduleDefinitions == null)
 | 
			
		||||
            {
 | 
			
		||||
                // 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();
 | 
			
		||||
            }
 | 
			
		||||
            // get module assemblies 
 | 
			
		||||
            List<ModuleDefinition> moduleDefinitions = LoadModuleDefinitionsFromAssemblies();
 | 
			
		||||
 | 
			
		||||
            // get module definitions in database
 | 
			
		||||
            List<ModuleDefinition> moduledefs = _db.ModuleDefinition.ToList();
 | 
			
		||||
@ -95,13 +122,9 @@ namespace Oqtane.Repository
 | 
			
		||||
                if (moduledef == null)
 | 
			
		||||
                {
 | 
			
		||||
                    // new module definition
 | 
			
		||||
                    moduledef = new ModuleDefinition {ModuleDefinitionName = moduledefinition.ModuleDefinitionName};
 | 
			
		||||
                    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,11 +173,15 @@ 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)
 | 
			
		||||
            {
 | 
			
		||||
                moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
 | 
			
		||||
                if (System.IO.File.Exists(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), Utilities.GetTypeName(assembly.FullName) + ".dll")))
 | 
			
		||||
                {
 | 
			
		||||
                    moduleDefinitions = LoadModuleDefinitionsFromAssembly(moduleDefinitions, assembly);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return moduleDefinitions;
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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),
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
@ -35,7 +44,10 @@ namespace Oqtane.Repository
 | 
			
		||||
            var assemblies = AppDomain.CurrentDomain.GetOqtaneAssemblies();
 | 
			
		||||
            foreach (Assembly assembly in assemblies)
 | 
			
		||||
            {
 | 
			
		||||
                themes = LoadThemesFromAssembly(themes, assembly);
 | 
			
		||||
                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");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								Oqtane.Server/Scripts/Tenant.02.00.01.02.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Oqtane.Server/Scripts/Tenant.02.00.01.02.sql
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										17
									
								
								Oqtane.Server/Scripts/Tenant.02.00.01.03.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Oqtane.Server/Scripts/Tenant.02.00.01.03.sql
									
									
									
									
									
										Normal 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
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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" />
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Oqtane.Server/wwwroot/Modules/Templates/External/Server/wwwroot/_content/Placeholder.txt
									
									
									
									
										vendored
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										9
									
								
								Oqtane.Shared/Interfaces/ISettingsControl.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Oqtane.Shared/Interfaces/ISettingsControl.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace Oqtane.Interfaces
 | 
			
		||||
{
 | 
			
		||||
    public interface ISettingsControl
 | 
			
		||||
    {
 | 
			
		||||
        Task UpdateSettings();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								Oqtane.Shared/Models/Language.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Oqtane.Shared/Models/Language.cs
									
									
									
									
									
										Normal 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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user